claude-code - 💡(How to fix) Fix Documentation discrepancy: Read(...) deny rules affect Bash tool calls (undocumented behavior) [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#45200Fetched 2026-04-09 08:10:56
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Author
Participants
Timeline (top)
labeled ×5

Claude Code v2.1.92 blocks Bash tool calls whose arguments structurally match paths in Read(...) deny rules, even when sandbox is disabled. The official docs at code.claude.com/docs/en/permissions explicitly state the opposite: "Read and Edit deny rules apply to Claude's built-in file tools, not to Bash subprocesses." This discrepancy was confirmed via controlled experiment.

Error Message

  1. Observed: the Bash call is denied with the error Permission to use Bash with command ls ~/private-dir/ has been denied.

Root Cause

  • Positive: users who set Read(...) denies get stronger protection than the docs describe. This is a useful defense-in-depth.
  • Negative:
    1. Users cannot rely on this protection, because the docs actively tell them it does not exist.
    2. The protection has a trivially exploitable bypass (test 5: assign the full path to a variable).
    3. If a future release "fixes" the behavior to match the current docs, users will silently lose protection they did not know they had.
    4. The mechanism contradicts the stated separation between permission layers and sandbox layers, making the security model harder to reason about.

Code Example

{
     "permissions": {
       "allow": ["Bash(ls *)"],
       "deny": ["Read(~/private-dir/**)"]
     },
     "sandbox": { "enabled": false }
   }
RAW_BUFFERClick to expand / collapse

Summary

Claude Code v2.1.92 blocks Bash tool calls whose arguments structurally match paths in Read(...) deny rules, even when sandbox is disabled. The official docs at code.claude.com/docs/en/permissions explicitly state the opposite: "Read and Edit deny rules apply to Claude's built-in file tools, not to Bash subprocesses." This discrepancy was confirmed via controlled experiment.

Environment

  • Claude Code version: 2.1.92
  • Platform: macOS (Darwin 25.2.0)
  • Shell: zsh
  • Sandbox status: disabled (verified during the session via the /sandbox slash command)
  • No managed settings, no project-level settings, no PreToolUse hooks matching Bash

The contradiction

What the docs say

From permissions.md, "Read and Edit" section (verbatim):

⚠️ Warning Read and Edit deny rules apply to Claude's built-in file tools, not to Bash subprocesses. A Read(./.env) deny rule blocks the Read tool but does not prevent cat .env in Bash. For OS-level enforcement that blocks all processes from accessing a path, enable the sandbox.

What actually happens

With only "Read(~/private-dir/**)" in permissions.deny and sandbox disabled, Bash tool calls whose arguments resolve (or structurally pattern-match) to paths under ~/private-dir/ are auto-denied within ~7 ms — far faster than a human prompt-approval could happen.

Reproduction

  1. In ~/.claude/settings.json, ensure:
    {
      "permissions": {
        "allow": ["Bash(ls *)"],
        "deny": ["Read(~/private-dir/**)"]
      },
      "sandbox": { "enabled": false }
    }
  2. Create ~/private-dir/ with any files inside.
  3. In a Claude Code session, ask Claude to run ls ~/private-dir/.
  4. Observed: the Bash call is denied with the error Permission to use Bash with command ls ~/private-dir/ has been denied.
  5. Expected per docs: the Bash call should succeed, since Bash(ls *) is in allow and no Bash(...) deny rule matches.

Test matrix (what characterized the mechanism)

Tests run in a single session with Read(~/private-dir/**) active in the deny array, sandbox off:

#CommandExpected per docsObserved
1Read tool on ~/private-dir/fileDeniedDenied ✓
2ls ~/Desktop/AllowedAllowed ✓
3ls ~/private-dir/AllowedDenied
4echo private-dir is a stringAllowedAllowed ✓
5BD=~/private-dir; ls "$BD"AllowedAllowed ✓ (listed contents)
6B=private; C=dir; ls ~/$B-$CAllowedDenied

Tests 3 and 6 contradict the docs. Tests 4 and 5 show the mechanism is not a literal substring scan — it appears to be a structural path-pattern analysis that treats embedded shell variables as wildcards and conservatively matches against Read(...) deny rules.

Control experiment proving causation

After observing the discrepancy, I removed the single line "Read(~/private-dir/**)" from permissions.deny, restarted no session state, and re-ran tests 3 and 6 in the same running Claude Code session:

  • Test 3 (ls ~/private-dir/): passed, listed directory contents
  • Test 6 (B=private; C=dir; ls ~/$B-$C): passed, listed directory contents

Re-adding the line restored the denials. The Read(...) rule is the sole causal variable.

Evidence from session logs

The session JSONL at ~/.claude/projects/.../<session>.jsonl shows these paired events for test 3:

  • tool_use (Bash ls ~/private-dir/) at 2026-04-07T20:43:18.119Z
  • tool_result (denial) at 2026-04-07T20:43:18.127Z

An 8 ms gap rules out interactive user denial. The denial is produced by an internal automatic check.

Mechanism hypothesis

Based on the asymmetry between tests 4, 5, and 6:

  • Test 4 (echo private-dir is a string) passed — the matcher does not do blind literal substring scanning.
  • Test 5 (BD=~/private-dir; ls "$BD") passed — the matcher does not evaluate the command in a subshell to resolve variables. The ls argument is opaque ("$BD") and passes through.
  • Test 6 (B=private; C=dir; ls ~/$B-$C) was denied — the ls argument has a path-like structure (~/ + literal text + - + literal text) and the matcher appears to treat $B/$C as unknowns matching pessimistically against Read(...) deny patterns.

This suggests Claude Code has an argument-structure analyzer in the Bash matcher that checks Bash arguments against Read(...) deny rules using conservative glob matching. The feature is not documented.

Impact

  • Positive: users who set Read(...) denies get stronger protection than the docs describe. This is a useful defense-in-depth.
  • Negative:
    1. Users cannot rely on this protection, because the docs actively tell them it does not exist.
    2. The protection has a trivially exploitable bypass (test 5: assign the full path to a variable).
    3. If a future release "fixes" the behavior to match the current docs, users will silently lose protection they did not know they had.
    4. The mechanism contradicts the stated separation between permission layers and sandbox layers, making the security model harder to reason about.

Requested action

Please either update the docs to describe this cross-tool argument-scanning behavior (including the test-5 bypass), or update the implementation to match the current docs. My main concern is that users should be able to reason about the security model from the documentation alone.

extent analysis

TL;DR

The most likely fix is to update the documentation to reflect the actual behavior of Claude Code's argument-structure analyzer in the Bash matcher.

Guidance

  • Review the documentation at code.claude.com/docs/en/permissions to ensure it accurately describes the behavior of Read(...) deny rules in relation to Bash subprocesses.
  • Consider adding notes on the argument-structure analyzer and its implications for security, including the potential bypass via assigning paths to variables.
  • Evaluate the trade-offs between updating the documentation versus changing the implementation to match the current documentation.
  • Test the behavior with various Bash commands and Read(...) deny rules to ensure the documentation accurately reflects the implementation.

Example

No code changes are suggested at this point, as the issue is primarily related to documentation and understanding of the current implementation.

Notes

The current behavior of Claude Code provides additional protection, but its undocumented nature may lead to confusion and potential security issues if not properly understood. Updating the documentation is a crucial step in ensuring users can rely on the stated security model.

Recommendation

Apply a documentation update to reflect the actual behavior of the argument-structure analyzer in the Bash matcher, including its implications and potential bypasses. This will ensure that users have a clear understanding of the security model and can make informed decisions about their configurations.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING