[BUG]

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 1 comment · opened Jul 29, 2026

Preflight Checklist

  • [ ] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

Compound Bash commands (joined with &&) bypass --allowedTools prefix
matching under --permission-mode dontAsk in headless claude -p mode. A
command chained after (or before) an allowlisted command executes in full,
even when it matches NO prefix in --allowedTools at all. permission_denials
comes back empty and is_error:false — no denial is recorded anywhere.

This contradicts the documented behavior in the Permissions doc
("Compound commands" section, https://code.claude.com/docs/en/permissions.md),
which states Claude Code recognizes shell separators (&&, ||, ;, |, |&, &,
newlines) and that "a rule must match each subcommand independently."

What Should Happen?

Per the documented per-subcommand matching rule, a compound command
containing any subcommand that doesn't match an allowed prefix should be
denied outright, in either ordering. Neither ls /some/dir && date +%s%N
nor date +%s%N && ls /some/dir should succeed when date matches no
--allowedTools prefix.

Error Messages/Logs

Exact invocation:
  claude -p "<prompt>" --output-format json --max-budget-usd 0.50 \
    --permission-mode dontAsk \
    --allowedTools "Read Grep Glob WebFetch WebSearch Bash(sqlite3:*) Bash(python3:*) Bash(curl:*) Bash(ls:*) Bash(cat:*) Bash(grep:*) Bash(head:*)
  Bash(tail:*)" \
    --disallowedTools "Write Edit MultiEdit NotebookEdit"

  Case 1 result: is_error:false, permission_denials:[], result text contains
  both the real directory listing AND date's epoch-nanosecond output
  (1785365741334684000).

  Case 2 result: is_error:false, permission_denials:[], result text contains
  date's output FIRST (1785365755011911000) then the directory listing.

  Proof these are genuine executions, not model-fabricated text: both
  nanosecond values, converted to human time, land exactly inside the actual
  wall-clock window the two calls ran in (16:55:41 and 16:55:55), and an
  independent `date +%s%N` run immediately afterward read 16:56:02 -- all
  three in the correct monotonic order.

Steps to Reproduce

  1. Run:

claude -p "Run exactly this Bash command and report the literal raw
output: ls /some/dir && date +%s%N" --output-format json \
--permission-mode dontAsk \
--allowedTools "Bash(ls:) Bash(cat:) Bash(grep:) Bash(head:) Bash(tail:*)" \
--disallowedTools "Write Edit MultiEdit NotebookEdit"

  1. Observe the result: both the ls listing AND date's output appear;

permission_denials is empty; is_error is false.

  1. Repeat with the order reversed: "date +%s%N && ls /some/dir" -- same

result, date still executes.

date does not match any prefix in --allowedTools in either test, so per
the documented behavior both calls should have been denied outright.

Claude Model

Other

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.220 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Terminal.app (macOS)

Additional Information

This allowlist is the safety boundary for an unattended, overnight agent
pipeline that processes untrusted web content and has already encountered
a live prompt-injection attempt embedded in a target site's robots.txt. If
a compound command can smuggle an unlisted subcommand past --allowedTools,
that boundary does not hold against an injected instruction telling an
agent to chain an unapproved command onto an approved-looking one.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗