fix(guardrails): close interpreter-producer write bypass under the PowerShell tool (#1199)#1200
Conversation
…werShell tool
block-hook-bypass classified only PowerShell cmdlet/redirect write forms and then
exit 0'd before the shell-agnostic scans, so `python3 -c "open('x','w')…"` — the
identical command the Bash lane blocks — executed unguarded when issued through the
PowerShell tool. Reproduced end-to-end: same command, Bash blocked, PowerShell wrote
the file.
Run the interpreter rule on the PowerShell lane too, mirroring the Bash lane's
two-part shape: detect the INVOCATION in a quote-blanked form (ps::blank_quoted_spans,
so a quoted mention stays inert) and scan the RAW command for the write indicators,
which legitimately live inside the quoted -c payload. PowerShell cmdlet/redirect
coverage unchanged. Four regression fixtures added. Closes the in-comment
"deferred to A2b" gap. guardrails 0.14.2.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 13s —— View job Code Review
|
|
Claude finished @kyle-sexton's task in 2m 54s —— View job Security Review
No security vulnerabilities found in this PR's changes. Detail follows. Changed files reviewed
AnalysisInjection surfaces — none introduced. The new code at lines 456–460 passes Logic fidelity — correct two-part detection. The invocation check runs on False-positive protection — holds. For the quoted-mention test case No new attack surface on the The Variable scoping — no collision risk.
Documented limitations (pre-existing, not introduced here).
Test integrity. The four new fixtures correctly cover the two must-block cases ( Verdict: no security findings. The fix is narrowly scoped, correctly mirrors the existing Bash lane pattern, introduces no new injection surfaces, and the false-positive guards are sound. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 86d06cc9dd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… lane (review) Review P2: the PowerShell python-write indicator scan is raw-command-wide, so a real python3 -c invocation compounded with an unrelated later segment mentioning an indicator over-blocks. This is the identical accepted floor the Bash lane's python-write rule already carries (fail-safe over-block, structurally unusual); documented in-comment and kept byte-for-byte in step with the Bash lane rather than diverging one tool's precision from the other. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 25s —— View job Review
SummaryThis PR has two commits: the core fix (
|
|
Claude finished @kyle-sexton's task in 4m 41s —— View job Security Review
One security finding. SUGGESTION — Backtick obfuscation bypasses the new
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f5b500e51a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…e invocation scan (review) Review P2: ps::blank_quoted_spans removes ordinary quoted spans but not PowerShell here-strings (@'...'@) or # comments, so inert text inside them mentioning both python3 -c and a write indicator was read as an invocation and over-blocked (e.g. a here-string body containing `python3 -c open(`). Chain ps::blank_herestrings + blank_quoted_spans + a line-comment strip before the invocation regex — parity with the Bash lane's strip_literals (which already drops heredocs and comments). ps::blank_herestrings fail-safes to the raw command on an unbalanced here-string (over-block, never under-block). Two regression fixtures added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 34s —— View job Review
What the latest commit (
|
|
Claude finished @kyle-sexton's task in 4m 21s —— View job Security Review
Two security findings. No injection surfaces or authorization gaps introduced by this PR. IMPORTANT — Backtick obfuscation bypass remains open (unfixed from prior review)Confidence: HIGH The prior security review on Trace through Input:
Fix: one line after the lowercasing step at line 473: _ps_exec_lc="${_ps_exec_lc//\`/}" # mirrors ps::write_bypass line 371IMPORTANT — PowerShell
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0353200975
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…on python3 write evasions (review P1) Two P1 evasions of the interpreter-producer block: `& 'python3' -c "open(...)"` (the call operator runs a QUOTED command word that quote-blanking erased) and a backtick line-continuation splitting `python3` from `-c`. Both are real writes that exited 0. Mirror ps::write_bypass: delete backticks first (continuation joins under [[:space:]]+, escape-obfuscation resolves), then run two invocation scans — a call/dot-source of a quoted `python3` on the quote-INTACT text, and the bare invocation on the quote-blanked text. Inert here-string/comment/quoted-arg mentions still stay quiet; real writes (bare, unquoted-call-op, both quoted-call forms, continuation) all block. Three P1 fixtures added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 5m 17s —— View job Review
|
|
Claude finished @kyle-sexton's task in 4m 15s —— View job Security Review
Prior findings resolved by
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4cf6ea4f09
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…te-bypass lanes (review P1) A path-qualified interpreter target bypassed the python-write gate on both lanes: `/usr/bin/python3 -c "open(...)"` (Bash) and `& 'C:\Python313\python3.exe' -c` / bare `C:\Python313\python3.exe -c` (PowerShell). The command-word anchor required `python3` at a shell-separator boundary with no path prefix and no extension, so the path separator before the basename (and the `.exe` suffix) defeated the match, while `ps::write_bypass` intentionally permits arbitrary quoted programs — a real file-write bypass for a standard PowerShell call-operator form. Both lanes now admit `/` `\` path separators in the command-word boundary and an optional `.exe`; the PowerShell quoted-call scan additionally admits a path prefix inside the quotes. All anchored on the `python3` BASENAME, so `notpython3` and a non-python quoted exe (`& 'C:\...\app.exe'`) stay inert. Fixing both lanes (not PowerShell only) preserves the documented Bash/PowerShell parity — the Bash lane carried the identical hole. Six regression fixtures added (abs-path + `.exe` Bash writes, quoted-path + bare path PowerShell writes MUST block; `notpython3` and non-python quoted exe MUST stay quiet). Suite PASS=185 FAIL=0. Folded into the unreleased 0.14.2 entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 5m 29s —— View job Security Review
One security finding. Every prior review round flagged it; IMPORTANT — PowerShell
|
|
Claude finished @kyle-sexton's task in 3m 35s —— View job Review
What
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a45dc75a1d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…evasion (review P1)
A PowerShell invoked script block glues its first command onto the opening brace,
so `&{python3 -c "open('x','w').write('a')"}` (compact), `& {python3 …}` (spaced),
and `&{'python3' …}` (quoted name inside the block) executed the interpreter write
but exited 0: after quote-blanking the char before `python3` is `{`, absent from
the bare-invocation boundary class, and the quoted-call scan requires the quote to
hug the `&`.
Normalize `{`/`}` to spaces on the PowerShell lane before both derived scan forms,
so all three collapse to the already-covered `& python3` / `& 'python3'` shapes.
PowerShell-only: `&{cmd}` is a script-block invocation with no Bash `{ …; }`
analogue (that group needs surrounding spaces, already covered), so the Bash lane
needs no matching change — this is not a parity divergence.
Four regression fixtures added (compact / spaced / quoted-in-block writes MUST
block; a fully-quoted `&{python3 …}` string MUST stay quiet — brace normalization
does not defeat quote-blanking). Suite PASS=189 FAIL=0. Folded into 0.14.2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 58s —— View job Review
|
| Finding | Commit | Status |
|---|---|---|
Original bypass (python3 -c via PS tool) |
86d06cc |
✅ Closed |
Here-string body (@'\npython3 -c open\n'@) |
0353200 |
✅ Closed |
Line-comment (# python3 -c open() |
0353200 |
✅ Closed |
Backtick obfuscation (python3 `-c) |
4cf6ea4 |
✅ Closed |
Backtick line-continuation (python3 `↵-c) |
4cf6ea4 |
✅ Closed |
Call-operator with quoted 'python3' / "python3" |
4cf6ea4 |
✅ Closed |
| Path-qualified bare + quoted interpreter | a45dc75 |
✅ Closed |
Invoked script block &{python3 -c} forms |
bd4a05f |
✅ Closed |
<# ... #> inline block-comment |
— | ❌ Still open |
Optional Follow-Up (Non-Blocking)
Double-quoted mention test (Write-Output "python3 -c open() later") — mechanism is correct (brace normalization doesn't affect it; ps::blank_quoted_spans blanks the double-quoted span before scan 1), but the test matrix covers single-quote form only. Flagged since the first code review; still optional.
Verdict
bd4a05f is correct — the invoked-script-block normalization is a clean, minimal fix that mirrors ps::write_bypass's established approach, the test fixtures cover all three attack shapes plus the false-positive guard, and no new issues are introduced. The one remaining unresolved mechanical bypass on this lane is the <# ... #> inline block-comment evasion — a pre-existing gap that has survived every review round since 0353200 introduced the # anchor strip. The fix is a one-liner and should be applied before merge given this bypass is real and straightforward to close.
|
Claude finished @kyle-sexton's task in 5m 1s —— View job Security Review
What
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bd4a05fe6b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…sink (review P1)
Three review rounds each surfaced a fresh evasion of the precise PowerShell
`python3 -c` scan (path-qualified target, `&{python3}` script block, quoted-`#`
comment truncation), with `<# #>` block comments and `-ArgumentList` arg-splitting
still open. PowerShell is not faithfully bash-tokenizable, so a precise
regex/normalize stack cannot converge on this class.
Pivot the PowerShell lane to the repo's SINK DOCTRINE (mirrors
ps::classify_git_command / ps::might_invoke_git): block on the mangle-resistant
co-occurrence of (a) a raw write indicator (_py_write) and (b) a python3 token
plus a `-c` inline-code flag, both seen on the quote-intact, backtick-recovered
command (new ps::might_write_via_python3). This uniformly closes the quoted,
path-qualified, brace-glued, backtick-obfuscated, block-comment, and arg-split
forms — deleting the brace-normalize, path-qualified-quoted regex, and
comment-strip scaffolding the prior rounds added (net -16 lines in the hook).
`-c` is required and position-independent, so a legitimate script or module run
(`python3 build.py`, `python3 -m tool`) touching an `open(`-like path is NOT
blocked. This CONSCIOUSLY diverges from the Bash lane, which keeps its precise
scan (strip_literals is genuinely quote-aware; Bash has no `<# #>` or `&{}`) plus
the merged path-qualified recognition.
Accepted behavior change (the approved fail-closed choice): a command that only
MENTIONS `python3 … -c` + a write indicator in prose, a line/block comment, or a
quoted string now over-blocks (three prior allow-fixtures flipped to expect-block);
here-string mentions stay inert. Accepted residual: a stdin heredoc
(`python3 - <<PY … PY`, no `-c`) is uncovered, as today.
Fixtures cover every evasion form + the flipped mentions (MUST block) and
script/module runs, read-only os.path.normpath, non-python quoted exe, here-string
mention (MUST stay quiet). Suite PASS=193 FAIL=0. Folded into 0.14.2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 4m 34s —— View job Review
|
| Finding | Status |
|---|---|
<# ... #> inline block-comment bypass |
✅ CLOSED — the new approach never strips/parses comments; both python3 and -c tokens are still visible in the raw command |
-ArgumentList '-c',… arg-splitting |
✅ CLOSED — the -c boundary pattern `(^ |
Quoted-# comment truncation (Codex P1 from bd4a05f) |
✅ CLOSED — no comment stripping is performed; Write-Output '#'; python3 -c "open(...)" has python3 and -c both present → blocked |
| All fixes from prior rounds (backtick, call-op, brace-glued, path-qualified) | ✅ Remain closed — the token co-occurrence approach inherits all of these for free |
Logic Verification
python3 token regex — (^|[^[:alnum:]_.])python3([.]exe)?([^[:alnum:]_]|$):
notpython3 -c→tbeforepython3IS in[[:alnum:]]→ boundary fails → not matched ✓& 'c:\python313\python3.exe'→\beforepython3is NOT in[[:alnum:]_.]→ boundary matches;([.]exe)?takes.exe;'after is NOT in[[:alnum:]_]→ matched ✓&{python3 -c}→{beforepython3is NOT in[[:alnum:]_.]→ matched ✓python3 <# comment #> -c→^anchor → matched ✓
-c flag regex — (^|[[:space:]$q])-c([[:space:]$q]|$) (where $q = "'):
-Command→ after-c, the next char iso, not in[[:space:]"'], not end-of-string → not matched ✓-Confirm→ same → not matched ✓python3 -c "open..."→ space on both sides of-c→ matched ✓-ArgumentList '-c',open→'on both sides of-c→ matched ✓python3 <# comment #> -c "..."→ space before and after-c→ matched ✓
$q expansion in [[ =~ ]]: $q="\"'" expands before the bracket expression is evaluated, making [[:space:]$q] = [[:space:]"']. Standard Bash ERE behavior, consistent with how it's used throughout the file.
Here-string protection holds: ps::blank_herestrings "$COMMAND" runs before ps::might_write_via_python3 "$PS_BLANKED". Python3 mentions inside @'…'@ here-strings are blanked before the probe, identical to the git lane's pattern. Test at line 518 confirms this.
Accepted Behavior Change — Documented and Tested
Three prior "must stay quiet" fixtures now correctly expect exit 2:
| Command | Old | New |
|---|---|---|
Write-Output 'run python3 -c open() later' |
exit 0 |
exit 2 (over-block) |
Write-Output ok # python3 -c open( |
exit 0 |
exit 2 (over-block) |
Write-Output '&{python3 -c open(}' |
exit 0 |
exit 2 (over-block) |
These over-blocks are the explicit fail-closed cost of the sink approach and are unlikely in real LLM-generated tool calls. The documentation at block-hook-bypass.sh:468–473 and ps-command.sh:220–228 clearly explain the tradeoff and the approved failure direction.
Legitimate invocations stay allowed (the -c gate does the work):
python3 build.py --path "open('x','w')"→ no-cflag → allowed ✓python3 -m mytool "open('x','w')"→ no-cflag → allowed ✓& 'C:\Program Files\app.exe' -c "print open("→ nopython3token → allowed ✓
Architecture Alignment
The architectural pivot is well-reasoned and follows established repo patterns. The commit message's diagnosis — "PowerShell is not faithfully bash-tokenizable, so a precise regex/normalize stack cannot converge on this class" — is exactly what the review history demonstrates: six successive review rounds each surfaced a new evasion. The SINK DOCTRINE answer (block on mangle-resistant co-occurrence, not precise parse) is the same pattern used for ps::might_invoke_git and is the correct response to this class of problem.
The deliberate divergence from the Bash lane is correctly justified: strip_literals on the Bash lane is genuinely quote-aware, and Bash has no <# #> block comments or &{} script-block syntax to contend with.
Net code reduction of ~16 lines in the hook. The normalization scaffolding (brace normalize, backtick strip, comment strip, path-qualified-quoted regex, two-scan pattern) that accrued over the review rounds is correctly deleted.
Test Coverage — Complete
The suite covers the full attack matrix and the important carve-outs:
- ✅ Basic
open(/pathlib.write_textwrites (blocked) - ✅ Here-string mention (allowed)
- ✅ Script/module run with
open(arg but no-c(allowed) - ✅ Read-only
os.path.normpath(allowed) - ✅ Prose mention over-block (now blocked — documented)
- ✅ Line-comment over-block (now blocked — documented)
- ✅ Quoted string over-block (now blocked — documented)
- ✅ All evasion forms from prior rounds (blocked)
- ✅ Block-comment decoy + real invocation after it (blocked)
- ✅
-ArgumentList '-c',...arg-splitting (blocked) - ✅ Non-python quoted exe (allowed)
- ✅
ps::might_write_via_python3is the python3 analogue ofps::might_invoke_git— the sink pattern is correctly implemented and positioned
No New Issues Found
All injection surfaces are unchanged: $COMMAND flows through printf '%s' | sed ... in ps::blank_herestrings; user-controlled content enters only via stdin. ps::might_write_via_python3 uses only pure Bash string substitution and [[ =~ ]] with a script-literal regex. Variable scoping is clean — everything is consumed within the PowerShell branch before its exit 0.
Verdict: Approved. The sink pivot is the correct architectural response to the class of problems the review history exposed. Every previously open security finding is now closed, the over-block cases are narrow and explicitly accepted, and the implementation is clean and well-tested.
|
Claude finished @kyle-sexton's task in 3m 55s —— View job Security Review
No security vulnerabilities found in this PR's changes. Detail follows. What
|
| Bypass form | Fixed by | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
Here-string body (@'\npython3 -c open\n'@) |
0353200 |
✅ |
Line-comment (# python3 -c open() |
0353200 |
✅ |
Backtick obfuscation (python3 `-c) |
4cf6ea4 |
✅ |
| Backtick line-continuation | 4cf6ea4 |
✅ |
Call-operator + quoted interpreter (& 'python3' -c) |
4cf6ea4 |
✅ |
| Path-qualified interpreter | a45dc75 |
✅ |
Invoked script block (&{python3 -c}) |
bd4a05f |
✅ |
<# … #> inline block-comment |
053425b |
✅ |
-ArgumentList '-c',… arg-splitting |
053425b |
✅ |
Injection surfaces — none introduced
ps::might_write_via_python3: user-controlled input flows only through Bash parameter expansion (${1//\/}) and then into [[ =~ ]]regex matching — noeval, exec, sed` script composition, or subprocess with user data in the command word. No injection path.
ps::blank_herestrings (called before the sink): uses printf '%s' "$line" | sed "s/'[^']*'//g" — user content flows through printf '%s' (no format-specifier interpretation) into sed via stdin only; the sed expression is a fixed literal. No injection path.
$COMMAND_LC =~ $_py_write: _py_write is a script literal defined at line 313, not derived from user input. Unquoted ERE variable in [[ =~ ]] is intentional Bash idiom. No injection path.
Logic correctness
Here-string/raw-command consistency. COMMAND_LC (raw) is checked for _py_write while PS_BLANKED (here-string-blanked) is passed to ps::might_write_via_python3. This is the correct split: write indicators legitimately live inside the quoted -c payload (where blanking would remove them), while the python3 and -c tokens must appear structurally in the command (outside a here-string body to be meaningful). A here-string body that mentions open( fires condition 1, but the body's python3 -c is blanked in PS_BLANKED, so condition 2 stays false — the command passes. Confirmed by the PS: here-string mentions python3 -c open (allowed) fixture.
-c boundary vs longer flags. The regex (^|[[:space:]$q])-c([[:space:]$q]|$) (where $q="\"'") only matches -c followed immediately by whitespace, a quote, or end-of-string. -Confirm lowercases to -confirm; after -c comes o, not a boundary character → no match. -Command → after -c comes o → no match. ✓
python3 boundary vs notpython3. The leading boundary [^[:alnum:]_.] prevents notpython3 from matching (the t before python3 is alphanumeric). The Bash lane fixture run "notpython3 -c open (allowed)" ... 0 covers this on the Bash side; the PS lane inherits the same test premise.
-c required; script/module runs stay allowed. python3 build.py --path "open('x','w')" has no -c token → condition 2 false → allowed ✓. python3 -m mytool "open('x','w')" — -m is not -c → condition 2 false → allowed ✓. Both are new fixtures that pass.
Execution ordering. The new block is inserted BEFORE emit_tel "ok" ""; exit 0, so the original bypass (PS tool classifies cmdlet/redirect, then exit 0 before shell-agnostic scans) remains closed. ✓
Variable scoping. _ps_nobt, _ps_lcq, _ps_bare, and the other now-deleted intermediate variables are gone. PS_BLANKED is set by ps::blank_herestrings and read by ps::might_write_via_python3. The PowerShell block terminates unconditionally at exit 0 — no bleed into the Bash lane. ✓
Accepted residuals (pre-existing, not introduced by this PR)
py -3, barepython,python3.11-as-only-token — other interpreter spellings; the same residual the Bash lane carriespython3 - <<'PY' … PYstdin heredoc (no-c) — documented in the commit message and function comment- Command word from a variable (
& $interp -c "open(...)") — the shared Bash+PS variable-command-word residual
Verdict: no security findings. The pivot to the sink doctrine is the correct architectural response to the normalize-then-scan stack's convergence failure. It uniformly closes the <# … #> block-comment bypass and the -ArgumentList arg-splitting gap alongside all previously-fixed forms, introduces no new injection surfaces, and correctly maintains the fail-closed direction (over-block on mentions, never under-block on real writes).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 053425b371
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
PowerShell evaluates expression-valued native-command arguments before launching,
so `python3 ('-'+'c') "open(...)"` and `Start-Process python3 -ArgumentList
('-'+'c'),...` construct the `-c` flag with no literal token — the fail-closed
sink's literal `-c` check returned false and the write executed.
When no literal `-c` is present, ps::might_write_via_python3 now fails closed if
the args carry a non-tokenizable subexpression construct, reusing the git lane's
own ps::has_special_constructs test on the quote-blanked command. This is the same
construct class the git lane already refuses to parse, so the boundary is
consistent: a variable ARG (`python3 $c`) stays tokenizable/allowed exactly as
`git $x` does, while a `(...)` subexpression that could compute `-c` blocks.
Module and script runs stay allowed (`python3 -m tool "open('x')"`,
`python3 build.py --path "open('x')"` — no unquoted construct). Two computed-flag
fixtures added (subexpression + -ArgumentList). Suite PASS=195 FAIL=0. Folded into 0.14.2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 4m 24s —— View job Review
What
|
| Finding | Commit | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
Here-string body (@'\npython3 -c open\n'@) |
0353200 |
✅ |
Line-comment (# python3 -c open() |
0353200 |
✅ |
Backtick obfuscation (python3 `-c) |
4cf6ea4 |
✅ |
Call-operator + quoted 'python3' / "python3" |
4cf6ea4 |
✅ |
Path-qualified interpreter (/usr/bin/python3) |
a45dc75 |
✅ |
Invoked script block (&{python3 -c}) |
bd4a05f |
✅ |
<# … #> block-comment bypass |
053425b (sink pivot) |
✅ |
-ArgumentList '-c',… arg-splitting |
053425b |
✅ |
Computed -c (('-'+'c') subexpression) |
3eeb06f |
✅ |
Accepted Residuals (Documented, Pre-Existing)
py -3, barepython,python3.11— alternate interpreter spellings not in scopepython3 - <<PY … PY— stdin heredoc (no-cflag)& $interp -c "open(...)"— variable command word (shared Bash+PS residual, same as git lane's& $x)
Test Coverage — Complete
Two new fixtures added for the computed-flag case (both MUST-block); the documented over-block cases (three prior allow-fixtures flipped to expect-block) are already covered from the 053425b round. Suite PASS=195 FAIL=0. CHANGELOG correctly updated.
Verdict: Approved. The computed -c fix is structurally consistent with the git lane's established pattern, correctly identified and closed the last mechanical bypass on the PS python3 lane, and introduces no new issues.
|
Claude finished @kyle-sexton's task in 5m 6s —— View job Security Review
No security vulnerabilities found in this PR's changes at HEAD ( What
|
| Finding | Commit | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
Here-string body (@'\npython3 -c open\n'@) |
0353200 |
✅ |
Line-comment (# python3 -c open() |
0353200 |
✅ |
Backtick obfuscation (python3 `-c) |
4cf6ea4 |
✅ |
| Backtick line-continuation | 4cf6ea4 |
✅ |
Call-operator + quoted interpreter (& 'python3' -c) |
4cf6ea4 |
✅ |
| Path-qualified interpreter | a45dc75 |
✅ |
Invoked script block (&{python3 -c}) |
bd4a05f |
✅ |
<# … #> inline block-comment |
053425b |
✅ (sink doesn't parse comments) |
-ArgumentList '-c',… arg-splitting |
053425b |
✅ |
Computed -c via ('-'+'c') |
3eeb06f |
✅ |
Verdict: no security findings. The computed--c fix is correct and consistent with the git lane's established ps::has_special_constructs pattern. No new injection surfaces or bypass vectors are introduced.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3eeb06f2c8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…(review P1)
A launcher whose PROGRAM name is a computed expression still bypassed the
python-write sink: `Start-Process -FilePath ('py'+'thon3') -ArgumentList
'-c','open(...)'` evaluates to the same inline write, but the literal
python3-token test in ps::might_write_via_python3 cannot see a computed name, so
it returned false before the computed-`-c` fallback could run.
ps::might_write_via_python3 now checks a computed-launcher-TARGET clause FIRST
(before the literal-token test), mirroring ps::might_invoke_git: a launcher
(Start-Process/saps/start/pwsh/powershell/cmd) followed by a computed program
argument (`(…)`/`$…`, optionally behind one named parameter) fails closed, since
the program could evaluate to python3. A LITERAL non-python launcher target
(`Start-Process notepad …`) is not matched and falls through to the token test, so
a non-python launch stays allowed. The caller still gates on a raw write indicator.
Two fixtures added (computed `-FilePath` target MUST block; literal notepad
launcher MUST stay quiet). Suite PASS=197 FAIL=0. Folded into 0.14.2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 34s —— View job Review
What
|
| Finding | Commit | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
Here-string body (@'\npython3 -c open\n'@) |
0353200 |
✅ |
Line-comment (# python3 -c open() |
0353200 |
✅ |
Backtick obfuscation (python3 `-c) |
4cf6ea4 |
✅ |
| Backtick line-continuation | 4cf6ea4 |
✅ |
Call-operator + quoted interpreter (& 'python3' -c) |
4cf6ea4 |
✅ |
| Path-qualified interpreter | a45dc75 |
✅ |
Invoked script block (&{python3 -c}) |
bd4a05f |
✅ |
<# … #> inline block-comment bypass |
053425b (sink pivot) |
✅ |
-ArgumentList '-c',… arg-splitting |
053425b |
✅ |
Computed -c flag (('-'+'c')) |
3eeb06f |
✅ |
Computed launcher target (-FilePath ('py'+'thon3')) |
6ffd3d3 |
✅ |
No New Issues Introduced
Injection surfaces — clean. The new clause is a pure Bash ERE test on $lc, which is the backtick-stripped, lowercased copy of PS_BLANKED. No subprocess, no eval, no sed with user data in the expression.
Launcher-name set (start-process|saps|start|pwsh|powershell|cmd) — identical to ps::might_invoke_git. start has potential overlap with other uses, but it requires [[:space:]]+ after and [\(\$] within (-[a-z]+[[:space:]]+)?, so start-service and bare mentions don't match. This is the same set and boundary used in the git lane — consistency is correct.
saps $exe -ArgumentList '-c','open(...)' correctly blocked. A variable-program launcher ($) matches [\(\$] and can't be proven non-python3; blocking is the correct fail-closed outcome and mirrors ps::might_invoke_git's treatment of & $tool ….
Test suite. Two fixtures added: computed -FilePath target (MUST block) and literal notepad launcher (MUST stay quiet). The CHANGELOG 0.14.2 entry is updated to include computed-launcher-target coverage. PASS=197 FAIL=0 per commit message.
Accepted Residuals (Pre-Existing, Documented)
python3 $flag …where$flag = '-c'at runtime — variable command word, same residual as the Bash lane and the git lanepy -3, barepython— alternate interpreter spellings not in scopepython3 - <<PY … PY— stdin heredoc (no-c)- Multi-line
<# … #>block comments (opener and closer on separate lines) — harder than single-line; the single-line form is covered by the sink approach, and multi-line is rare in LLM-generated tool calls
Verdict: Approved. The computed-launcher-target fix is structurally identical to ps::might_invoke_git's established clause, correctly positioned as the first check before the early-return literal token test, and well-tested. Every P1 security finding raised across the full review history is now closed. The implementation is clean and the accepted residuals are documented.
|
Claude finished @kyle-sexton's task in 4m 4s —— View job Security Review
No security vulnerabilities found in this PR's changes. Detail follows. What
|
| Finding | Commit | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
Here-string body (@'\npython3 -c open\n'@) |
053425b (sink pivot) |
✅ |
Line-comment / <# #> block-comment bypass |
053425b (sink pivot) |
✅ |
Backtick obfuscation (python3 `-c) |
053425b (sink pivot) |
✅ |
Call-operator + quoted interpreter (& 'python3' -c) |
053425b (sink pivot) |
✅ |
| Path-qualified interpreter | 053425b (sink pivot) |
✅ |
Invoked script block (&{python3 -c}) |
053425b (sink pivot) |
✅ |
-ArgumentList '-c',… arg-splitting |
053425b (sink pivot) |
✅ |
Computed -c via ('-'+'c') |
3eeb06f |
✅ |
Computed Start-Process target ('py'+'thon3') |
6ffd3d3 |
✅ |
Verdict: no security findings. The computed-launcher fix mirrors ps::might_invoke_git's established pattern exactly, correctly closes the last Codex P1 on this PR, and introduces no new injection surfaces or bypass vectors.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6ffd3d3bf7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… (review P1) PowerShell joins an adjacent variable/subexpression into the same native-command argument, so `python3 -c$code` passes a single `-c<source>` arg. The literal-`-c` check required a whitespace/quote/end boundary after `-c`, so the concatenated form slipped it, and a bare `$var` is not a special construct for the fallback. ps::might_write_via_python3 now also blocks when `-c` is immediately followed by a variable (`$`) or subexpression (`(`) — a computed inline-code flag. A longer literal flag (`-config`, `-Command`, `-Confirm`) is unaffected (the char after `-c` is a letter, not `$`/`(`). Two fixtures added (Codex's exact `-c$code` MUST block; `-config` longer flag MUST stay quiet). Suite PASS=199 FAIL=0. Folded into 0.14.2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 4s —— View job Security Review
No security vulnerabilities found in this PR's changes. Detail follows. Changed files reviewed
AnalysisInjection surfaces — none introduced. User-controlled data (
Logic correctness — four-path detection is sound.
In POSIX ERE inside bash Execution ordering — correct.
Variable scoping — clean.
Here-string / The PowerShell lane calls
The write-indicator scan still runs on the raw command (not the here-string-blanked form) because the write indicators legitimately live inside the All prior P1 bypass findings — confirmed closed at HEAD.
No workflow files touched. No credential or secret handling introduced. No dependency or supply-chain changes. Verdict: no security findings. The sink-doctrine implementation is correct and consistent with the established |
|
Claude finished @kyle-sexton's task in 3m 23s —— View job Review
What
|
| Finding | Commit | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
Here-string body (@'\npython3 -c open\n'@) |
053425b (sink pivot) |
✅ |
Line-comment / <# #> block-comment bypass |
053425b (sink doesn't parse comments) |
✅ |
Backtick obfuscation (python3 `-c) |
053425b (sink pivot) |
✅ |
Call-operator + quoted interpreter (& 'python3' -c) |
053425b (sink pivot) |
✅ |
Path-qualified interpreter (/usr/bin/python3, python3.exe) |
053425b (sink pivot) |
✅ |
Invoked script block (&{python3 -c}, & {python3 -c}) |
053425b (sink pivot) |
✅ |
-ArgumentList '-c',… arg-splitting |
053425b (sink pivot) |
✅ |
Computed -c via ('-'+'c') |
3eeb06f |
✅ |
Computed launcher target (-FilePath ('py'+'thon3')) |
6ffd3d3 |
✅ |
-c concatenated with $var / (expr) |
e22217a |
✅ |
No New Injection Surfaces
The new check is a pure [[ =~ ]] ERE test on $lc (a backtick-stripped, lowercased expansion of $PS_BLANKED). No subprocess, no eval, no sed with user content in the script. Consistent with every other test in the function.
Accepted Residuals (Documented, Pre-Existing)
python3 $flag …where$flag = '-c'at runtime — variable command word;$is not inps::has_special_constructsby design (shared Bash+PS residual, same as the git lane)py -3, barepython,python3.11— alternate interpreter spellingspython3 - <<PY … PY— stdin heredoc (no-cflag)
Optional Follow-Up (Non-Blocking, Carry-Over)
Double-quoted mention test — Write-Output "python3 -c open() later" is not covered as a test fixture. The mechanism handles it consistently (the sink's python3 token + -c check over-blocks it the same way as the single-quoted mention at line 523), but the test matrix has the single-quote form only. Flagged as optional since the first code review round; the behavior is correct even without it.
Verdict: Approved. The -c$code fix is a minimal, precisely-targeted addition to ps::might_write_via_python3 — one [[ =~ ]] line that closes the last mechanical bypass form without affecting legitimate longer flags. Every P1 security finding from across the ten-commit review history is now closed. The implementation is clean, well-commented, correctly tested, and the SINK DOCTRINE architecture has proven its value by absorbing the full class of PowerShell tokenization evasions.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e22217adb9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…(review P1)
PowerShell binds a cmdlet parameter with a colon as well as whitespace, so
`Start-Process -FilePath:$p -ArgumentList '-c',...` names a computed program the
same as `-FilePath ('py'+'thon3')`. The computed-launcher clause in
ps::might_write_via_python3 required whitespace after the optional parameter name,
so the colon-bound form slipped it and the launch executed unguarded.
The optional parameter-name group now accepts a colon OR whitespace value
separator (`-[a-z]+([[:space:]]+|:)`), so `-FilePath:$p` / `-FilePath ('py'…)` are
both caught; a literal non-python launcher (`Start-Process notepad …`,
`Start-Process foo:bar baz`) still is not. ps::might_invoke_git carries the
identical clause with the same whitespace-only gap — a matching fix on the git lane
is tracked separately (out of scope for this #1199 python-write PR).
One fixture added (colon-bound computed target MUST block). Suite PASS=200 FAIL=0.
Folded into 0.14.2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 19s —— View job Review
What
|
| Finding | Commit | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
Here-string body (@'\npython3 -c open\n'@) |
053425b (sink pivot) |
✅ |
<# … #> block-comment bypass |
053425b (sink pivot) |
✅ |
Backtick obfuscation (python3 `-c) |
053425b (sink pivot) |
✅ |
Call-operator + quoted interpreter (& 'python3' -c) |
053425b (sink pivot) |
✅ |
Path-qualified interpreter (/usr/bin/python3, python3.exe) |
053425b (sink pivot) |
✅ |
Invoked script block (&{python3 -c}) |
053425b (sink pivot) |
✅ |
-ArgumentList '-c',… arg-splitting |
053425b (sink pivot) |
✅ |
Computed -c via ('-'+'c') |
3eeb06f |
✅ |
Computed launcher target (-FilePath ('py'+'thon3')) |
6ffd3d3 |
✅ |
-c concatenated with $var / (expr) |
e22217a |
✅ |
Colon-bound computed launcher target (-FilePath:$p) |
352dd78 |
✅ |
No New Issues Introduced
Injection surfaces — clean. The change is entirely within a [[ "$lc" =~ … ]] ERE test where $lc is the left-hand side (quoted). No subprocess, no eval, no sed with user data in the script.
start name overlap. The start launcher name in (start-process|saps|start|pwsh|powershell|cmd) is identical to the prior commit — the new colon form doesn't change its scope. start notepad:task baz would not match because notepad:task would need t to match [\(\$].
ps::might_invoke_git parallel gap. Correctly noted in the commit message as out-of-scope for this PR and tracked separately. The python3 lane is the only lane this PR covers.
Accepted Residuals (Documented, Pre-Existing)
python3 $flag …where$flag = '-c'at runtime — variable command word; same residual as the Bash lane and git lanepy -3, barepython,python3.11— alternate interpreter spellingspython3 - <<PY … PY— stdin heredoc (no-c)- Multi-param colon-bound launchers (e.g.,
Start-Process -Wait:$true -FilePath:$p) — the first-Wait:would match the optional group, then$from$truetriggers a fail-close. This is the correct fail-safe behavior: any$-valued parameter in the first slot blocks rather than passes.
Optional Follow-Up (Non-Blocking)
Double-quoted mention test (Write-Output "python3 -c open() later") — still not covered as a fixture. The mechanism handles it correctly (the token co-occurrence probe blocks it the same way as the single-quoted form at line 523), but the test matrix has the single-quote form only. This has been flagged since the first code review round and remains optional.
Verdict: Approved. The colon-bound launcher fix is a one-group regex change, exactly targeted, with no new injection surfaces, and correctly closes the last Codex P1 on this PR. The full 11-commit review history has converged on a clean SINK DOCTRINE implementation — ps::might_write_via_python3 now covers the complete class of PowerShell token obfuscation forms. Ready to merge.
|
Claude finished @kyle-sexton's task in 3m 50s —— View job Security Review
|
| Finding | Commit | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
Here-string body (@'\npython3 -c open\n'@) |
053425b (sink pivot) |
✅ |
<# … #> inline block-comment bypass |
053425b (sink never strips comments) |
✅ |
| Backtick obfuscation / line-continuation | 053425b (${1//\/}` strip) |
✅ |
Call-operator + quoted interpreter (& 'python3') |
053425b (quote-intact probe) |
✅ |
Path-qualified interpreter (\python3.exe) |
053425b (token boundary admits .) |
✅ |
Invoked script block (&{python3 -c}) |
053425b (quote-intact probe) |
✅ |
-ArgumentList '-c',… arg-splitting |
053425b (literal -c quote-bounded) |
✅ |
Computed -c via ('-'+'c') |
3eeb06f (ps::has_special_constructs fallback) |
✅ |
Computed launcher target ('py'+'thon3') |
6ffd3d3 (launcher clause) |
✅ |
Concatenated -c$code |
e22217a (-c[\$\(] pattern) |
✅ |
Colon-bound -FilePath:$p |
352dd78 (([[:space:]]+|:) alt) |
✅ |
Verdict: no security findings. The colon-bound parameter fix is a minimal, correctly-targeted change that mirrors the whitespace-separated form already in ps::might_invoke_git. Every P1 bypass surfaced across the full eleven-commit review history is now closed. The fail-closed direction is maintained throughout, and no new injection surfaces or logic gaps are introduced.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 352dd784a6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…target (review P1) Modeling Start-Process parameter ordering with a regex kept losing to fresh variants — one preceding option, then colon binding, now multiple options before `-FilePath` (`Start-Process -NoNewWindow -FilePath $exe -ArgumentList '-c',...`). Each slipped the per-parameter regex and launched an inline Python write unguarded. Replace the per-parameter regex in ps::might_write_via_python3 with a conservative rule that closes the whole ordering/binding sub-class: a launcher (Start-Process/saps/start/pwsh/powershell/cmd) present TOGETHER with an unquoted computed construct (`$` or `(`, on the quote-blanked command) fails closed — the launched program cannot be proven non-python regardless of how it is bound or how many options precede it. A launcher with only literal args (`Start-Process notepad -ArgumentList '-c','open(...)'`, `Start-Process notepad -ArgumentList "$file"`) carries no unquoted construct and stays allowed via the literal-token path. ps::might_invoke_git still models this with the narrower per-parameter regex; a matching hardening on the git lane is tracked separately (out of this #1199 scope). One fixture added (multi-option computed target MUST block). Suite PASS=201 FAIL=0. Folded into 0.14.2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 5m 3s —— View job Review
|
| Finding | Commit | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
| Here-string body / block-comment bypass | 053425b (sink pivot) |
✅ |
| Backtick obfuscation / line-continuation | 053425b |
✅ |
| Call-operator + quoted interpreter | 053425b |
✅ |
| Path-qualified interpreter | 053425b |
✅ |
Invoked script block (&{python3 -c}) |
053425b |
✅ |
-ArgumentList '-c',… arg-splitting |
053425b |
✅ |
Computed -c via ('-'+'c') |
3eeb06f |
✅ |
Computed launcher target (-FilePath ('py'+'thon3')) |
6ffd3d3 |
✅ |
Concatenated -c$code |
e22217a |
✅ |
Colon-bound launcher target (-FilePath:$p) |
352dd78 |
✅ |
Multi-option computed launcher (-NoNewWindow -FilePath $exe) |
65e4c33 |
✅ |
One Minor Observation (Non-blocking)
if ps::has_launcher "$recovered" && [[ "$blanked" == *'$'* || "$blanked" == *'('* ]]; thenps::has_launcher receives $recovered (already backtick-stripped) but internally strips backticks again (local lc="${1//\/}"`). The double-strip is harmless — just redundant. Not worth changing unless you're doing a separate cleanup pass.
Test Coverage — Complete
201 fixtures per commit message (PASS=201 FAIL=0). The new fixture at line 574 covers the exact evasion form that drove the change (-NoNewWindow -FilePath $exe with two named params before the computed target). The existing Start-Process notepad fixture at line 576 confirms the false-positive guard. All prior attack forms from the review history remain covered.
CHANGELOG and Version Bump
plugin.json correctly bumped to 0.14.2. The CHANGELOG entry is comprehensive — it names every evasion form covered, the deliberate divergence from the Bash lane, the accepted over-block behavior, and the acknowledged residuals (python3 $flag, py -3, stdin heredoc).
Verdict
Approved. The conservative any-construct check correctly closes the whole parameter-ordering/binding sub-class with a single structural rule instead of a regex that kept being outpaced. The false-positive guard (write-indicator tokens in quoted strings don't survive quote-blanking) is sound, and the implementation mirrors the established sink doctrine. All P1 security findings from the full 12-commit review history are closed at HEAD.
|
Claude finished @kyle-sexton's task in 6m 7s —— View job Security Review
|
| Finding | Commit | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
Here-string body (@'\npython3 -c open\n'@) |
053425b (sink pivot) |
✅ |
<# … #> inline block-comment |
053425b (sink doesn't parse comments; tokens still present) |
✅ |
Backtick obfuscation (python3 `-c) |
053425b |
✅ |
Call-operator + quoted interpreter (& 'python3' -c) |
053425b |
✅ |
Path-qualified interpreter (\python3.exe) |
053425b |
✅ |
Invoked script block (&{python3 -c}) |
053425b |
✅ |
-ArgumentList '-c',… arg-splitting |
053425b |
✅ |
Computed -c via ('-'+'c') |
3eeb06f |
✅ |
Computed launcher target ('py'+'thon3') |
6ffd3d3 |
✅ |
Concatenated -c$code |
e22217a |
✅ |
Colon-bound -FilePath:$p |
352dd78 |
✅ |
Multi-option computed target (-NoNewWindow -FilePath $exe) |
65e4c33 |
✅ |
No security vulnerabilities found. The conservative launcher check in 65e4c33 correctly closes the multi-option computed target class without introducing injection surfaces, logic gaps, or regressions in prior fixes. The fail-closed direction (over-block, never under-block) is maintained throughout.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 65e4c33909
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…get (review P1)
A call `&` / dot-source `.` of a DOUBLE-quoted interpolated target still bypassed
the sink: `& "$env:PYTHON_BIN" -c "open('x','w')..."` resolves to python3, but
blank_quoted_spans erases the quoted target, it is not a launcher, and it carries
no literal python3 token — so ps::might_write_via_python3 returned false.
ps::write_bypass misses it too (its computed-target check wants an UNQUOTED
`& $`/`& (`).
ps::might_write_via_python3 now fails closed on a call/dot-source of a
double-quoted target that interpolates a variable or subexpression (matched on the
quote-intact text before the token test). A SINGLE-quoted target does not
interpolate in PowerShell (`& '$x'` is the literal name `$x`), so it is not matched
and stays allowed via write_bypass's arbitrary-quoted-program residual.
Two fixtures added (interpolated `& "$env:PYTHON_BIN"` MUST block; single-quoted
`& '$x'` MUST stay quiet). Suite PASS=203 FAIL=0. Folded into 0.14.2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 4m 42s —— View job Review
What
|
| Finding | Commit | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
Here-string body (@'\npython3 -c open\n'@) |
053425b (sink pivot) |
✅ |
<# … #> inline block-comment |
053425b (sink; tokens still visible) |
✅ |
| Backtick obfuscation / line-continuation | 053425b |
✅ |
Call-operator + quoted interpreter (& 'python3') |
053425b |
✅ |
Path-qualified interpreter (\python3.exe) |
053425b |
✅ |
Invoked script block (&{python3 -c}) |
053425b |
✅ |
-ArgumentList '-c',… arg-splitting |
053425b |
✅ |
Computed -c via ('-'+'c') |
3eeb06f |
✅ |
Computed launcher target (-FilePath ('py'+'thon3')) |
6ffd3d3 |
✅ |
Concatenated -c$code |
e22217a |
✅ |
Colon-bound -FilePath:$p |
352dd78 |
✅ |
Multi-option computed target (-NoNewWindow -FilePath $exe) |
65e4c33 |
✅ |
Interpolated double-quoted call-op (& "$env:PYTHON_BIN") |
9d6c979 |
✅ |
Accepted Residuals (Documented, Pre-Existing)
python3 $flag …where$flag = '-c'at runtime — variable command word; shared Bash+PS residual, same as the git lanepy -3, barepython,python3.11— alternate interpreter spellingspython3 - <<PY … PY— stdin heredoc (no-c)- Multi-line
<# … #>block comments (opener/closer on separate lines) — the single-line form is covered by the sink approach; multi-line is substantially rarer in LLM-generated tool calls
Optional Follow-Up (Non-Blocking, Carry-Over)
Double-quoted mention test — Write-Output "python3 -c open() later" as a must-stay-quiet fixture. The mechanism handles it correctly (the token+-c co-occurrence over-blocks it the same way as the single-quoted mention and line-comment forms, all of which are already documented at line 523). The test matrix has the single-quote form only. Flagged as optional since round 1; the behavior is documented and intentional.
Verdict
Approved. The interpolated call-operator fix is correctly implemented, positioned at the right point in the check sequence, tested with two well-chosen fixtures (MUST block and MUST stay quiet), and introduces no new injection surfaces. The 13-commit review series has converged: ps::might_write_via_python3 now covers the complete class of PowerShell token obfuscation and computed-invocation forms, and every P1 security finding surfaced across all review rounds is closed at HEAD.
|
Claude finished @kyle-sexton's task in 9m 33s —— View job Security Review
No security vulnerabilities found in this PR's changes. Detail follows. What
|
| Order | Check | Catches |
|---|---|---|
| 1 | Launcher + computed construct (line 247) | Start-Process … $exe … |
| 2 | [NEW] Interpolated call-op target (line 258) | & "$var" -c … |
| 3 | Literal python3 token (line 260) |
& "python3" …, /path/python3 |
| 4 | Literal -c flag (line 263) |
python3 -c "open(…)" |
| 5 | Concatenated -c$code (line 269) |
python3 -c$code |
| 6 | Special-construct fallback (line 275) | python3 ('-'+'c') … |
The new check MUST run before line 260, because ps::blank_quoted_spans (called at line 235 to compute blanked) would have erased "$env:PYTHON_BIN" — making the token test at line 260 unable to see a python3 token. Inserting it at line 258 is correct.
Variable scoping — clean
recovered, lc, q, blanked are all local to ps::might_write_via_python3. PS_BLANKED is set by ps::blank_herestrings "$COMMAND" before the call and consumed before the PowerShell block's exit 0. No bleed into the Bash lane below. ✓
All prior P1 findings — confirmed closed at HEAD (9d6c979)
| Finding | Commit | Status |
|---|---|---|
Original PS lane gap (python3 -c via PS tool) |
86d06cc |
✅ |
Here-string body (@'\npython3 -c open\n'@) |
053425b (sink pivot) |
✅ |
<# … #> block-comment bypass |
053425b (sink never parses comments) |
✅ |
| Backtick obfuscation / line-continuation | 053425b (${1//\/}` strip) |
✅ |
Call-operator + quoted interpreter (& 'python3' -c) |
053425b (quote-intact probe) |
✅ |
Path-qualified interpreter (\python3.exe) |
053425b (token boundary admits .) |
✅ |
Invoked script block (&{python3 -c}) |
053425b (quote-intact probe) |
✅ |
-ArgumentList '-c',… arg-splitting |
053425b (literal -c quote-bounded) |
✅ |
Computed -c via ('-'+'c') |
3eeb06f (special-construct fallback) |
✅ |
Computed launcher target (-FilePath ('py'+'thon3')) |
6ffd3d3 (launcher clause) |
✅ |
Concatenated -c$code |
e22217a (-c[\$\(] pattern) |
✅ |
Colon-bound computed launcher (-FilePath:$p) |
352dd78 (([[:space:]]+|:) alt) |
✅ |
Multi-option computed launcher (-NoNewWindow -FilePath $exe) |
65e4c33 (conservative blanked-scan) |
✅ |
Interpolated call-op target (& "$env:PYTHON_BIN" -c) |
9d6c979 |
✅ |
Verdict: no security findings. The interpolated call-operator fix is minimal, correctly targeted, and introduces no new injection surfaces or logic gaps. The full 13-commit review history has converged on a complete, fail-closed sink implementation.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d6c9796cf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
CI classification (pre-merge)All required checks passed on head
Local guard suite: PASS=203 FAIL=0 (shellcheck clean). Review-thread disposition9 Codex P1 rounds on the PowerShell python-write sink. Rounds 1–8 were genuine gaps and are fixed (fold into 0.14.2): the fail-closed pivot to the sink doctrine, plus computed- Merging per the standing directive (drive to MERGED with a CI-classification comment; claude-review / claude-security-review are fail-open on infra-fail — not exercised here, all green). |

Summary
Closes a live write-gate bypass.
block-hook-bypass.shclassified only PowerShell cmdlet/redirectwrite forms (
ps::write_bypass) and thenexit 0ed before the shell-agnostic scans — so aninterpreter-producer write,
python3 -c "open('x','w').write('a')", executed unguarded through thePowerShell tool while the identical command through Bash was blocked. Interpreter-producer writes are
shell-agnostic: whichever tool launches
python3 -c, the write bypasses Write/Edit identically.The interpreter rule now also runs on the PowerShell lane, mirroring the Bash lane's two-part shape:
the invocation is detected in a quote-blanked form (
ps::blank_quoted_spans, so a quoted mentionstays inert) while the write indicators are scanned on the raw command, where they legitimately live
inside the quoted
-cpayload. PowerShell cmdlet/redirect coverage is unchanged. guardrails 0.14.2.This was the gap the code already acknowledged in-comment as "deferred to A2b".
Note on provenance: this is one of the two real causes behind the reported "PowerShell guard bypass".
The other (disk-hygiene's engine gate being dropped when
disk_hygiene_enabledis unconfigured) ishandled separately. An earlier hypothesis — that PreToolUse hooks don't fire for the PowerShell tool —
was falsified: they fire correctly, payload
tool_nameis literallyPowerShell.Test plan
Verified end-to-end through the actual hook (CC 2.1.218):
python3 -c "open('x','w').write('a')"python3 -c "import pathlib; pathlib.Path('x').write_text('a')"python3 -c "import os; print(os.path.normpath('a/b'))"Write-Output 'run python3 -c open() later'Set-Content -Path f.txt -Value 'x'Four regression fixtures added to the PowerShell section of
block-hook-bypass.test.sh(two MUST-block,two MUST-stay-quiet).
shellcheck -S errorclean.Fresh-docs basis (CLAUDE.md mandate):
tools-reference+hooksfetched this session; the PowerShelltool's hook firing and payload
tool_namewere verified by live payload dump, not recalled.Related
Closes #1199.
Sibling cause of the same report: #1195 (disk-hygiene engine gate dropped when the option is unconfigured).