Summary
The template builtin's {{#each}} block handler performs a string replacement of {{.}} with the current array item value, then passes the result through the template engine for recursive evaluation. If a JSON data value contains template syntax like {{SECRET_VAR}}, it will be evaluated as a variable reference, leaking shell variables and environment variables to untrusted data.
Threat category: NEW — Template Injection (no existing TM-* category covers data-to-code injection in template processing)
Severity: Medium
Component: crates/bashkit/src/builtins/template.rs, line ~232
Root Cause
In render_template_inner(), the #each handler does:
let rendered_body = block_body.replace("{{.}}", &item_str);
let rendered = render_template_inner(&rendered_body, ...)?;
The item_str (from JSON data) is interpolated into the template string before recursive template evaluation. Any {{variable}} syntax in the data value is then processed as a template directive.
Steps to Reproduce
# Set a secret variable
SECRET_KEY=s3cr3t_value_123
# Create JSON data with malicious item
echo '{"items": ["normal", "{{SECRET_KEY}}", "also_normal"]}' > /data.json
# Create template using #each
printf '{{#each items}}Item: {{.}}\n{{/each}}' > /tpl.txt
# Execute template - SECRET_KEY value is leaked
template -d /data.json /tpl.txt
# Output includes: Item: s3cr3t_value_123
Verified with a Rust test that confirms the secret value appears in the output.
Impact
- Information disclosure: Attacker-controlled JSON data can exfiltrate any shell variable or environment variable
- AI agent context: An LLM could be manipulated to create malicious JSON files that leak secrets when processed with
template
- Cross-boundary leak: Data from external APIs stored in JSON can read internal shell state
Acceptance Criteria
Proposed Fix
Replace the naive string substitution with an approach that doesn't re-evaluate data:
- Process the template body normally but track
{{.}} positions
- After template evaluation, substitute
{{.}} markers with the raw data value
- Or: escape
{{ in item_str before replacement (e.g., replace {{ with a safe sentinel)
Summary
The template builtin's
{{#each}}block handler performs a string replacement of{{.}}with the current array item value, then passes the result through the template engine for recursive evaluation. If a JSON data value contains template syntax like{{SECRET_VAR}}, it will be evaluated as a variable reference, leaking shell variables and environment variables to untrusted data.Threat category: NEW — Template Injection (no existing TM-* category covers data-to-code injection in template processing)
Severity: Medium
Component:
crates/bashkit/src/builtins/template.rs, line ~232Root Cause
In
render_template_inner(), the#eachhandler does:The
item_str(from JSON data) is interpolated into the template string before recursive template evaluation. Any{{variable}}syntax in the data value is then processed as a template directive.Steps to Reproduce
Verified with a Rust test that confirms the secret value appears in the output.
Impact
templateAcceptance Criteria
#eachhandler escapes or does not re-evaluate{{}}syntax in data values{{VAR}}in#eachmust produce literal{{VAR}}in output, not the variable's value{{#if ...}}) must not be evaluatedProposed Fix
Replace the naive string substitution with an approach that doesn't re-evaluate data:
{{.}}positions{{.}}markers with the raw data value{{initem_strbefore replacement (e.g., replace{{with a safe sentinel)