Preflight Checklist
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 --version confirmed)
- 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: true with allowedPaths restricted 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.json containing "deny": ["Read(/etc/**)", "Read(/home/**)"] (and a narrower allow carving back only the project directory), start a session.
- Confirm via
/permissions that the rules appear as active/duplicated in the Deny tab.
- Call the
Read tool 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 the
permissions.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 same
permissions.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.json containing "deny": ["Read(/etc/**)", "Read(/home/**)"] (and a narrower allow carving back only the project directory), start a session.
- Confirm via
/permissions that the rules appear as active/duplicated in the Deny tab.
- Call the
Read tool 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
Preflight Checklist
What's Wrong?
Title:
Readtool ignores activepermissions.denyrules (verified via/permissions), whileBashcorrectly enforces the equivalent sandbox restriction on the same pathsSummary
The
Readtool successfully reads files matched by rules that are confirmedactive and present in the
Denylist (verified via the/permissionsUI), for two independent rules, across two directories, at multiple path
depths. The
Bashtool, by contrast, correctly enforces its own filesystemsandbox restrictions on the very same paths — so this is not a general
policy failure, it's specific to how
Read(and likelyWrite/Edit)checks permissions before opening a file.
Environment
claude --versionconfirmed)7.1.5-201.fc44.x86_64(Fedora-based), x86_64.claude/settings.json, no git repoCLAUDE_CODE_ENTRYPOINT=cli)sandbox.enabled: truewithallowedPathsrestricted to the project dirRelevant 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 → searchetc:Header text: "Claude Code will always reject requests to use denied tools."
Steps to reproduce
.claude/settings.jsoncontaining"deny": ["Read(/etc/**)", "Read(/home/**)"](and a narrowerallowcarving back only the project directory), start a session./permissionsthat the rules appear as active/duplicated in the Deny tab.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
/permissionsUI's own description, Claude Code should "always rejectrequests 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 thatfailure was
EACCES— i.e. blocked by real Unix file permissions (root-onlyfile), 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 failureThe
Bashtool's own sandbox did correctly restrict the same filesystemregion:
ls -la /home/leon/.bashrcviaBashreturned "No such file ordirectory" (the mount-level sandbox hides paths outside
sandbox.allowedPathsentirely). But
Read(/home/leon/.bashrc)— same absolute path — succeededand returned the file's contents.
This indicates
Read/Write/Editare not subject to the same OS/mount-level sandbox that constrains
Bash, and their only access control is thepermissions.allow/denyrule engine — which, per the above, is not beingconsulted (or is silently no-op'ing) before the file open for these rules.
Impact
Any project relying on
permissions.denywithRead(...)glob rules tokeep Claude out of specific paths (secrets, other users' files, system
config, sibling projects, etc.) gets no actual protection from that rule for
the
Readtool — only real OS file permissions and the (separatelyconfigured, and much more limited) sandbox
allowedPathslist provide anyenforcement. This is easy to miss because the
/permissionsUI displays therule as present and active, and explicitly states denied tools are "always"
rejected.
Suggested fix direction
Have the
Read/Write/Edittool handlers run the samepermissions.denyglob match that gates the interactive approval promptbefore performing the filesystem operation, independent of whether the
call would otherwise auto-approve/auto-deny via the sandbox. Alternatively,
if
Read/Write/Editare intentionally exempted from mount-levelsandboxing for legitimate reasons, that tradeoff should at minimum be
reflected honestly in the
/permissionsUI (e.g. "Deny rules apply to Bashonly" 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
/permissionsUI's own description, Claude Code should "always rejectrequests to use denied tools," with no prompt and no read.
Error Messages/Logs
Steps to Reproduce
Steps to reproduce
.claude/settings.jsoncontaining"deny": ["Read(/etc/**)", "Read(/home/**)"](and a narrowerallowcarving back only the project directory), start a session./permissionsthat the rules appear as active/duplicated in the Deny tab.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