fix(guardrails): boundary-anchor _py_write path( indicator (#1178) - #1179
Conversation
block-hook-bypass's python-write rule used an unanchored `path[[:space:]]*\(` substring, so `path(` matched as the suffix of longer identifiers: read-only os.path.*path( helpers (normpath, abspath, realpath, relpath, commonpath) were blocked as file-write bypasses despite writing nothing. Identifier-boundary anchor the pathlib/path( indicators; real writes stay blocked via the independent `.write(` / `open(` indicators. Regression fixtures for each *path( helper (MUST-stay-quiet) plus pathlib.Path().write_text (MUST-block) added. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 16s —— View job Code Review
The fix is correct and precisely targeted. Here's a full breakdown: Regex correctnessOld pattern: New pattern: The boundary anchor
One subtlety worth noting: Test coverageThe 5 new fixtures in
Minor note — Missing fixture — Other files
VerdictThe root cause diagnosis is accurate, the fix is minimal and precise, and the true-positive (real write blocked) is confirmed by test. Approve with the one minor suggestion: add a |
|
Claude finished @kyle-sexton's task in 2m 2s —— View job Security Review
No security vulnerabilities found in this PR. The change is confined to one regex variable ( Regex anchoring is correct and introduces no bypassBefore: The boundary
Defense-in-depth means the narrowing cannot create a write bypassAll real Python file-write paths hit at least one unchanged indicator:
No Python file write can happen without either No new injection surfaces in testsThe five new test cases in Pre-existing residuals (not introduced by this PR, not a finding)The script's own header documents two known scope gaps (command substitution nesting, group-level redirects). These are unchanged and already covered by accepted-floor tests; this PR does not widen either gap. Verdict: this PR is safe to merge. The anchoring is precise, defense-in-depth is preserved, and no bypass surface is widened. |
…1178) Completes the os.path.*path( helper set flagged in review — commonpath( also ends in path( and must stay allowed under the boundary-anchored indicator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 1m 23s —— View job Code Review
This is a follow-up review of the PR after commit Status: the prior review gap is closedThe second commit responds directly to the prior review's note:
run "python3 -c os.path.commonpath (allowed)" \
"python3 -c \"import os; print(os.path.commonpath(['a/b','a/c']))\"" 0Full fixture set is correctThe
Note on Regex, version bump, and CHANGELOG unchanged since last reviewAll three were already verified as correct in the prior review pass. No new issues introduced. VerdictThe PR is complete. Every |
|
Claude finished @kyle-sexton's task in 2m 43s —— View job Security Review
No security vulnerabilities found in this PR. The change is confined to one regex variable ( Regex narrowing introduces no bypassBefore: The boundary
Defense-in-depth: narrowing cannot open a write bypassEvery standard Python file-write path hits at least one unchanged indicator:
There is no Python file-write form where:
The intersection of those constraints is empty — no real bypass surface was opened. No new injection surfaces in testsThe five new test fixtures in Pre-existing residuals (not introduced by this PR)Two scope gaps are already documented in the hook's header (command substitution inside double quotes; group-level redirects). This PR neither widens nor narrows them. Supply chain / GitHub ActionsNo Verdict: safe to merge. The anchoring is precise, defense-in-depth is preserved, and no bypass surface is widened. |
|
CI classification: all 25 checks green; claude-review verdict "Ready to merge" and claude-security-review "No security vulnerabilities found — safe to merge." Change is confined to the |
…h-substring-fp # Conflicts: # plugins/guardrails/.claude-plugin/plugin.json # plugins/guardrails/CHANGELOG.md
|
Claude finished @kyle-sexton's task in 1m 20s —— View job Code Review (post-sync with main)
This review covers the branch at its current state after merging Version bump — correctly re-anchored after merge
Regex fix — unchanged and still correct_py_write='open[[:space:]]*\(|\.write[[:space:]]*\(|(^|[^[:alnum:]_])pathlib|(^|[^[:alnum:]_])path[[:space:]]*\('Boundary anchor is correct and unchanged. Test fixtures — unchanged and complete
VerdictThe merge with main introduced only the necessary version re-base. The fix is correct, tests are complete, and the CHANGELOG accurately describes the change under the right version. Ready to merge. |
|
Claude finished @kyle-sexton's task in 1m 30s —— View job Security Review
No security vulnerabilities found. The change is confined to one variable assignment in Regex narrowing introduces no bypassBefore: The check at line 470–472 applies
The boundary anchor removes the false-positive surface (
|
Summary
block-hook-bypass'spython-writerule false-positived on read-only path arithmetic. The_py_writewrite indicator'spath[[:space:]]*\(was an unanchored substring — it matchedpath(as the suffix of a longer identifier, so
python3 -c "…os.path.normpath(os.path.join(a,b))…"(and everyother
os.path.*path(helper:abspath,realpath,relpath,commonpath) was blocked as afile-write bypass despite writing nothing.
This anchors the
pathlib/path(indicators to an identifier boundary(
(^|[^[:alnum:]_])), so they still catch the write-capablepathlib.Path(producer while clearing theread-only helpers. Real writes remain blocked via the independent
.write(/open(indicators. Patchrelease
0.13.1.New instance of the guardrails false-positive class tracked by #547; per its convention the false
positive lands as a regression fixture.
Test plan
guardrails@0.13.0, Claude Code 2.1.218):os.path.normpath/abspath/realpath/relpath/join→ exit 2 (blocked ❌); realopen('x','w').write/pathlib.Path().write_text→ exit 2 (blocked ✓).*path(helpers → exit 0 (allowed ✓); real writes → exit 2 (still blocked ✓).block-hook-bypass.test.shextended with apython-write false-positive regressionblock (fourMUST-stay-quiet
os.path.*path(cases + one MUST-blockpathlib.Path().write_text). Full suite:PASS=169 FAIL=0.
shellcheck -S errorclean onblock-hook-bypass.sh.Fresh-docs basis (CLAUDE.md mandate):
code.claude.com/docs/en/hooksand.../plugins-referencefetched this session; the fix (a hook regex + a semver/manifest bump) was additionally verified by live
reproduction through the actual installed hook, so behavior is empirically grounded, not recalled.
Related
Closes #1178.
Class umbrella: #547 (guardrails false-positive over-fire prevention).