Bash permission allow-rule doesn't split on ;/&& before matching (contrary to docs) — compound-command smuggling bypasses scoped allowlist

Status Open
Reported on v2.1.183
Maintainer reply None cached
Activity 2 comments · opened Sep 14, 2026

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

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):

{
  "permission_denials": [],
  "result": "Raw output:\n\n```\n<git log output>\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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗