From c8e5bfe2bb4bd250c9a30111fa2f34398a81b975 Mon Sep 17 00:00:00 2001 From: ayushcodes10 Date: Tue, 22 Sep 2026 15:14:54 +0530 Subject: [PATCH] Stop substituting INPUT_PATH into an interpreted shell command in Step 1 Step 1's own bash and PowerShell fragments both built a command whose literal text was the resolved scan path, substituted verbatim by the agent following the instructions before that line ever ran. The bash form, echo dollar paren cd INPUT_PATH and pwd close paren, still expands a command substitution even inside the outer double quotes, and the cd itself was entirely unquoted; the PowerShell form passed INPUT_PATH as a bare unquoted argument to Resolve Path, exposed to the same class of problem through a stray semicolon or pipe. A path drawn from an untrusted source, a malicious readme's suggested command, a copied value from an untrusted page, executed as code the moment this line ran, before any Python was ever reached. Both now capture INPUT_PATH through a construct their own interpreter guarantees performs no expansion at all: a quoted bash heredoc for the posix form, a single quoted PowerShell here string for the windows form. The raw text reaches Python (respectively Resolve Path) only as inert data on standard input, never as source the shell itself parses. Verified for the posix form by actually executing the extracted Step 1 block with a malicious substituted value and confirming no injected command runs; the PowerShell form could not be executed in this environment (no interpreter available to install without an interactive credential prompt), so it was written using well established, unambiguous here string syntax and reviewed carefully rather than run. As a side effect of no longer building the path through an unquoted cd, a legitimate path containing a space is now handled correctly too, and a nonexistent path now fails the whole step loudly instead of silently continuing past a swallowed cd error. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_017qfdzgbA5KedGEjD1AayNh (cherry picked from commit 9a7e5d0a51aa9c48055f6cd2d1b9dc12edb72d9c) --- tools/skillgen/fragments/shell/posix.md | 10 ++++++++-- tools/skillgen/fragments/shell/powershell.md | 11 +++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/tools/skillgen/fragments/shell/posix.md b/tools/skillgen/fragments/shell/posix.md index 3534417d2..ed0c555aa 100644 --- a/tools/skillgen/fragments/shell/posix.md +++ b/tools/skillgen/fragments/shell/posix.md @@ -30,8 +30,14 @@ fi # Write interpreter path for all subsequent steps (persists across invocations) mkdir -p graphify-out "$PYTHON" -c "import sys; open('graphify-out/.graphify_python', 'w', encoding='utf-8').write(sys.executable)" -# Save scan root so `graphify update` (no args) knows where to look next time -echo "$(cd INPUT_PATH && pwd)" > graphify-out/.graphify_root +# Save scan root so `graphify update` (no args) knows where to look next time. +# INPUT_PATH is passed through a quoted heredoc, never substituted into the +# command line itself: a bare `cd INPUT_PATH` (or an unquoted heredoc, which +# still expands $()/backticks in its body) would let a malicious path execute +# as shell code the moment this line runs. +"$PYTHON" -c "import os, sys; out_path = os.path.abspath('graphify-out/.graphify_root'); os.chdir(sys.stdin.readline().rstrip('\n')); open(out_path, 'w', encoding='utf-8').write(os.getcwd())" <<'GRAPHIFY_ROOT_EOF' +INPUT_PATH +GRAPHIFY_ROOT_EOF ``` If the import succeeds, print nothing and move straight to Step 2. diff --git a/tools/skillgen/fragments/shell/powershell.md b/tools/skillgen/fragments/shell/powershell.md index 71e493cf8..7837595ea 100644 --- a/tools/skillgen/fragments/shell/powershell.md +++ b/tools/skillgen/fragments/shell/powershell.md @@ -57,8 +57,15 @@ if (-not $GRAPHIFY_PYTHON) { # encoding writes the bytes POSIX writes, and adds no trailing newline. $Utf8NoBom = New-Object System.Text.UTF8Encoding $false [System.IO.File]::WriteAllText((Join-Path $PWD 'graphify-out\.graphify_python'), [string]$GRAPHIFY_PYTHON, $Utf8NoBom) -# Save scan root so `graphify update` (no args) knows where to look next time -[System.IO.File]::WriteAllText((Join-Path $PWD 'graphify-out\.graphify_root'), (Resolve-Path INPUT_PATH).Path, $Utf8NoBom) +# Save scan root so `graphify update` (no args) knows where to look next time. +# INPUT_PATH is captured through a single-quoted (literal) here-string, never +# substituted directly into the command line: an unquoted bareword argument +# substituted here would let a malicious path (a stray `;`, `|`, or `$(...)`) +# execute as script code the moment this line runs. +$InputPathRaw = @' +INPUT_PATH +'@ +[System.IO.File]::WriteAllText((Join-Path $PWD 'graphify-out\.graphify_root'), (Resolve-Path $InputPathRaw.Trim()).Path, $Utf8NoBom) ``` If the import succeeds, print nothing and move straight to Step 2.