anthropics / anthropics/claude-code
[BUG] Bash deny rules bypassed by any wrapper program outside ax()'s 8-item unwrap list
- Langage dominant
- Python
- Étoiles
- 145k
- Forks
- 23.1k
- Métriques de merge des PR
- Métriques de PR en attente
Description
## Summary
`permissions.deny` rules for Bash are matched against the program name of each
sub-command in the parsed AST. Before matching, Claude Code unwraps a **fixed,
eight-item list** of wrapper programs. Any other wrapper — an ordinary binary or
script on `PATH` that execs its arguments — shifts the program name, so no
`Bash( ...)` rule applies to the command underneath it. Deny and allow
rules alike are silently skipped.
This is not the env-var prefix case (#31558, closed) and not the command-chaining
case (#4956). Compound splitting works correctly. The hole is the wrapper
allowlist itself, and I could not find it named in any existing issue.
## Root cause
From `~/.local/share/claude/versions/2.1.258`, function `ax()`. The complete set
of wrappers it strips before rule matching:
```
timeout time nice stdbuf nohup command builtin noglob
```
plus leading `VAR=value` assignments. There is no fallback and no attempt to
resolve what an unknown wrapper actually executes.
## Reproduction
1. `permissions.deny`: `"Bash(git add -A*)"`
2. Ask for `git add -A` → correctly **denied**.
3. Ask for `git add -A` preceded by any wrapper binary on `PATH` that forwards
its arguments → **runs**. The deny rule is never consulted.
Compound forms are correctly caught, which shows the AST splitter is not the
problem: `cd ; git revert ...`, `rm -f ; git add -A ...`, and
`git add -A && git status` were all denied in the same environment.
## Observed impact
In one real session, `git add -A` was denied six times over several weeks. The
same operation then ran four times behind a wrapper, committing 103 unintended
files and 657,373 insertions to a working repository. The user had explicitly
configured that rule to prevent exactly this.
Denials: 2026-08-27T18:15:51Z, 2026-08-28T07:23:26Z, 07:42:37Z, 07:59:53Z,
09:35:12Z, 09:41:26Z.
Bypasses: 2026-09-06T16:57:28Z, 17:12:26Z, 18:44:10Z, 19:01:59Z.
## Why this is worth fixing even though deny rules are "best effort"
The documented position is that deny rules are client-side filtering, useful
against accidents rather than a determined bypass. This case is squarely the
accident it is meant to catch: no adversary, no prompt injection, no attempt to
evade. The wrapper was adopted for an unrelated reason and silently disabled
every git rule in the config.
Related, and relevant: #49874 recommends putting commands behind a wrapper
script specifically so allowlist matching stops seeing them. The same mechanism
that makes that workaround function is what defeats the deny list.
## Suggested direction
1. When the program of a sub-command is not a known wrapper and is not on
`PATH` as a resolvable non-wrapper, treat the command as **unmatched rather
than unmatchable** — i.e. escalate to a prompt instead of falling through.
2. Or: surface an explicit warning at config-load time that `Bash(...)` rules
only bind the literal program name and can be trivially shifted.
3. Or: check every token of a sub-command against deny rules, not only the
leading program.
At minimum, document the eight-item unwrap list. Users writing deny rules today
have no way to know it exists or how short it is.
## Environment
- Claude Code 2.1.258
- Linux
- Permission prompts suppressed for the session, so an unmatched command ran
silently rather than prompting. The bypass itself does not depend on that;
in a prompting session the same command would have asked instead of being
refused outright.
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Piste de recherche
Start by inspecting function ax() in ~/.local/share/claude/versions/2.1.258 and the Bash permission-matching path, then reproduce the behavior with a forwarding wrapper on PATH. The issue names no repository source files or tests; done should include an agreed handling direction and regression coverage showing that a wrapper cannot silently bypass Bash deny rules.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- bash
- Domaine
- cli, security
- Type d'issue
- Bug
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Active
- Clarté
- À clarifier
- Accessibilité débutants
- 25/100