Permission rules don't evaluate pipe segments independently — piped commands bypass allow rules
Summary
When a Bash command contains pipes (|), semicolons (;), &&, or ||, the full command string is matched as a unit against permission rules. This means a command like grep pattern file | head -20 does not match Bash(grep *) in the allow list, even though the command starts with grep.
Expected behaviour
Each segment of a piped/compound command should be evaluated independently against the allow/ask/deny rules. For example:
grep pattern file | head -20- Segment 1:
grep pattern file→ matchesBash(grep *)→ ✅ allow - Segment 2:
head -20→ matchesBash(head *)→ ✅ allow - Result: auto-approved, no prompt
grep pattern file | rm -rf /- Segment 1:
grep pattern file→ matchesBash(grep *)→ ✅ allow - Segment 2:
rm -rf /→ no allow rule → ⚠️ prompt - Result: user is prompted
Current behaviour
The entire command string is matched as a unit. grep pattern file | head -20 does not match Bash(grep *) because * does not match the | character, so the user is prompted even though both segments are individually covered by allow rules.
Why the obvious workaround is a security hole
Adding Bash(grep * | *) to the allow list would fix the prompt for grep ... | head ..., but it would also silently allow grep foo | rm -rf / — the second segment is completely unchecked. So per-segment evaluation is the correct fix, not broader wildcard patterns.
Steps to reproduce
- Add
Bash(grep *)andBash(head *)to theallowlist in~/.claude/settings.json - Have Claude run a command like
grep -r "pattern" . | head -20 - Observe: permission prompt appears despite both commands being individually covered
Environment
- Claude Code version: 2.1.175
- Running via: VSCode extension
- OS: macOS
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗