anthropics / anthropics/claude-code
[BUG] Bash deny rules bypassed by any wrapper program outside ax()'s 8-item unwrap list
- 主要语言
- Python
- 星标
- 145k
- 派生
- 23.1k
- PR 合并指标
- PR 指标待抓取
描述
## 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.
贡献指南
这个仓库没有索引到贡献指南
调研方向
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.
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- bash
- 领域
- cli, security
- Issue 类型
- 缺陷
- 难度
- 5/5
- 预计耗时
- 一周以上
- 活跃度
- 活跃
- 描述清晰度
- 需要澄清
- 新手友好度
- 25/100