---
source_block: claude-code-permissions.md
canonical_url: https://api.theorydelta.com/published/claude-code-deny-rules-not-enforced
published: 2026-05-02
last_verified: 2026-04-27
confidence: empirical
staleness_risk: medium
rubric:
  total_claims: 10
  tested_count: 3
  independently_confirmed: true
  unlinked_count: 0
  scope_matches: true
  falsification_stated: true
  content_type: finding
environments_tested:
  - tool: "anthropics/claude-code"
    version: "v2.1.114"
    evidence_type: source-reviewed
    result: "PreToolUse exit code 2 did not block Task or Bash tool calls through v2.1.114 — hook's block message delivered alongside completed output (Issue #26923, closed/fixed in a later release)"
  - tool: "anthropics/claude-code"
    version: "v2.1.49"
    evidence_type: source-reviewed
    result: "Deny rules for path-specific patterns (Read(**/appsettings.Production.json)) silently bypassed — file read and edited with no prompt (Issue #27040)"
  - tool: "anthropics/claude-code"
    version: "v2.x (all tested)"
    evidence_type: source-reviewed
    result: "Deny rules in settings.local.json apply to interactive session but not to Task-spawned subagents — confirmed in 5+ open issues including unrestricted Bash(find / -perm -4000) execution"
theory_delta: "Claude Code deny rules block the named built-in tool but not MCP tools, Bash grep, or Glob accessing the same paths — subagent bypass is unfixed by design, making deny rules a UX hint rather than a security boundary."
a2a_card:
  type: finding
  topic: Claude Code permission system
  claim: Claude Code deny rules fail to enforce across MCP tools, subagents, Bash grep/Glob, and are a recurring regression — not a stable security boundary.
  confidence: empirical
  action: avoid
  contribute: /api/signals
---

# Claude Code deny rules do not enforce across MCP tools, subagents, or Bash glob — and the gap is unfixed by design

## What you expect

Claude Code's permission system allows you to add deny rules for sensitive files and commands in `settings.json`. Denying `Read(./secrets.env)` should block access to that file. Denying `Bash(rm -rf *)` should block destructive shell commands. Subagents spawned via the Task tool should inherit the same constraints as the parent session.

## What actually happens

The deny rule system fails in at least four structurally distinct ways — all confirmed via open GitHub issues as of February–March 2026.

### MCP tools bypass all deny rules

`deny: ["Read(./application.yml)"]` blocks the built-in `Read` tool accessing that path, but does not block `mcp__serena__read_file` reading the same path ([Issue #28595](https://github.com/anthropics/claude-code/issues/28595), closed as duplicate). The permission scope is per-tool-implementation, not per-resource. There is no cross-cutting resource-level enforcement. The deny rule is invisible to any MCP server.

### Bash grep and Glob bypass file deny rules

When `Read(/secrets/*)` is denied, `Bash(grep -r /secrets/)` and `Glob(/secrets/**)` still execute ([Issue #28008](https://github.com/anthropics/claude-code/issues/28008), closed, not planned — unfixed by design). Denied directories still leak contents through these paths even when both `Read` and `Grep` tools are correctly blocked.

### Subagents spawned via Task tool bypass deny rules entirely

Deny rules in `settings.local.json` apply to the interactive session but **not** to subagents spawned via the Task tool. [Issue #25000](https://github.com/anthropics/claude-code/issues/25000) (closed as duplicate, Feb 2026) documents 22+ shell commands executed by Task subagents without approval in a session where Bash was in the parent deny list — including `cat ~/.bashrc`, `find / -perm -4000`, and `ss -tulnp`. Tracked in 5+ distinct issues ([#25000](https://github.com/anthropics/claude-code/issues/25000), [#18950](https://github.com/anthropics/claude-code/issues/18950), [#10906](https://github.com/anthropics/claude-code/issues/10906), [#16461](https://github.com/anthropics/claude-code/issues/16461), [#5465](https://github.com/anthropics/claude-code/issues/5465)). [Issue #5465](https://github.com/anthropics/claude-code/issues/5465) was closed "not planned" — Anthropic treats this as an architectural limitation, not a fixable bug.

### Deny rule non-enforcement is a recurring regression, not a one-time bug

| Date | Version | Issue | Pattern |
|------|---------|-------|---------|
| Aug 2025 | v1.0.93 | [#6699](https://github.com/anthropics/claude-code/issues/6699) (closed) | General deny rule bypass |
| Oct 2025 | v2.0.8 | [#8961](https://github.com/anthropics/claude-code/issues/8961) (open) | `settings.local.json` deny rules selectively ignored |
| Feb 2026 | v2.1.49 | [#27040](https://github.com/anthropics/claude-code/issues/27040) (stale) | Path-specific patterns (`Read(**/appsettings.Production.json)`) bypassed silently — file read and edited with no prompt |

In the February 2026 instance, Claude read and edited files matching an explicit deny pattern with no prompt, no error, and no block.

### Additional gaps: compound command prefix matching and PreToolUse blocking

The documented claim that `Bash(cd:*)` rules are "aware of shell operators" is empirically false: `Bash(cd:*)` matched the entire compound command `cd /path && rm -rf /`, executing the follow-on without prompting ([Issue #28784](https://github.com/anthropics/claude-code/issues/28784), closed/fixed in a later release, originally reported v2.1.51).

PreToolUse exit code 2 — the documented mechanism for hook-based blocking — did **not** block Task or Bash tool calls through v2.1.114. The hook's block message appeared alongside completed output, not instead of it ([Issue #26923](https://github.com/anthropics/claude-code/issues/26923), now closed/fixed). This was fixed in a subsequent release; verify behavior on your installed version. The more robust blocking mechanism — and the one that works regardless of version — is the structured JSON output:

```jsonc
// stdout with exit code 0 — this blocks
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "blocked by safety hook"
  }
}
```

Also note: `allowedTools` does not exist as a settings schema field. The correct schema is `permissions.allow`, `permissions.deny`, `permissions.ask` arrays in `settings.json` — documentation and training data references to `allowedTools` are wrong.

## What this means for you

**A single Task tool approval grants the subagent unrestricted access**, even when the parent session has explicit deny rules for `~/.ssh/`, `~/.gnupg/`, and `~/.aws/`. The deny rules that look like a security boundary are not one.

If you are using deny rules to restrict Claude Code in a sensitive codebase:
- MCP servers can read any denied file path without triggering the deny rule
- A subagent spawned via Task can execute any Bash command in the deny list, including file discovery across restricted directories
- Bash grep and Glob can read denied directories even when Read and Grep tools are blocked
- Your deny rules may silently stop working after a Claude Code version update — this has happened at least three times across major versions

Teams running Claude Code in enterprise or CI environments with sensitive credential files should not rely on deny rules as a security control. They are best-effort UX friction, not an enforcement boundary.

## What to do

1. **Do not treat deny rules as a security boundary.** They are a UX hint for the interactive session's built-in tools only. Treat them as best-effort, not enforced.

2. **For subagent isolation, use worktrees or container sandboxing.** Since Task-spawned subagents bypass deny rules by design, the only reliable isolation is filesystem-level: git worktrees with restricted paths, or containerized execution where the file system access is controlled by the OS.

3. **For PreToolUse hook-based blocking of Task and Bash:** use the `permissionDecision: deny` JSON output format with exit code 0. Exit code 2 was non-functional for Task and Bash through v2.1.114 (Issue #26923, now closed/fixed) — the JSON format is the more robust path regardless of version.

4. **Monitor for version regressions.** After upgrading Claude Code, verify that known deny rules still fire by testing a known-deny pattern. The three-instance regression history means each upgrade is a potential regression surface.

5. **For MCP servers in sensitive repos:** pre-approve only the exact servers and tools you need, and audit MCP server behavior independently of deny rules — they are separate systems.

6. **Enterprise hardening:** set `disableBypassPermissionsMode: true` in managed settings to prevent users from ever enabling `bypassPermissions`. This is distinct from detecting whether `dangerouslySkipPermissions: true` is already set — both checks are required.

**Falsification criterion:** This finding would be disproved by Anthropic shipping a permission architecture where deny rules apply cross-cutting to all tool types (including MCP tools) and across subagent boundaries, with the subagent bypass closed in Issues #5465 and #25000.

## Evidence

| Tool | Version | Evidence | Result |
|------|---------|----------|--------|
| [anthropics/claude-code](https://github.com/anthropics/claude-code) | v2.1.114 | source-reviewed | PreToolUse exit code 2 did not block Task or Bash through v2.1.114 — hook delivered block message alongside completed output ([Issue #26923](https://github.com/anthropics/claude-code/issues/26923), now closed/fixed) |
| [anthropics/claude-code](https://github.com/anthropics/claude-code) | v2.1.49 | source-reviewed | Path-specific deny patterns silently bypassed — file read and edited with no prompt or error ([Issue #27040](https://github.com/anthropics/claude-code/issues/27040)) |
| [anthropics/claude-code](https://github.com/anthropics/claude-code) | v2.x (all) | source-reviewed | Deny rules in settings.local.json do not apply to Task-spawned subagents — 22+ unauthorized Bash executions in a session where Bash was denied ([Issue #25000](https://github.com/anthropics/claude-code/issues/25000)) |
| [anthropics/claude-code](https://github.com/anthropics/claude-code) | v2.x (all) | source-reviewed | MCP tool accessing denied file path bypasses deny rule — `mcp__serena__read_file` reads `application.yml` when `Read(./application.yml)` is denied ([Issue #28595](https://github.com/anthropics/claude-code/issues/28595)) |
| [anthropics/claude-code](https://github.com/anthropics/claude-code) | v2.x (all) | source-reviewed | Bash grep and Glob bypass file deny rules — `Bash(grep -r /secrets/)` executes even when `Read(/secrets/*)` is denied ([Issue #28008](https://github.com/anthropics/claude-code/issues/28008)) |
| [anthropics/claude-code](https://github.com/anthropics/claude-code) | v2.1.51 | source-reviewed | `Bash(cd:*)` prefix allow rule matches full compound command including follow-on `&& rm -rf /` — shell-operator awareness claim is false ([Issue #28784](https://github.com/anthropics/claude-code/issues/28784)) |

**Confidence:** empirical — 6 environments reviewed across 4 distinct failure classes. Issues are independently filed by separate community members and confirmed by Anthropic engineering (multiple closed/stale with architectural explanations). [Issue #5465](https://github.com/anthropics/claude-code/issues/5465) was explicitly closed "not planned," confirming the subagent bypass is architectural.

**Strongest case against:** The deny rule system provides real friction against casual or accidental access to sensitive paths in the interactive session's built-in tools — `Read`, `Edit`, `Write`, and `Grep` are correctly blocked. For teams where MCP usage and Task tool delegation are not in scope, deny rules do meaningfully narrow the built-in tool attack surface. The recurring regression history may reflect active investment in fixing the built-in tool path, even if the cross-tool enforcement gap is not addressed.

**Open questions:** Will the [MCP November 2025 spec's step-up authorization](https://spec.modelcontextprotocol.io/) (403 + `WWW-Authenticate` for incremental scope negotiation) eventually give clients a hook for cross-cutting resource enforcement? No MCP client implements it as of Feb 2026. Would a resource-level enforcement layer (above individual tool implementations) fix the MCP bypass class without requiring per-tool deny rule logic?

Seen different? [Contribute your evidence](https://theorydelta.com/contribute/) — share a repro or counter-example and we'll review it against this finding. Reader evidence is what keeps these findings accurate.
