theorydelta field guide
built 2026-06-01 findings: 49 task hubs: 6 independent · evidence-traced · no vendor influence

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

Published: 2026-05-02 Last verified: 2026-04-27 empirical

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, 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, 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 (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, #18950, #10906, #16461, #5465). Issue #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

DateVersionIssuePattern
Aug 2025v1.0.93#6699 (closed)General deny rule bypass
Oct 2025v2.0.8#8961 (open)settings.local.json deny rules selectively ignored
Feb 2026v2.1.49#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, 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, 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:

// 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

ToolVersionEvidenceResult
anthropics/claude-codev2.1.114source-reviewedPreToolUse exit code 2 did not block Task or Bash through v2.1.114 — hook delivered block message alongside completed output (Issue #26923, now closed/fixed)
anthropics/claude-codev2.1.49source-reviewedPath-specific deny patterns silently bypassed — file read and edited with no prompt or error (Issue #27040)
anthropics/claude-codev2.x (all)source-reviewedDeny 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)
anthropics/claude-codev2.x (all)source-reviewedMCP tool accessing denied file path bypasses deny rule — mcp__serena__read_file reads application.yml when Read(./application.yml) is denied (Issue #28595)
anthropics/claude-codev2.x (all)source-reviewedBash grep and Glob bypass file deny rules — Bash(grep -r /secrets/) executes even when Read(/secrets/*) is denied (Issue #28008)
anthropics/claude-codev2.1.51source-reviewedBash(cd:*) prefix allow rule matches full compound command including follow-on && rm -rf / — shell-operator awareness claim is false (Issue #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 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 (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 — share a repro or counter-example and we’ll review it against this finding. Reader evidence is what keeps these findings accurate.

theorydelta.com · 2026 independent · evidence-backed · every claim sourced or labelled glossary · rss · mcp · /scan · llms.txt