Skip to content

fix(recipe): host steps left dir: and script: unsubstituted - #216

Merged
kuny0707 merged 1 commit into
tronprotocol:developfrom
barbatos2011:fix/recipe-host-substitution
Aug 6, 2026
Merged

kuny0707 merged 1 commit into
tronprotocol:developfrom
barbatos2011:fix/recipe-host-substitution

Conversation

@barbatos2011

Copy link
Copy Markdown

kind: host (#214) wired substitution for args, run[1:] and env: — and missed the other two fields it accepts. Found by writing the first real recipe that uses them: a private chain, a TRC20 deploy, and a load run. Both fields were broken, and only one broke visibly.

script: — the silent one

The template text reached the shell verbatim:

script: until curl -sf {{ params.node_url }}/wallet/getnowblock; do sleep 2; done

That curled a literal {{ params.node_url }} forever. The step ran, nothing errored, and the recipe hung until I gave up and killed it.

Substituting it is not the fix. Interpolating a value into a shell body is command injection by construction — a param of ; rm -rf / would execute. env: is already substituted and the shell sees it as data, so {{ }} inside script: is now rejected at load time with a message naming that alternative:

env:    {NODE_URL: "{{ params.node_url }}"}
script: curl -sf "$NODE_URL/wallet/getnowblock"

dir: — the opposite treatment

Now substituted. It becomes cmd.Dir, a path handed to chdir where a hostile value can only fail to open. No shell, no injection surface.

It failed loudly — chdir {{ params.lab_dir }}/contract: no such file or directory — which is the only reason it was the second bug found rather than the first.

The two fields differing is the point, and the rule is where the value lands: a path substitutes, code does not. Both are documented on the struct fields so the next field added has a rule to follow.

Testing

  • make test -race — 25 packages, 0 failures
  • make lint — no findings
  • Three new tests: {{ }} in script: is rejected and the error names env:; env: actually carries substituted values into the shell; dir: substitutes
  • Verified by the recipe that found it — 5 steps, status=success in 12.6s, ending in an assertion that TRC20 receivers hold exactly tx_count * amount

Still open

kind: host has no per-step timeout, which is why the script: bug presented as an unbounded hang rather than a failure. That was deliberately deferred in #214 (the runner has no timeout for command steps either, and it wants to arrive for both at once) — but this is the second time its absence turned a bug into a wedged run, so it is worth doing.

`kind: host` wired substitution for args and run[1:] and env:, and missed
the other two fields it accepts. Found by writing the first real recipe
that uses them — a private chain, a TRC20 deploy and a load run — where
both failed, and only one failed visibly.

**script: was the dangerous one.** The template text reached the shell
verbatim, so

    script: until curl -sf {{ params.node_url }}/wallet/getnowblock; do sleep 2; done

curled a literal "{{ params.node_url }}" forever. The step ran, nothing
errored, and the recipe hung until the operator gave up — a silent wrong
result in the most literal sense.

Substituting it is not the fix: interpolating a value into a shell body
is command injection by construction, and a param of "; rm -rf /" would
execute. env: is already substituted and the shell sees it as data, so
{{ }} inside script: is now rejected at load time with a message naming
that alternative:

    env:    {NODE_URL: "{{ params.node_url }}"}
    script: curl -sf "$NODE_URL/wallet/getnowblock"

**dir: gets the opposite treatment and is now substituted.** It becomes
cmd.Dir, a path handed to chdir where a hostile value can only fail to
open — no shell, no injection surface. It failed loudly ("chdir
{{ params.lab_dir }}/contract: no such file or directory") rather than
silently, which is the only reason it was the second bug found and not
the first.

The two fields differing is the point, and the rule is where the value
lands: a path substitutes, code does not.
@kuny0707
kuny0707 merged commit 5b40754 into tronprotocol:develop Aug 6, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants