Bible Network Crypto DeFi Onchain RWA AI Agent Stablecoin Chain SAFU CryptoTax DeFAI AGI Claude Me Claude Skill Claude Design Claude Cowork
Independent Media
Not affiliated with any project
Learn Claude Skills. Do Everything Better.
claudeskill-me.com
LATEST
Watching Claude Code Compaction and Hooks Through OpenTelemetry: Reading the compaction, hook_execution_complete and precompute_reuse Fields  ·  Blind Spots in Bash Permission Rules: deny Misses /bin/rm, bash -c and git -C, and devbox run * Lets Any Inner Command Through  ·  CLAUDE.md Too Long? Use .claude/rules/ and paths So Rules Load Only When Claude Touches Matching Files  ·  Chromium Says No usable sandbox! Inside Claude Code's Sandbox: What --no-sandbox, excludedCommands and allowNestedUserNamespaces Each Give Up  ·  Claude Code v2.1.309 Fixes a Batch of Sandbox and Permission Gaps: Startup Files Outside the Sandbox, denyWrite Through Symlinks and More  ·  The auto mode $defaults Trap: Leave One String Out of autoMode.soft_deny and the Built-In Force-Push Rules Disappear
practice

Blind Spots in Bash Permission Rules: deny Misses /bin/rm, bash -c and git -C, and devbox run * Lets Any Inner Command Through

30-Second Version · For the impatient
A Bash deny rule matches command text, not the program; spell the same program differently and the rule cannot see it.

Full Explanation +
01 · Why did this happen?

If I deny Bash(git push *), can it still push?

It can. The docs say the rule stops git push origin main but not spellings such as git -C . push origin main. For a hard Block use the Sandbox network limits or a PreToolUse hook.

02 · What is the mechanism?

How do I write an allow rule that is not too wide?

Put * after the subcommand and include the inner command. Use Bash(devbox run npm test) rather than Bash(devbox run *), which also matches devbox run rm -rf ..

03 · How does it affect me?

Is limiting domains with WebFetch(domain:...) enough?

No. The docs say WebFetch alone does not prevent network access: if Bash is allowed, Claude can still reach any URL with curl. Pair it with denying curl and wget plus the Sandbox network allowlist.

04 · What should I do?

I'm not an engineer. One-line version?

A permission rule is like a "no knives" sign that recognizes only the usual way of carrying one; hide it differently and it does not notice. A real Block needs the security scanner, which here is the Sandbox.

Full Content +

A Bash permission rule looks like "allow or forbid this command", but the official permissions docs repeatedly stress that rules match the command text Claude writes, not the program itself. This article collects the places in those docs where people most easily think something is blocked when it is not. Everything comes from the official permissions page (Configure permissions); I did not test each case myself, so I add no testing claims, and anything marked "my judgment" is my own summary.

1. Where the wildcard goes changes the scope a lot

The docs warn to put * after the subcommand. Bash(git log *) allows only git log, while Bash(git *) allows every git command. More subtle is Bash(git * main): the docs table says it matches git -c core.fsmonitor=<script> diff main, because the * stands in for the subcommand and every option before it, including -c, which makes git run a program you name. Also, a trailing space-star such as Bash(ls *) does not match lsof, while Bash(ls*) does.

2. A deny rule does not stop another spelling of the same program

The docs table lists what deny or ask rules stop and what they do not:

  • Bash(rm *) stops rm -rf build/ but not /bin/rm -rf build/ or bash -c 'rm -rf build/'.
  • Bash(curl *) stops curl https://example.com but not /usr/bin/curl or sh -c 'curl ...'.
  • Bash(git push *) stops git push origin main but not git -C . push origin main, git -c push.default=current push origin main or git 'push' origin main.

The docs conclude plainly: a deny or ask rule covers the invocation Claude usually produces and is not a security boundary around the program. For filesystem and network enforcement that does not depend on command text, use the Sandbox; to inspect the full command text with your own logic, use a PreToolUse hook. Commands in the "does not stop" column are decided by your other rules and the permission mode.

3. Compound commands and stripped wrappers

  • Rules understand shell operators: &&, ||, ;, |, |&, & and newlines. Bash(safe-cmd *) does not approve safe-cmd && other-cmd; each subcommand must match. Deny and ask rules fire if any subcommand matches, including inside a subshell, a command substitution or a for loop.
  • Before matching, a fixed set of wrappers is stripped: timeout, time, nice, nohup, stdbuf, command, builtin, and zsh's noglob, so Bash(npm test *) also matches timeout 30 npm test. The list is built in and not configurable.
  • A leading assignment of certain known-safe environment variables is stripped; for other variables an allow rule does not match past the assignment, but a deny or ask rule does, so a Bash(rm *) deny still catches FOO=bar rm -rf tmp/.
  • Environment runners are not in the list: direnv exec, devbox run, mise exec, npx and docker exec are not stripped, and Bash(devbox run *) matches whatever follows run, including devbox run rm -rf .. The docs suggest a specific rule that includes the inner command, such as Bash(devbox run npm test).
  • Exec wrappers such as watch, setsid, ionice and flock, and find with -exec, -delete or -fprint, cannot be auto-approved by a prefix rule and still prompt in Manual mode.

4. Restricting curl URLs by argument is fragile

The docs use Bash(curl http://github.com/ *) as the example: meant to limit curl to GitHub, it misses options placed before the URL, a switch to https, -L following a redirect, and a URL stored in a variable. Their alternatives: deny curl and wget and allow domains through WebFetch(domain:github.com); because deny does not catch path spellings or sh -c, pair it with the sandbox network allowlist when the restriction must hold; or use a PreToolUse hook. The docs also stress that WebFetch alone does not prevent network access: if Bash is allowed, Claude can still reach any URL with curl.

5. Precedence: deny first, and a hook cannot allow past a deny

The docs say a deny at any scope beats allow: a managed deny cannot be overridden by --allowedTools, and a user-level deny blocks a project-level allow. A PreToolUse hook does not bypass rules: a matching deny rule still blocks, and a matching ask rule still prompts even if the hook returned "allow". In the other direction, a hook that exits with code 2 stops the call before Permission Rules are evaluated, even when an allow rule would let it through.

My judgment: how to use this

This section is my summary, not official advice. Treat Bash rules as a convenience layer that reduces prompts, not as a guarantee that something cannot happen. Things that truly must not happen (deletion, pushing, outbound calls) belong to the sandbox or a hook, and allow rules should be specific enough to include the inner command, avoiding wide shapes like Bash(git *) or Bash(npx *).

Sources: Configure permissions - Claude Code Docs, Configure the sandboxed Bash tool - Claude Code Docs
Diagram
Bash deny 規則擋得住與擋不住的寫法三條 deny 規則各自擋得住的常見寫法與擋不住的繞路寫法,以及被剝除與不被剝除的外殼;真正的邊界要靠沙盒與 hookWhat a Bash deny rule stops, and what it does notRule (deny)StopsDoes not stopBash(rm *)rm -rf build//bin/rm -rf build/ · bash -c 'rm -rf build/'Bash(curl *)curl https://example.com/usr/bin/curl ... · sh -c 'curl ...'Bash(git push *)git push origin maingit -C . push · git -c k=v push · git 'push'Wrappers stripped before matching: timeout, time, nice, nohup, stdbuf, command, builtin, noglobNot stripped: npx, devbox run, mise exec, direnv exec, docker exec (Bash(devbox run *) matches devbox run rm -rf .)For a boundary that does not depend on command text: sandbox (files, network)For full command-text checks: PreToolUse hook (exit code 2 blocks even when an allow rule matches)Claude Skill Me · claudeskill-me.com
Feel free to share. Please credit the source.
Ask a Question
Please enter at least 10 characters
Related Terms
Related Articles
The auto mode $defaults Trap: Leave One String Out of autoMode.soft_deny and the Built-In Force-Push Rules Disappear
practice · Oct 23
Chromium Says No usable sandbox! Inside Claude Code's Sandbox: What --no-sandbox, excludedCommands and allowNestedUserNamespaces Each Give Up
advanced · Oct 23
Check Before Upgrading to Claude Code 2.1.299: Stricter MCP Allow Rules, Skill Names With Odd Characters Rejected, UserPromptSubmit Hooks Now Run First
practice · Oct 19
Claude Code Hooks Fail Open by Default: A Missing Script, a Timeout or Exit 1 Lets the Action Through — Using onFailure: "block" from v2.1.295
practice · Oct 19
Related News