Claude Code 2.1.246: audit Bash wildcard rules before trusting Auto mode

Quick answer

Claude Code 2.1.246 adds a startup warning for Bash allow rules whose first wildcard appears before the subcommand, such as Bash(git * main). That pattern does not mean “safe Git operations on main.” The wildcard can stand for any Git subcommand and options placed before it, so the same rule can match git merge main, git push origin main, or a git -c ... command.

The release also adds an Auto mode tab to /permissions. Treat the two changes as one migration task: inventory every effective Bash rule, remove ambiguous middle wildcards, put a wildcard only after the exact subcommand you intend to allow, and keep destructive or external actions behind an ask rule, a blocking PreToolUse hook, and OS-level sandbox limits.

Who this is for

This guide is for developers and teams that keep Claude Code permissions in user, project, local, or managed settings, especially if they use Auto mode, background sessions, CI, or shared repository configuration.

If an agent opens untrusted repositories, pair this audit with the untrusted-repository sandbox checklist. For earlier path, marketplace, and redaction gates, see the Claude Code 2.1.234 security canaries.

What changed—and what it does not prove

Surface Confirmed in 2.1.246 or current docs Boundary
Startup audit Claude Code warns about an allow rule with * before the subcommand A warning does not rewrite or disable the rule
Wildcard semantics * matches any text, including spaces A branch name or final argument does not constrain the operation
/permissions Auto mode classifier rules now have a visible tab when Auto mode is available Visibility is not proof that every action is safe or approved
Rule order Deny, then ask, then allow A narrow allow cannot override a matching broad deny or ask
Compound commands Recognized shell-separated commands are checked independently A text rule is still not an OS sandbox
Hooks and sandbox A blocking PreToolUse hook can stop a call before allow rules; sandboxing restricts Bash at the OS layer Prompt instructions and CLAUDE.md alone are not enforcement

This is a permissions-semantics release, not a claim that Auto mode is universally safe. Version 2.1.246 also fixes a malformed-command approval path and the command sandbox's handling of --setting-sources, but those fixes do not make a broad allow rule least-privilege.

Why Bash(git * main) is broader than it looks

Claude Code matches the text before the first wildcard as the fixed prefix. Compare four shapes:

Rule Effective scope Audit decision
Bash(git status) Exactly one command Keep when the exact form is sufficient
Bash(git log *) The git log subcommand with varying arguments Usually acceptable for a read-only task after canaries
Bash(git * main) Any Git subcommand or option sequence ending in main Remove or rewrite; 2.1.246 warns about this class
Bash(git *) Every Git subcommand Do not use as a convenience allow rule in a normal host environment

The branch name is not a capability boundary. main may appear in a read, write, remote, or configuration-changing command. The current documentation explicitly shows that Bash(git * main) matches both git push origin main and a git -c core.fsmonitor=<script> diff main form.

Run a four-stage permission audit

1. Inventory effective settings, without editing them

Start by using /permissions, because it shows the source file for each effective rule. Then generate a review list from the common local files:

for settings_file in ~/.claude/settings.json .claude/settings.json .claude/settings.local.json; do
  [ -f "$settings_file" ] || continue
  jq -r --arg file "$settings_file" '
    .permissions.allow[]?
    | select(startswith("Bash("))
    | [$file, .]
    | @tsv
  ' "$settings_file"
done

Do not copy a rule from one scope to another merely to make it visible. User, project, local, and managed sources have different ownership and precedence. Record the source, owner, reason, and last verified date for every persistent allow.

2. Classify the first wildcard

For every Bash allow rule, identify the program, subcommand, and first *.

Also inspect wrappers. Claude Code strips a documented fixed set such as timeout, time, nice, nohup, stdbuf, command, and bare xargs before matching. Other environment runners, including devbox run, mise exec, npx, and docker exec, are not equivalent. Never allow an environment runner with a broad suffix merely because the outer program looks familiar.

3. Replace convenience rules with task-shaped rules

A conservative project baseline might look like this:

{
  "permissions": {
    "allow": [
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git log *)",
      "Bash(npm run test *)"
    ],
    "ask": [
      "Bash(git commit *)"
    ],
    "deny": [
      "Bash(git push *)",
      "Bash(git reset --hard *)"
    ]
  }
}

This is an example policy, not a universal safe list. Keep only commands the repository actually needs. If a task must publish, deploy, push, delete, or contact a production system, use a separately reviewed workflow rather than widening a persistent allow rule.

4. Add a semantic backstop for high-impact actions

Text rules are useful for common command shapes, but argument-restricted patterns are fragile. For high-impact Git, cloud, package-publication, or network actions, add a PreToolUse hook that parses the actual command and exits with code 2 on a blocked action. A blocking hook takes precedence even if an allow rule matches.

Then enable sandbox restrictions for filesystem and network reach. Permissions decide which tool call Claude Code may attempt; sandboxing constrains what Bash and its child processes can reach. The Claude Code Action sandbox guide covers the separate runtime boundary.

Seven acceptance canaries

Use a disposable repository with no real remotes, secrets, or production credentials.

Canary Test Pass condition
Exact Allow Bash(git status), then try git status It runs under the intended rule
Exact negative Try git status --short It does not inherit the exact rule silently
Subcommand family Allow Bash(git log *), then try two harmless log forms Both are covered, while a sibling subcommand is not
Middle wildcard Place Bash(git * main) only in an isolated fixture and start 2.1.246 A startup warning appears; the rule is not promoted
Compound Try an allowed read command followed by a write command with && Each subcommand is evaluated; the write does not ride along
Hook Send a synthetic blocked Git or network command PreToolUse blocks it before execution and records the reason
Sandbox Attempt a harmless read outside the allowed path and a denied test domain OS-level filesystem and network boundaries fail closed

Do not use a real push, destructive reset, delete, or production endpoint as a canary. The purpose is to prove the decision path without creating a side effect.

Decision tree

Does the Bash allow rule contain a wildcard?
  no -> is the exact command required and harmless?
          yes -> keep, record owner, run exact and negative canaries
          no  -> remove
  yes -> is the first wildcard after a named subcommand?
           no -> remove or rewrite; never rely on a final branch/path argument
           yes -> does the task require the whole argument family?
                    no -> replace with exact rules
                    yes -> add ask/deny for neighboring impact
                           -> add semantic PreToolUse backstop when high-impact
                           -> verify sandbox reach
                           -> enable limited Auto-mode rollout only after canaries

Common mistakes

Copyable audit record

date / owner / Claude Code version:
repository / settings source / managed policy:
rule / program / subcommand / first wildcard position:
required task / neighboring denied action:
exact positive / exact negative / sibling subcommand:
compound / wrapper / hook / sandbox:
Auto mode classifier tab reviewed:
decision: remove | exact-only | bounded-family | ask | deny | isolated-only
rollback owner / evidence path:

FAQ

Does 2.1.246 disable Bash(git * main)?

No. The release adds a startup warning. Treat the warning as an audit finding and rewrite the rule; do not assume Claude Code changed it for you.

Is Bash(git log *) safe for every repository?

It is narrower because the wildcard follows the log subcommand, but safety still depends on arguments, wrappers, hooks, repository trust, and sandbox configuration. Run positive and negative canaries in your environment.

Can a deny rule replace sandboxing?

No. Permission rules and sandboxing are complementary. The first governs tool-call decisions; the second constrains Bash and child processes at the OS layer.

Sources