Summary
guardrails hooks/block-hook-bypass.sh python-write rule false-positives on read-only path
arithmetic. A pure path-compute + print command is blocked as a filesystem-write bypass:
python3 -c "import os,sys; print(os.path.normpath(os.path.join(sys.argv[1],sys.argv[2])))" "$a" "$b"
→ BLOCKED: python3 -c file write bypasses Write/Edit hooks. The command writes nothing.
Root cause
The python3 -c invocation detection is correct. The defect is the _py_write write indicator
(hook line 309):
_py_write='open[[:space:]]*\(|\.write[[:space:]]*\(|pathlib|path[[:space:]]*\('
path[[:space:]]*\( is an unanchored substring meant to catch pathlib.Path(-style writes, but it
also matches path( as the suffix of a longer identifier: os.path.normpath( ends in ...path(.
The same over-match hits every read-only os.path.*path( helper — normpath(, abspath(, realpath(,
relpath(, commonpath( — none of which writes anything.
Reproduced end-to-end through the hook
| command |
before |
after |
os.path.normpath(...) |
exit 2 (blocked ❌) |
exit 0 (allowed ✓) |
os.path.abspath(...) |
exit 2 (blocked ❌) |
exit 0 (allowed ✓) |
os.path.realpath/relpath(...) |
exit 2 (blocked ❌) |
exit 0 (allowed ✓) |
os.path.join(...) producer |
exit 2 (blocked ❌) |
exit 0 (allowed ✓) |
open('x','w').write('a') |
exit 2 (blocked ✓) |
exit 2 (blocked ✓) |
pathlib.Path('x').write_text('a') |
exit 2 (blocked ✓) |
exit 2 (blocked ✓) |
Fix
Identifier-boundary-anchor the pathlib / path( indicators so they match pathlib.Path( but not a
*path( suffix:
_py_write='open[[:space:]]*\(|\.write[[:space:]]*\(|(^|[^[:alnum:]_])pathlib|(^|[^[:alnum:]_])path[[:space:]]*\('
Real writes stay blocked: .write_text(/.write_bytes( match \.write[[:space:]]*\( independently, and
pathlib with a leading boundary still fires. Regression fixtures for each os.path.*path( helper
(MUST-stay-quiet) plus a pathlib.Path().write_text (MUST-block) added to block-hook-bypass.test.sh.
Acceptance
python3 -c "…os.path.normpath(os.path.join(a,b))…" (and the other *path( helpers) is NOT blocked.
python3 -c "…pathlib.Path(x).write_text(y)…" and python3 -c "open('f','w')…" ARE still blocked.
Relation
New instance of the guardrails false-positive-over-fire class tracked by #547 (whose member list —
diff-scope, quoted-arg, boolean-intent, stdin, path-branch — does not include this _py_write
substring over-match). Per #547's convention the false positive lands as a regression fixture.
Residual (pre-existing, out of scope): the boundary pathlib token still matches a read-only
import pathlib; print(...), and open( matches read-opens — broader indicator-precision items, not
this path( substring bug.
Producer: SW2030 (Windows/PowerShell). Verified against guardrails@0.13.0 on Claude Code 2.1.218.
Summary
guardrailshooks/block-hook-bypass.shpython-writerule false-positives on read-only patharithmetic. A pure path-compute +
printcommand is blocked as a filesystem-write bypass:→
BLOCKED: python3 -c file write bypasses Write/Edit hooks. The command writes nothing.Root cause
The
python3 -cinvocation detection is correct. The defect is the_py_writewrite indicator(hook line 309):
_py_write='open[[:space:]]*\(|\.write[[:space:]]*\(|pathlib|path[[:space:]]*\('path[[:space:]]*\(is an unanchored substring meant to catchpathlib.Path(-style writes, but italso matches
path(as the suffix of a longer identifier:os.path.normpath(ends in...path(.The same over-match hits every read-only
os.path.*path(helper —normpath(,abspath(,realpath(,relpath(,commonpath(— none of which writes anything.Reproduced end-to-end through the hook
os.path.normpath(...)os.path.abspath(...)os.path.realpath/relpath(...)os.path.join(...)produceropen('x','w').write('a')pathlib.Path('x').write_text('a')Fix
Identifier-boundary-anchor the
pathlib/path(indicators so they matchpathlib.Path(but not a*path(suffix:_py_write='open[[:space:]]*\(|\.write[[:space:]]*\(|(^|[^[:alnum:]_])pathlib|(^|[^[:alnum:]_])path[[:space:]]*\('Real writes stay blocked:
.write_text(/.write_bytes(match\.write[[:space:]]*\(independently, andpathlibwith a leading boundary still fires. Regression fixtures for eachos.path.*path(helper(MUST-stay-quiet) plus a
pathlib.Path().write_text(MUST-block) added toblock-hook-bypass.test.sh.Acceptance
python3 -c "…os.path.normpath(os.path.join(a,b))…"(and the other*path(helpers) is NOT blocked.python3 -c "…pathlib.Path(x).write_text(y)…"andpython3 -c "open('f','w')…"ARE still blocked.Relation
New instance of the guardrails false-positive-over-fire class tracked by #547 (whose member list —
diff-scope, quoted-arg, boolean-intent, stdin, path-branch — does not include this
_py_writesubstring over-match). Per #547's convention the false positive lands as a regression fixture.
Residual (pre-existing, out of scope): the boundary
pathlibtoken still matches a read-onlyimport pathlib; print(...), andopen(matches read-opens — broader indicator-precision items, notthis
path(substring bug.Producer: SW2030 (Windows/PowerShell). Verified against
guardrails@0.13.0on Claude Code 2.1.218.