Skip to content

sec(template): #each re-evaluates data values as template directives, enabling variable exfiltration #1164

@chaliy

Description

@chaliy

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

  • Template #each handler escapes or does not re-evaluate {{}} syntax in data values
  • Add test: JSON data containing {{VAR}} in #each must produce literal {{VAR}} in output, not the variable's value
  • Add test: Nested template directives in data ({{#if ...}}) must not be evaluated
  • Update threat model with new template injection category

Proposed Fix

Replace the naive string substitution with an approach that doesn't re-evaluate data:

  1. Process the template body normally but track {{.}} positions
  2. After template evaluation, substitute {{.}} markers with the raw data value
  3. Or: escape {{ in item_str before replacement (e.g., replace {{ with a safe sentinel)

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