Path-scoped Bash deny rules silently fail to match (e.g. `Bash(grep * /etc/*)` does not block `grep root /etc/passwd`)

Resolved 💬 3 comments Opened May 6, 2026 by ksacry-ft Closed May 10, 2026

Summary

Bash deny rules with internal * wildcards followed by path patterns silently fail to match commands that they should block per the documented semantics. The deny does not fire, the command falls through to a matching allow (or to Claude Code's built-in read-only list), and runs without prompting.

Documented behavior

Per Configure permissions → Bash:

A single * matches any sequence of characters including spaces, so one wildcard can span multiple arguments. Bash(git *) matches git log --oneline --all, and Bash(git * main) matches git push origin main as well as git merge main.

And precedence:

Rules are evaluated in order: deny -> ask -> allow. The first matching rule wins, so deny rules always take precedence.

Repro

.claude/settings.json:

{
  "permissions": {
    "deny": [
      "Bash(grep * /etc/*)",
      "Bash(grep * ~/.*)"
    ],
    "allow": [
      "Bash(grep:*)"
    ]
  }
}

Start a fresh claude session in any project directory, then have Claude run:

| Command | Expected | Actual |
|---|---|---|
| grep root /etc/passwd | Blocked by Bash(grep * /etc/*) deny | Runs unprompted |
| grep export ~/.bashrc | Blocked by Bash(grep * ~/.*) deny | Prompts (deny did not fire) |
| grep -c \".\" ~/.aws/credentials | Blocked by Bash(grep * ~/.*) deny | Prompts (Claude Code's built-in sensitive-file prompt fires; the deny rule itself did not) |

Even under the most conservative reading of * (single argument, no space spanning), the first command (grep root /etc/passwd — one arg between grep and /etc/passwd) should match. It doesn't.

Side observation: docs disagreement

The Settings page describes the opposite wildcard semantics:

* matches any sequence of characters within a single argument, not across spaces. Wildcards do not span argument boundaries or path separators unless explicitly included.

That contradicts the Permissions page quoted above. Worth aligning the two docs as part of fixing this — confusion about which doc is authoritative was a real time sink while diagnosing the underlying bug.

Environment

  • Claude Code 2.1.131
  • Linux 6.17.0-1017-oem (Ubuntu)
  • Native binary install (~/.local/share/claude/versions/2.1.131)

Workaround

None known short of dropping the broad Bash(grep:*) allow entirely (forcing a prompt on every grep) — defeats the purpose of having a path-scoped deny.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗