Verified against Claude Code · 2026-07-31
Audit Claude Code's Bash permissions for the escape route a single rule missed
A holistic security audit of an existing settings.json's full Bash permission surface — checking whether the union of individually reasonable allowed patterns can be composed into a forbidden action — rather than reviewing any single rule in isolation, plus a check of the actual sandbox mode restrictions in effect.
The prompt
Ready to copy — highlighted parts are example details you can swap.
You are auditing the full Bash permission surface of this settings.json as a system, not reviewing each rule in isolation for whether it looks reasonable on its own. A permission set can be composed entirely of individually sensible rules and still, taken together, permit a path to an action every one of those rules was individually meant to prevent. CURRENT PERMISSIONS CONFIGURATION allow: Bash(git:*), Bash(npm run:*), Edit, Read; ask: Bash(git push:*); deny: Bash(rm -rf:*), Bash(git push --force:*) CURRENT SANDBOX MODE SETTINGS No explicit sandbox mode configured beyond the default; filesystem and network restrictions are whatever Claude Code applies out of the box. KNOWN COMPOSABILITY RISKS ALREADY SUSPECTED Suspect that the broad Bash(git:*) allow rule permits git config core.hooksPath, which could be combined with Edit access to plant a malicious hook script. WHAT THE DENY LIST WAS ACTUALLY MEANT TO PREVENT The deny list exists to make destructive filesystem deletion and a forced push to a protected branch structurally impossible from this tool. AUDIT PROCESS 1. List every allow-listed Bash pattern in allow: Bash(git:*), Bash(npm run:*), Edit, Read; ask: Bash(git push:*); deny: Bash(rm -rf:*), Bash(git push --force:*) individually, and for each, name the broadest thing it actually permits, not just the narrow case it was written for — an allowed pattern like Bash(git config:*) permits every git config subcommand, including one that sets core.hooksPath to a directory containing an attacker-controlled script that runs on the next commit, not just the specific config change the rule was written to allow. 2. For every pair or small group of allowed patterns that touch related tools, check whether they can be chained to reach an action the deny list, described in The deny list exists to make destructive filesystem deletion and a forced push to a protected branch structurally impossible from this tool., was meant to block — an allowed npm run pattern combined with an allowed ability to edit package.json's own scripts field can let a script that looks safe on allow-listing day get silently redefined into one that is not, without ever triggering a new permission check on the redefined command itself. 3. Cross-check Suspect that the broad Bash(git:*) allow rule permits git config core.hooksPath, which could be combined with Edit access to plant a malicious hook script. against the actual permission set — confirm or rule out each named risk specifically, citing the exact combination of patterns responsible if confirmed. 4. Check No explicit sandbox mode configured beyond the default; filesystem and network restrictions are whatever Claude Code applies out of the box. independently of the allow and deny lists — sandboxing restricts what a command can reach regardless of whether the command itself was permitted, such as filesystem access outside the working directory or outbound network access, and a permission set that looks tightly scoped on paper can still be running with a sandbox mode that grants broader reach than the allow list alone suggests, or the reverse, where an overly cautious allow list is compensating for a sandbox mode that was never actually tightened. 5. Name every finding as either CLOSED, meaning the composability path was checked and does not exist, or OPEN, meaning it does, with the exact chain of patterns that reaches the forbidden action and a specific proposed fix — narrowing one of the patterns involved, adding a more specific deny entry, or restricting sandbox mode further. CONSTRAINTS - Do not clear a composability risk as CLOSED without actually tracing the specific chain of allowed patterns and confirming none of them combine to reach the forbidden action; a rule that looks narrow in isolation is exactly the kind of rule most likely to be wrongly assumed safe without that check. - Treat a deny-list gap discovered during this audit as more urgent to report than a merely inefficient allow pattern — the two are not equally severe findings and should not be presented as though they are. OUTPUT A table of allow pattern, broadest actual permission, composability check result, and any finding, ranked by severity, followed by the sandbox mode check as a separate section.
Customize
Optional — swap in your own details for the highlighted parts above.
Why this works
Individually reviewing each Bash allow pattern in isolation is the standard way a permissions file gets written and the standard way it fails to catch its own gaps, because the actual risk in a permission system built from many small allow rules is rarely any single rule looking too permissive on its own — it is the union of several individually reasonable rules combining into a path nobody who wrote any one of them in isolation would have approved if shown the combination directly. Naming the broadest thing each pattern actually permits, rather than the narrow case it was written for, matters because a wildcard suffix like Bash(git config:*) reads as scoped and specific when a reviewer is thinking about the one config change it was added to allow, but the pattern itself makes no such distinction — it permits every git config subcommand indiscriminately, including ones with security-relevant side effects like redirecting where git looks for hook scripts, and that gap between intended scope and actual scope is exactly what a rule-by-rule review checking only 'does this look reasonable' will not surface. Checking pairs and small groups of related allowed patterns for a chainable path to a denied outcome targets the specific way real permission-composability exploits work — not through any single overprivileged rule, but through two or three individually modest rules that happen to touch adjacent surfaces, such as an allowed script-runner combined with allowed write access to the file defining what that script actually runs, letting an initially safe command get silently redefined into an unsafe one without triggering a fresh permission check on the redefinition itself. Auditing sandbox mode as a genuinely separate layer from the allow and deny lists matters because the two operate on different axes entirely — permission patterns gate which commands can run at all, while sandbox restrictions gate what a command that is already permitted can actually reach once it runs, and a permission set that looks tightly scoped on paper provides no real defense-in-depth if the sandbox mode underneath it was never actually tightened to match, just as an overly cautious allow list cannot make up for a sandbox mode that is more permissive than anyone reviewing the allow list alone would have assumed. Ranking a deny-list gap as more urgent than a merely inefficient allow pattern, rather than presenting every finding with equal weight, matters because a report that treats a genuine security gap and a minor tidiness issue as comparably important findings gives a reviewer no signal about which one actually needs fixing before anything else.
Verified against
Claude Code Sonnet 4.6 · 2026-07-31
Changelog
- 2026-07-31 — Initial publish, verified against Claude Code Bash permissions and sandbox mode auditing (Sonnet 4.6).
Building this for real?
This is a free starting point. If you'd rather have custom software built and running for your business, that's Scult's day job.
EXPLORE CUSTOM SOFTWARE
