Critical Issue: Bash Pipe Permission Behavior is Unpredictable and Undocumented
Critical Issue: Bash Pipe Permission Behavior is Unpredictable and Undocumented
The Core Problem
After hours of systematic testing, it is impossible to predict how Claude Code handles piped bash commands. The permission system's behavior with pipes is undocumented, inconsistent, and prevents users from having explicit security control.
What Should Be Documented
For a pattern like Bash(git status:*), users need to know:
- ✅ Does it match
git status | head? - ✅ Does it match
git status | awk '{print $1}'? - ✅ Does it match
git status | xargs echo? - ✅ What happens when the right side of the pipe is dangerous?
Currently: No documentation exists explaining this behavior.
Contradictory Test Results
After 35+ systematic tests, here's what we observed:
Approved Command Piped to Various Utilities
| Command | Result | Explanation |
|---------|--------|-------------|
| pwd \| head -1 | ✅ APPROVED | Works |
| pwd \| sed 's/a/b/' | ✅ APPROVED | Works |
| pwd \| awk '{print $1}' | ❌ DENIED | Why? Both pwd and awk behavior unclear |
| pwd \| grep foo | ✅ APPROVED | Works |
| pwd \| cut -d/ -f2 | ✅ APPROVED | Works |
Question: All of these start with approved pwd. Why does piping to awk fail but piping to sed works?
xargs Behavior (Completely Unpredictable)
| Command | Result | Explanation |
|---------|--------|-------------|
| echo test \| xargs echo Got: | ✅ APPROVED | Works |
| echo . \| xargs ls | ❌ DENIED | ls is approved, so why denied? |
| echo x \| xargs pwd | ❌ DENIED | pwd is approved, so why denied? |
| echo x \| xargs -I {} echo {} | ✅ APPROVED | Works with -I flag |
Question: Why does xargs work with echo but not with ls or pwd, when all three are approved commands?
Multiple Pipes
| Command | Result |
|---------|--------|
| echo \| grep \| sed | ✅ APPROVED |
| echo \| head \| awk | ❌ DENIED |
| pwd \| head \| tail | ✅ APPROVED |
Question: What determines if a multi-pipe command is approved?
Inconsistency Between Modes
Interactive Mode vs --print Mode
Same command, different behavior:
# Interactive mode
echo | awk '{print $1}' # ❌ Required permission prompt
# --print mode
claude --print "Run: echo | awk '{print $1}'" # ✅ Actually executed
Question: Why do permission rules differ between interactive and --print modes?
Impact on Security
Users cannot:
- ❌ Predict behavior - Don't know if a command will prompt or execute
- ❌ Audit permission surface - Can't determine what's actually approved
- ❌ Control pipeline composition - Can't allow
git status | headwhile denyinggit status | awk - ❌ Trust the system - Inconsistent behavior undermines confidence
What Users Need
1. Documented Pipe Matching Algorithm
Clear specification:
Pattern: Bash(command:*)
Pipe Behavior:
- command | <approved-utility> → APPROVED (list approved utilities)
- command | <blocked-utility> → DENIED (list blocked utilities)
- command | xargs <any-command> → Check if <any-command> is approved
- command && other → DENIED (command boundary)
- command > file → Requires file write permission
2. Pipeline-Aware Permission Patterns
Let users control both sides of pipes:
{
"allow": [
"Bash(git status:*)|Bash(head:*)", // Explicit: git status piped to head is OK
"Bash(git status:*)|Bash(grep:*)", // Explicit: git status piped to grep is OK
"Bash(dotnet build:*)|Bash(tail:*)" // Explicit: dotnet build piped to tail is OK
],
"deny": [
"Bash(*)|Bash(awk:*)", // Block awk on right side of ANY pipe
"Bash(*)|Bash(perl:*)", // Block perl on right side of ANY pipe
"Bash(*)|Bash(xargs rm*)", // Block dangerous xargs combinations
"Bash(*)|Bash(sed -i*)", // Block in-place sed anywhere
"Bash(*)|Bash(* > *)" // Block file redirection
]
}
Problem: Currently if git status is approved, you have NO CONTROL over what it can be piped to. User cannot allow git status | head while denying git status | awk.
3. Permission Testing Tool
# Test if command would be approved without executing it
claude --test-permission "git status | head -5"
# Output: APPROVED
# Matched rule: Bash(git status:*)
# Pipe: head is in default approved list
# Result: Command would execute without prompt
claude --test-permission "echo | awk '{print $1}'"
# Output: DENIED
# Matched rule: Bash(echo:*) [default]
# Pipe: awk is in default blocked list
# Result: Would require permission prompt
# List all effective permissions including defaults
claude --list-permissions --include-defaults
# Output:
# From settings.json:
# - Bash(git status:*)
# - Bash(pwd)
#
# Built-in defaults:
# - Bash(echo:*)
# - Bash(cat:*)
# - Bash(head:*)
#
# Built-in blocks:
# - Bash(awk:*) when not first command
# - Bash(perl:*)
# - Bash(sed:*) when first command
4. Consistent Behavior
Permission matching should work identically in:
- Interactive mode
--printmode- Agent mode
- Any other execution context
Currently, behavior differs between modes, which is confusing and undermines trust.
Expected Behavior
# User has: Bash(git status:*) in settings.json
git status → ✅ APPROVED (matches pattern)
git status --short → ✅ APPROVED (matches pattern with :*)
git status | head → ✅ APPROVED (head in default safe list)
git status | awk → ❌ DENIED (awk not in safe list)
echo | xargs git status → ❌ DENIED (git status not first command)
User understands exactly why each command was approved/denied.
Actual Behavior
# User has: Bash(git status:*) in settings.json
git status → ✅ Works (as expected)
git status | head → ✅ Works (why?)
pwd | sed 's/a/b/' → ✅ Works (why? sed not in settings)
pwd | awk '{print $1}' → ❌ Denied (why? awk vs sed difference?)
echo | xargs echo → ✅ Works (why?)
echo | xargs pwd → ❌ Denied (why? pwd is approved!)
User has no idea why different pipes behave differently.
Real-World Impact
Scenario: User wants to auto-approve dotnet build but examine output:
dotnet build # Want: auto-approved
dotnet build | tail -20 # Want: auto-approved (safe filter)
dotnet build | sed ... # Want: denied (transformation)
dotnet build | awk ... # Want: denied (complex processing)
Current Reality: Cannot configure this. Pipe behavior is opaque and uncontrollable.
Request
Please provide:
- Documentation of complete permission matching algorithm (especially pipes)
- Testing tool to check permissions without executing (
--test-permission) - Pipeline-aware patterns to control left and right side of pipes separately
- Explicit mode to disable all built-in defaults
- Audit logging of executed commands
Without this, security-conscious users cannot safely use Claude Code with auto-approvals.
---
Priority: Critical - Security and trust issue
Category: Permission System, Documentation Gap
Affects: All users who configure custom permission rules
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗