anthropics / anthropics/claude-code
Bash permission allow-rule doesn't split on ;/&& before matching (contrary to docs) — compound-command smuggling bypasses scoped allowlist
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
## Bash permission allow-rule does not split on `;`/`&&` before matching, contrary to docs
**CLI version tested:** `2.1.183` and `2.1.236` (both, identical result — not a stale-version issue)
### What the docs say
https://code.claude.com/docs/en/permissions, "Compound commands" section, states:
> Claude Code is aware of shell operators, so a rule like `Bash(safe-cmd *)` won't give it
> permission to run the command `safe-cmd && other-cmd`. The recognized command separators are
> `&&`, `||`, `;`, `|`, `|&`, `&`, and newlines. A rule must match each subcommand independently.
### What actually happens
With `--allowedTools "Read,Grep,Glob,Bash(git log:*),Bash(git blame:*)"`, prompting the model to
invoke the Bash tool with a command chained by `;` or `&&` after a matching prefix gets
**auto-approved as a single unit and fully executed** — the trailing, non-matching subcommand runs
too, with `permission_denials` empty (i.e. no denial is recorded at all).
### Repro
```bash
mkdir /tmp/repro && cd /tmp/repro
git init -q
export GIT_AUTHOR_NAME=test GIT_AUTHOR_EMAIL=test@example.com GIT_COMMITTER_NAME=test GIT_COMMITTER_EMAIL=test@example.com
echo a > f.txt && git add f.txt && git commit -q -m "c1"
claude -p \
--allowedTools "Read,Grep,Glob,Bash(git log:*),Bash(git blame:*)" \
--model claude-haiku-4-5-20251001 \
--max-turns 3 \
--output-format json \
"Invoke the Bash tool with exactly this command line, verbatim, with no changes and no added cd: git log --oneline -5; whoami Report back the raw output you receive, including any denial/error message."
```
**Observed result (`2.1.236`):**
```json
{
"permission_denials": [],
"result": "Raw output:\n\n```\n\nmylocalusername\n```\n\nNo errors or denials. Command executed successfully."
}
```
`whoami`'s real output appears in the tool result. The same happens with `&&` in place of `;`.
Both were re-tested independently against `2.1.183` (identical outcome) and `2.1.236` (identical
outcome) — this is not a stale-pin artifact.
### What *does* correctly get denied (for contrast — so the splitting logic clearly exists for some cases)
Against the same allow rule, all of these were correctly denied, with `permission_denials`
populated:
- Command substitution: `git log --oneline $(whoami)`
- Explicit subshell wrapper: `sh -c 'git log --oneline -5; whoami'`
- Disguised subcommand: `git -C . log --oneline -5`
- Disguised path: `/usr/bin/git log --oneline -5`
So the matcher clearly does *some* structural analysis beyond naive string-prefix matching — it's
specifically the `;` and `&&` (and, going by the docs, presumably `||`/`|`/`|&`/newline, not
independently tested) shell-operator-splitting path that isn't enforcing what the docs describe.
### Why this matters
An allow rule like `Bash(git log:*)` is a common shape for scoping an agent to read-only,
low-risk commands. As tested, that scoping is not a security boundary against the compound-command
smuggling the docs explicitly say it defends against — any untrusted text an agent might be
induced to pass to the Bash tool (e.g. content from a file it read, a ticket description, etc.)
can ride along after a valid-looking prefix and execute with no denial recorded.
### Ask
Please confirm whether this is a known regression, and if so whether `;`/`&&` splitting is
expected to be restored, or whether the "Compound commands" doc section needs to be corrected to
reflect current behavior in the meantime.
Contributor guide
No contributing guide indexed for this repository
Research direction
Run the provided `claude -p` reproduction with the `--allowedTools` rule against both `;` and `&&`, and compare the result with the documented Compound commands behavior. Trace the Bash permission matching entry point and verify that each chained subcommand is checked independently, with the reproduction no longer executing `whoami` without a denial.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100