Skip to content

wrap layer expansion can loop forever when the core content contains {CORE_TEMPLATE} #4688

Description

@pkoppularipple

Affected: scripts/bash/common.sh:606, confirmed present at tag v0.11.9
(fetched from raw.githubusercontent.com/github/spec-kit/v0.11.9/scripts/bash/common.sh).

The wrap strategy substitutes the core content into the layer at each {CORE_TEMPLATE}
placeholder:

case "$layer_content" in
    *'{CORE_TEMPLATE}'*) ;;
    *) echo "Error: wrap strategy missing {CORE_TEMPLATE} placeholder" >&2; return 1 ;;
esac
while [[ "$layer_content" == *'{CORE_TEMPLATE}'* ]]; do
    local before="${layer_content%%\{CORE_TEMPLATE\}*}"
    local after="${layer_content#*\{CORE_TEMPLATE\}}"
    layer_content="${before}${content}${after}"
done

The loop condition re-tests the string it just substituted into. If $content itself
contains the literal {CORE_TEMPLATE}, every iteration reintroduces the placeholder, the
condition never goes false, and layer_content grows by ${#content} each pass — an
unbounded loop that ends in memory exhaustion rather than an error message.

The guard above it does not cover this: it rejects a layer that is missing the
placeholder, and says nothing about the content being substituted in.

Suggested fix — scan left to right and never re-scan what was already substituted, which
also preserves the multi-placeholder behaviour the loop exists for:

out=""; rest="$layer_content"
while [[ "$rest" == *'{CORE_TEMPLATE}'* ]]; do
    out="${out}${rest%%\{CORE_TEMPLATE\}*}${content}"
    rest="${rest#*\{CORE_TEMPLATE\}}"
done
layer_content="${out}${rest}"

Reachability / why we are reporting rather than patching. Found while adopting a
Spec Kit-based plugin in a downstream repo. It is not reachable through that plugin: it
ships nothing that declares {CORE_TEMPLATE}, so $content never carries the placeholder
on that path (grep -rl CORE_TEMPLATE over the plugin returns nothing). It is reachable for
any consumer that authors a wrap template layer whose core content includes the literal
token — which is a normal thing to do by accident when a template documents its own
placeholder syntax.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions