Skip to content

guardrails: block-hook-bypass _py_write open( indicator blocks read-only python3 -c reads #1601

Description

@kyle-sexton

Summary

block-hook-bypass.sh's _py_write indicator includes a bare open[[:space:]]*\(, which matches any open( — including a read. Since open() defaults to mode 'r', a python3 -c that only reads a file is blocked with a message asserting it writes.

Filing as a precision question, not a clear-cut bug: this lane documents an "ACCEPTED OVER-BLOCK" posture (the fail-closed choice approved for the PowerShell branch, block-hook-bypass.sh:468-475). But that documented acceptance covers prose/comment/quoted-string mentions of a write. A genuine read-only open() is a different category, and it is not called out as accepted anywhere I could find.

Related but distinct: #1178 (CLOSED) fixed the path( sub-indicator's unanchored match. This is the sibling open( sub-indicator, and it is a mode gap rather than an anchoring gap — so #1178's fix does not cover it and this is not a regression of it.

Reproduction (live, not constructed)

Hit incidentally while reading a JSON settings file during unrelated work:

python3 -c "
import json,os
p=os.path.expanduser('~/.claude/settings.json')
d=json.load(open(p,encoding='utf-8'))
print(json.dumps(d.get('statusLine'),indent=2))
"

Blocked with:

PreToolUse:Bash hook error: ["${CLAUDE_PLUGIN_ROOT}"/hooks/block-hook-bypass.sh]:
BLOCKED: python3 -c file write bypasses Write/Edit hooks
Use the Write or Edit tool instead of a shell file-write workaround.

There is no write in that command. The only open() is a read, and the sole side effect is print.

Mechanism

block-hook-bypass.sh:313:

_py_write='open[[:space:]]*\(|\.write[[:space:]]*\(|(^|[^[:alnum:]_])pathlib|(^|[^[:alnum:]_])path[[:space:]]*\('

The first alternative matches open( with no regard for mode. The call site at :505-508 gates only on "is this a python3 -c invocation?" plus that indicator:

if [[ "$EXEC_LC" =~ (^|[[:space:];|&()/\\]+)python3(\.exe)?[[:space:]]+-c ]] &&
  [[ "$COMMAND_LC" =~ $_py_write ]]; then
  block_bypass "python-write" "python3 -c file write bypasses Write/Edit hooks"
fi

Note the asymmetry with the PowerShell branch at :477, which requires a second confirming predicate (ps::might_write_via_python3) before blocking. The python3 -c site has no equivalent corroboration step.

Why it is worth precision work

json.load(open(p)) / open(p).read() is the most common way to read a config or JSON file in a one-liner, so this fires on routine read-only inspection rather than on anything resembling a bypass attempt. Two costs:

  1. The diagnostic is false. It states the command writes and instructs the operator to "use the Write or Edit tool instead" — advice that cannot apply to a read. An operator who trusts the message is misled about what their own command did.
  2. It trains around the guard. The natural response is to reach for another reader (jq, cat, Get-Content) until one is not blocked — which is exactly the "routine alternative after a denial" behavior pattern that makes over-blocking counterproductive.

Possible directions (not a prescription)

  • Require a write-mode signal near the open( — e.g. a second argument containing w/a/x/+, or mode=. A bare open(x) and open(x, 'r') are unambiguously reads.
  • Or mirror the PowerShell branch: keep open( as a cheap indicator but require a corroborating predicate before blocking, so the indicator alone cannot deny.
  • Or, if the over-block is genuinely intended here, say so in the block message — "read-only open() is also blocked; use Read/Grep" — so the diagnostic stops making a false factual claim.

Whichever way it goes, the message text should not assert a write that did not happen.

Environment

Windows 11 / Git Bash, CLI 2.1.220, guardrails as installed from the melodic-software marketplace at the time of the block. Reproduced once, incidentally; I have not swept for other read-only idioms that trip the same indicator (pathlib and the .write( alternative were not exercised).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.priority: mediumReal value, no hard deadline; normal backlog flow.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions