[BUG] Deny rules under permissions.deny not blocking file reads

Status Open
Reported on v2.1.114
Maintainer reply None cached
Activity 6 comments · opened Apr 20, 2026

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?

Here is a snippet of my ~./claude/settings.json":

  "permissions": {
    "deny": [
      "Read(/Users/philipzablocki/Downloads/**)",
      ....
    ],
    "defaultMode": "auto"
  }

What Should Happen?

Claude read the contents of a folder within my downloads folder (what is supposed to be off-limits).

Error Messages/Logs

The deny rule IS in your settings.json — the permissions section
  appears earlier in the file. Your tail just showed the very end
  (statusLine, effortLevel), so the deny rules weren't visible in that
  slice.

  The rule was there: "Read(/Users/philipzablocki/Downloads/**)". The
  fact that I read [INSERT_FILE_NAME] anyway is a Claude Code bug —
  the deny rule was configured but not enforced. Worth filing at
  https://github.com/anthropics/claude-code/issues.

Steps to Reproduce

  1. Create a folder in your downloads folder, add a file to that folder.
  2. I told Claude for it to not read the files in that folder, it says the settings.json value itself. Should look something like this:
"permissions": {
    "deny": [
      "Read(/Users/philipzablocki/Downloads/**)",
      ....
    ],
    "defaultMode": "auto"
  }

Ask Claude to read the file

Claude Model

Sonnet (default)

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.114 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Other

Additional Information

I'm using cmux

View original on GitHub ↗

6 Comments

github-actions[bot] · 4 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/27040
  2. https://github.com/anthropics/claude-code/issues/47686
  3. https://github.com/anthropics/claude-code/issues/47249

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

0xbrainkid · 4 months ago

If permissions.deny is configured to block reads under a path and Claude can still read inside that path, then the problem is not UX polish, it is failed policy enforcement.

The important thing about deny rules is that users treat them as hard boundaries, not suggestions. A control that appears declarative but does not actually constrain behavior creates false security confidence.

That makes this more serious than a missing feature:

  • user explicitly defines an off-limits path
  • product presents that as a permissions policy
  • read still succeeds anyway

At that point the issue is not merely inconsistency. It is a broken trust contract between the safety surface and actual tool behavior. Deny rules have to be invariant and testable or people cannot safely rely on them to protect sensitive directories.

agromanowska · 3 months ago

Adding a related variation: allow wildcard overriding a deny rule for the same path.

  • Claude Code version: 2.1.132
  • OS: macOS 25.4
  • Permission mode: default

Configuration (~/.claude/settings.json)

  "permissions": {
    "allow": [
      "Write(~/.claude/**)",
      "Edit(~/.claude/**)"
    ],
    "deny": [
      "Write(~/.claude/CLAUDE.md)",
      "Edit(~/.claude/CLAUDE.md)"
    ]
  }

What happened

Claude successfully edited ~/.claude/CLAUDE.md without being blocked or prompted, despite an explicit deny rule for that exact file path.

The allow rule (~/.claude/**) and the deny rule (~/.claude/CLAUDE.md) both match the same file. The allow rule is winning. Expected behavior: deny should take precedence over allow when both match, especially when the deny rule is more specific. The current behavior makes deny rules ineffective for any path already covered by a broader allow wildcard.

What should have happened

The Edit/Write operation on ~/.claude/CLAUDE.md should have been blocked silently, with no prompt shown.

jankrav · 3 months ago

Same issue, but in my case I ask it to read "~/.zshrc", approve with "Yes" and it skips global permissions.
I would expect prompt to fail with the reason "Access to the file is denied" or so on.

BIG-TATHEP · 3 months ago

Still reproduces on v2.1.150 (macOS).

Global user settings: permissions.allow contains a bare "Read", and permissions.deny contains Read(**/*.pem), Read(**/*.key), Read(**/credentials.json).

Controlled test with throwaway files (no real secrets):

| Tool call | Deny rule | Result |
|-----------|-----------|--------|
| Read(/tmp/x.key) | Read(**/*.key) | ❌ read succeeded |
| Read(/tmp/credentials.json) | Read(**/credentials.json) | ❌ read succeeded |
| Read(/tmp/x.pem) | Read(**/*.pem) | ❌ read succeeded |
| Bash(rm -rf …) | Bash(rm -rf *) | ✅ correctly blocked |

So enforcement is tool-inconsistent: the Bash(...) deny fires, the Read(...) denies do not. The differentiator in my config is the bare "Read" entry in permissions.allow — the same shape as @agromanowska's variation above (a broad allow wildcard overriding a more-specific deny).

Per the docs, deny is evaluated before allow ("deny → ask → allow, first match wins"), so a Read(**/*.key) deny should win over a bare Read allow. For the Read tool it does not. This makes path-based permissions.deny an unreliable boundary for protecting secret files (.env, *.pem, *.key, credentials.json) whenever a broad Read allow is also present.

Workaround for anyone hitting this: enforce secret-path blocking with a PreToolUse hook returning permissionDecision: "deny" — the hook mechanism fires reliably where the native deny does not.

piotrjez · 2 months ago

Still reproduces on v2.1.170 (Linux).