[BUG] Read tool ignores active permissions.deny rules (verified via /permissions), while Bash correctly enforces the equivalent sandbox restriction on the same paths
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Title: Read tool ignores active permissions.deny rules (verified via /permissions), while Bash correctly enforces the equivalent sandbox restriction on the same paths
Summary
The Read tool successfully reads files matched by rules that are confirmed
active and present in the Deny list (verified via the /permissions
UI), for two independent rules, across two directories, at multiple path
depths. The Bash tool, by contrast, correctly enforces its own filesystem
sandbox restrictions on the very same paths — so this is not a general
policy failure, it's specific to how Read (and likely Write/Edit)
checks permissions before opening a file.
Environment
- Claude Code version: 2.1.223 (
claude --versionconfirmed) - OS: Linux,
7.1.5-201.fc44.x86_64(Fedora-based), x86_64 - Project: local directory with a project-level
.claude/settings.json, no git repo - Session run as CLI (
CLAUDE_CODE_ENTRYPOINT=cli) sandbox.enabled: truewithallowedPathsrestricted to the project dir
Relevant config
<project>/.claude/settings.json:
{
"permissions": {
"deny": [
"Read(/home/**)",
"Read(/etc/**)"
],
"allow": [
"Read(~/claude/**)",
"Edit(~/claude/**)"
]
},
"sandbox": {
"enabled": true,
"allowedPaths": ["~/claude"],
"allowedDomains": ["api.github.com", "registry.npmjs.org"]
}
}
Confirmed via /permissions → Deny tab → search etc:
1. Edit(/etc/**)
2. Edit(/etc/wireguard/**)
3. Read(/etc/**)
4. Read(/etc/**) <- appears twice (two settings sources), both active
Header text: "Claude Code will always reject requests to use denied tools."
Steps to reproduce
- In a project with
.claude/settings.jsoncontaining"deny": ["Read(/etc/**)", "Read(/home/**)"](and a narrowerallowcarving back only the project directory), start a session. - Confirm via
/permissionsthat the rules appear as active/duplicated in the Deny tab. - Call the
Readtool on a path matched by the deny rule, e.g.:
Read(/etc/passwd)Read(/etc/hosts)Read(/etc/bluetooth/main.conf)(nested, 2 levels deep)Read(/etc/unbound/root.key)(nested)Read(/home/leon/.bashrc)(outside the allowed project carve-out)
Expected behavior
Every call above should be hard-blocked by the Deny rule — per the/permissions UI's own description, Claude Code should "always reject
requests to use denied tools," with no prompt and no read.
Actual behavior
All calls above succeeded and returned full file contents. No denial
message, no prompt, no error — behaviorally identical to there being no
deny rule at all.
The only case that failed was Read(/etc/ssh/sshd_config), and that
failure was EACCES — i.e. blocked by real Unix file permissions (root-only
file), not by Claude Code's permission engine. This rules out "the deny
rule works but I got a false positive from a different block" — the deny
engine visibly never engaged.
Why this looks like a Read-specific bypass, not a broader policy failure
The Bash tool's own sandbox did correctly restrict the same filesystem
region: ls -la /home/leon/.bashrc via Bash returned "No such file or
directory" (the mount-level sandbox hides paths outside sandbox.allowedPaths
entirely). But Read(/home/leon/.bashrc) — same absolute path — succeeded
and returned the file's contents.
This indicates Read/Write/Edit are not subject to the same OS/mount-
level sandbox that constrains Bash, and their only access control is thepermissions.allow/deny rule engine — which, per the above, is not being
consulted (or is silently no-op'ing) before the file open for these rules.
Impact
Any project relying on permissions.deny with Read(...) glob rules to
keep Claude out of specific paths (secrets, other users' files, system
config, sibling projects, etc.) gets no actual protection from that rule for
the Read tool — only real OS file permissions and the (separately
configured, and much more limited) sandbox allowedPaths list provide any
enforcement. This is easy to miss because the /permissions UI displays the
rule as present and active, and explicitly states denied tools are "always"
rejected.
Suggested fix direction
Have the Read/Write/Edit tool handlers run the samepermissions.deny glob match that gates the interactive approval prompt
before performing the filesystem operation, independent of whether the
call would otherwise auto-approve/auto-deny via the sandbox. Alternatively,
if Read/Write/Edit are intentionally exempted from mount-level
sandboxing for legitimate reasons, that tradeoff should at minimum be
reflected honestly in the /permissions UI (e.g. "Deny rules apply to Bash
only" or similar), rather than claiming a universal guarantee.
What Should Happen?
Expected behavior
Every call above should be hard-blocked by the Deny rule — per the/permissions UI's own description, Claude Code should "always reject
requests to use denied tools," with no prompt and no read.
Error Messages/Logs
Steps to Reproduce
Steps to reproduce
- In a project with
.claude/settings.jsoncontaining"deny": ["Read(/etc/**)", "Read(/home/**)"](and a narrowerallowcarving back only the project directory), start a session. - Confirm via
/permissionsthat the rules appear as active/duplicated in the Deny tab. - Call the
Readtool on a path matched by the deny rule, e.g.:
Read(/etc/passwd)Read(/etc/hosts)Read(/etc/bluetooth/main.conf)(nested, 2 levels deep)Read(/etc/unbound/root.key)(nested)Read(/home/leon/.bashrc)(outside the allowed project carve-out)
Claude Model
Sonnet (default)
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.223
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
Other
Additional Information
_No response_
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗