fix(security): close find -exec / write side-doors in shell command whitelist (CWE-184) - #2740
Conversation
… whitelist (CWE-184) run_shell_command is documented as "read-only, informational commands only" and enforces it with a command-name whitelist. But the validator only inspected the first token, so whitelisted commands with write/exec predicates smuggled prohibited operations past it: find … -exec/-execdir/-ok/-okdir <binary> → runs any non-whitelisted binary find … -delete → deletes files find … -fprint/-fprintf/-fls FILE → writes files sort -o/--output FILE → writes files uniq INPUT OUTPUT → writes files _validate_command now rejects these predicates for find/sort/uniq while leaving the read-only forms (-print/-printf/-ls/-name, plain sort/uniq) allowed. Runs per pipeline segment via the existing per-segment call, so `ls | sort -o x` is covered too.
Deep review found sort accepts unambiguous long-option abbreviations (--o, --out, --output=), which the initial guard missed — each still writes a file. Match any --output prefix abbreviation.
966f23d to
3b86b27
Compare
|
Verdict: Approve. This closes a real whitelist bypass (CWE-184): The guards deliberately err toward over-blocking, which is the right direction for a security check. Only nit is a slightly inaccurate comment; nothing blocking. Real-world evidenceStrong — the evidence bundle exercises the real
Agent-UI screenshot correctly marked N/A (this tool has no UI panel; no agent turn runs on the no-inference runner). Evidence matches the surface this PR changes and supports the Approve. 🔍 Technical detailsStrengths
🟢 Minor — 🟢 Follow-up (out of scope, non-blocking) — other allowed commands have write side-doors. |
…mment sort has 16 short flags, not just -o. The heuristic is safe because it over-blocks (a cluster like -to, separator 'o', is rejected), not because 'o' is unique. State the real reason so the tradeoff is not misread.
|
Good catch on the On the follow-up: |
|
Verdict: Request changes (small, one-line fixes — the core hardening is solid) This PR closes the Two residual holes in the same class remain, though — both let a write slip through the very guard this PR adds:
Since the whole point of the PR is to close this bypass class, worth folding both in before merge. Suggestions below. Real-world evidenceStrong and matched to the surface. Legit read-only 🔍 Technical details🟡 🟡 This keeps every existing test green ( Strengths
|
… gaps Two more write side-doors in the same class, found in review: - find -fprint0 FILE writes null-separated results to FILE, exactly like the -fprint/-fprintf/-fls actions already blocked. - sort -ro/tmp/x (short cluster with an attached value) slipped past the isalpha() check, which failed on the '/'. Match the leading letter-run instead, so any cluster containing 'o' is caught regardless of what follows it.
|
Both gaps confirmed and fixed in d9398f5 — you were right on both, and both were genuinely reachable. Verified each is a real write primitive before fixing (not just a theoretical flag):
For the Two tests added ( |
…amd#2752) Two changes that came out of the `find -exec` shell-whitelist finding (fixed separately in amd#2740), split out so the triage/process improvements don't ride on the code fix. **1. The security audit now checks allowlist *soundness*, not just guard presence.** That finding slipped past both the sink-taint and suppression-review lenses of `claude-security-audit.yml`: each looked straight at the guarded `subprocess.run`, saw a whitelist guard existed, and passed it — neither asked whether an *allowed* command could act as a gadget for a forbidden action (`find -exec`, `sort -o`, …). The lens prompts now demand that allowlist-soundness / GTFOBins check explicitly, with the case recorded in the header. **2. New `security-assessment` skill** for PSIRT/CVSS triage. Wraps the existing, tested `util/cvss4.py` scorer into a playbook so a triage never guesses a CVSS number — it computes it from a reviewed vector. Captures the GAIA metric rubric (esp. `UI:Active` for confirmation-gated tools), the "confirmation gate drives the CVE decision" test, CWE root-cause-first, and a worked example: the AI triage claimed **6.9** for a vector that actually scores **8.4**. ## Test plan - [x] `python util/cvss4.py "CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:A/VC:L/VI:H/VA:H/SC:N/SI:N/SA:N"` → `5.3 Medium` (matches FIRST 4.0 calculator) - [x] `claude-security-audit.yml` parses as valid YAML - [x] skill frontmatter (`name`, `description`) parses --------- Co-authored-by: Ovtcharov <kovtchar@amd.com>
run_shell_commandis documented as restricted to read-only, informational commands and gated behind confirmation. Before this change, that promise was breakable: the validator only checked the first token against the whitelist, so whitelisted commands with write/exec/delete predicates ran prohibited operations that a direct call blocks. A researcher demonstratedfind … -exec touch {} +creating a file that directtouchcannot (CWE-184). Investigation found the same class of hole infind -delete,find -fprint*,sort -o(incl.-oFILE,-ro, and--outputabbreviations like--out), anduniq INPUT OUTPUT— the researcher's one-line "block find -exec" fix would have left those open. After this change, all of these are rejected while legitimate read-only forms (-print/-printf/-ls/-name, plainsort/uniq) still work.The audit-workflow hardening and the PSIRT/CVSS triage skill that came out of this finding are in a separate PR (#2752).
Test plan
PYTHONPATH=src python -m pytest tests/unit/test_shell_guardrails.py— 63 passed (adds find exec/execdir/ok/okdir/delete/fprint*, sort -o/-oFILE/-ro/--out/--output=, uniq-output guards, plus read-only forms staying allowed)black --checkclean on changed files