aider/devin skills still build save-result/add commands from unescaped free text (RCE via $(...) in double quotes)
Summary
PR #3439/#3442 fixed the disk-write-capable skill platforms so graphify save-result/graphify add build their arguments from a file instead of substituting free text (a question, an LLM-generated answer, a scraped page's author/contributor name) directly into a shell command or Python source string. The aider and devin monolith skills, which have no separate file-write tool and instead run everything through $(cat graphify-out/.graphify_python) -c "..." / inline shell commands, were not covered by that fix and still use the vulnerable pattern.
Evidence (current v8)
tools/skillgen/fragments/core/aider.md (and the byte-identical pattern in tools/skillgen/fragments/core/devin.md, lines shifted):
L1045, L1120, L1186 — three separate
save-resultinvocations built as a single shell command with the free-text answer substituted directly into a double-quoted argument:$(cat graphify-out/.graphify_python) -m graphify save-result --question "QUESTION" --answer "ANSWER" --type query --nodes NODE1 NODE2Bash still performs
$(...)and backtick command substitution inside double quotes. IfANSWER(an LLM-generated answer, which can echo content from an untrusted source such as a fetched webpage) is substituted verbatim and contains$(some command), that command executes when the agent runs the line. This is not a corruption risk like single-quote breakout, it is a direct RCE vector, and it defeats the exact protection--answer-file/--question-file/--nodes-file(already shipped for disk-capable platforms in #3439) exists to provide.L1202 (aider.md) / L1334 (devin.md) — the
/graphify addflow builds a Python source string with the URL/author/contributor substituted into single-quoted Python literals:out = ingest('URL', Path('./raw'), author='AUTHOR', contributor='CONTRIBUTOR')A value containing an embedded
'(or\) breaks out of the string literal, letting an attacker-controlled URL, author, or contributor field (again, free text that can originate from a fetched page) inject arbitrary Python that then runs inside the-cscript — the exact "building generated code from unsanitized free text" class of bug #3439 targeted, just via a Python string literal instead of a JSON payload.
Why this needs its own fix, not a quick patch
Closing this properly needs a mechanism that works for a platform with only raw shell/python execution (no dedicated file-write tool), across all six affected call sites (3 save-result blocks x 2 files, plus the ingest() call x 2 files). The shape that avoids re-introducing the same risk: a quoted heredoc (<<'EOF' ... EOF, whose body bash does not expand at all) to get the free text onto disk without any shell interpretation, then dispatch through the already-existing --question-file/--answer-file/--nodes-file flags for save-result, and a JSON payload file (mirroring the disk-platform add fix) read via stdin or a temp file for ingest(). That is a real design/rewrite across two ~1300+ line monolith fragments plus their generated expected/ artifacts and monolith-roundtrip predicates, not a one-line fix, so it deserves its own dedicated PR with careful review rather than being folded into #3442's narrower scope.
Suggested fix direction
- For
save-result: replace each inline--question "QUESTION" --answer "ANSWER"shell line with instructions to write QUESTION/ANSWER/NODES to temp files via a quoted heredoc (cat > "$f" <<'GRAPHIFY_EOF' ... GRAPHIFY_EOF, single-quoted delimiter so bash performs zero expansion on the body), then invokesave-result --question-file "$qf" --answer-file "$af" --nodes-file "$nf". - For
/graphify add: same heredoc technique to write a JSON payload file, then have the-cscript read andjson.load()it (mirroring the already-fixed disk-platformadd-watch.mdflow) instead of embedding values as Python literals.
Filed as a standalone issue per triage of PR #3442's review — the fix there addressed only the two comments actually flagged on that PR (cli.py file-read error handling, and the same-class doc wording gap in the disk-platform add-watch.md), not this pre-existing, broader gap discovered while investigating it.
Source: Graphify-Labs/graphify