Bash Security
The Bash security system operates with 5 independent layers of defense in depth. Even if one layer is bypassed, the others continue blocking attacks — from injection validators and path constraints to sandbox isolation and environment scrubbing.
Architecture — 5 Layers
Command Execution Pipeline
Environment variables injected
| SHELL | Shell binary path |
| GIT_EDITOR | 'true' — prevent interactive git editors |
| CLAUDECODE | '1' — signals CLIs they're inside Claude Code |
| CLAUDE_CODE_SESSION_ID | Session ID (internal users only) |
Timeout system
sleep is in DISALLOWED_AUTO_BACKGROUND_COMMANDS — never auto-backgroundedSecurity Validators (22+)
Early validators (short-circuit on allow)
| # | Validator | What it catches |
|---|---|---|
| 1 | validateEmpty | Empty commands → allow |
| 2 | validateIncompleteCommands | Fragments starting with tabs, flags, operators |
| 3 | validateSafeCommandSubstitution | Safe heredoc-in-substitution patterns |
| 4 | validateGitCommit | git commit with simple quoted messages |
Main validators — misparsing attacks
| # | Validator | What it catches |
|---|---|---|
| 5 | validateJqCommand | system(), -f/--from-file |
| 6 | validateObfuscatedFlags | Quoted characters in flag names |
| 7 | validateShellMetacharacters | ;, |, & in arguments |
| 8 | validateDangerousVariables | Variables in redirection/pipe contexts |
| 9 | validateCommentQuoteDesync | # causing quote tracking desync |
| 10 | validateQuotedNewline | Newlines inside quotes splitting commands |
| 11 | validateCarriageReturn | CR shell-quote / bash tokenization differential |
| 12 | validateNewlines | Newlines separating commands (non-misparsing) |
| 13 | validateIFSInjection | $IFS variable usage |
| 14 | validateProcEnvironAccess | /proc/*/environ access |
| 15 | validateDangerousPatterns | Backticks, $(), $${}, $[], Zsh expansions |
| 16 | validateRedirections | Input < and output > redirections (non-misparsing) |
| 17 | validateBackslashEscapedWhitespace | Backslash-space bypasses |
| 18 | validateBackslashEscapedOperators | \;, \| etc. |
| 19 | validateUnicodeWhitespace | Non-ASCII whitespace characters |
| 20 | validateMidWordHash | # adjacent to quotes |
| 21 | validateBraceExpansion | Brace expansion patterns {a,b} |
| 22 | validateZshDangerousCommands | zmodload, emulate, sysopen, etc. (20 cmds) |
| 23 | validateMalformedTokenInjection | Unbalanced delimiters + separators (HackerOne eval bypass) |
Misparsing vs non-misparsing
Misparsing validators Their ask results set isBashSecurityCheckForMisparsing: true, causing early blocking in the permission flow. Catch attacks that exploit differences between how the security parser and actual bash tokenize commands.
Non-misparsing validators validateNewlines and validateRedirections — results are deferred to ensure misparsing validators still run first.
Quote extraction — 3 views
View What it does withDoubleQuotes Single-quoted removed, double-quoted preserved fullyUnquoted All quoted content removed unquotedKeepQuoteChars Content removed but quote delimiters preserved
Safe redirection stripping
stripSafeRedirections() removes known-safe patterns:
✓ 2>&1 (stderr to stdout) ✓ >/dev/null or 2>/dev/null ✓ </dev/null (empty stdin) Each pattern requires trailing boundary (?=\s|$) to prevent prefix attacks (e.g., >/dev/nullo would write to file nullo).
Permission Flow & Read-Only Commands
bashToolHasPermission() — 8-step pipeline
1. checkPermissionMode() — acceptEdits → auto-allow mkdir, touch, rm, etc. 2. checkReadOnlyConstraints() — Parse command → if proven read-only → allow 3. bashCommandIsSafeAsync() — Run all 22+ validators → any "ask" → block with reason 4. checkPathConstraints() — Extract paths → validate within allowed directories 5. checkSedConstraints() — Special handling for sed -i in-place edits 6. Permission rule matching — Check allow rules (prefix), check deny rules 7. Sandbox auto-allow — If sandboxed + autoAllowBashIfSandboxed → allow 8. ML Classifier (feature-gated) — ANT-only: bash classifier model → allow/deny Compound command handling
Commands with &&, ||, ;, | are split via splitCommand_DEPRECATED(). Cap of 50 subcommands prevents CPU exhaustion. If any subcommand fails → entire command blocked.
Read-only command allowlist
File operations: ls, cat, head, tail, wc, file, stat, du, df, find, tree, realpath, md5sum, sha256sum, xxd Text processing: grep, rg, awk, sed (no -i), sort, uniq, cut, tr, diff, comm, jq, yq, xq, column Git (read): git status, git log, git diff, git show, git branch, git tag, git blame, git shortlog Development: node -e, python -c, tsc --noEmit, eslint, prettier --check, cargo check, go vet System: echo, printf, date, env, which, type, uname, hostname, whoami, id, pwd, test Per-command flag validation
fd -x / -X — excluded: --exec / --exec-batch trigger code execution fd -l — excluded: internally executes ls subprocess (PATH hijacking risk) jq — blocks system(), -f, --from-file sed -i — in-place edit: NOT read-only, requires explicit permission Path Validation & Filesystem Protection
Per-command path extractors (30+ commands)
Commands Path extraction strategy cd, ls, cat, head, tail Positional args after flag stripping rm, rmdir, mkdir, touch All non-flag arguments mv, cp Source and destination arguments find First argument (search root) grep, rg File arguments after pattern sed -i target file git Subcommand-specific extraction jq Input file argument
POSIX -- end-of-options handling
filterOutFlags() correctly handles --: after it, ALL arguments are treated as paths, preventing:
rm -- -/../.claude/settings.local.json
# "-/../.claude/..." is a PATH, caught as traversal
Protected files & directories
Protected files (auto-edit blocked)
.gitconfig, .gitmodules, .bashrc, .bash_profile, .zshrc, .zprofile, .profile, .ripgreprc, .mcp.json, .claude.json
Protected directories
.git, .vscode, .idea, .claude
Claude config (extra protection)
.claude/settings.json, .claude/settings.local.json, .claude/commands/, .claude/agents/, .claude/skills/
Path safety checks
Check What it catches NTFS ADS : after position 2 (alternate data streams) 8.3 short names ~\d patterns (Windows short filenames) Long path prefixes \\?\, \\.\ (Windows extended) DOS device names CON, PRN, AUX, NUL, etc. Triple dots ... in path components Symlink resolution Both original path AND resolved target checked Case normalization normalizeCaseForComparison() lowercased everywhere
Sandbox & Environment Security
Sandbox platforms
Platform Technology Status macOS seatbelt (sandbox-exec) Built-in Linux bubblewrap (bwrap) Requires install WSL2 bubblewrap Requires install Windows native — Not supported
Sandbox decision flow
1. Sandboxing enabled? → No → no sandbox 2. dangerouslyDisableSandbox: true + areUnsandboxedCommandsAllowed() → no sandbox 3. Command in excludedCommands list? → no sandbox 4. Apply SandboxManager.wrapWithSandbox() excludedCommands is NOT a security boundary — permission system still applies.
Bare git repo defense
Sandbox blocks writes to git-structural files at CWD: HEAD, objects/, refs/, hooks/, config. Post-command, scrubBareGitRepoFiles() deletes any planted files. Prevents attackers from planting malicious git hooks.
Subprocess env scrubbing
Activated when CLAUDE_CODE_SUBPROCESS_ENV_SCRUB is set (GitHub Actions context):
Category Variables stripped Anthropic ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_AUTH_TOKEN AWS AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_BEARER_TOKEN_BEDROCK GCP/Azure GCP/Azure credential variables GitHub Actions ACTIONS_ID_TOKEN_REQUEST_TOKEN, runtime tokens OTEL Headers that may contain auth tokens INPUT_* All INPUT_ prefixed variants of the above
Variables NEVER safe to strip
Category Variables Risk Execution PATH, LD_PRELOAD, LD_LIBRARY_PATH, DYLD_* Binary/library hijacking Module loading PYTHONPATH, NODE_PATH, CLASSPATH, RUBYLIB Code injection via imports Code execution GOFLAGS, RUSTFLAGS, NODE_OPTIONS Arbitrary code via flags System HOME, TMPDIR, SHELL, BASH_ENV Behavior modification
Dangerous Patterns, PowerShell & Destructive Warnings
Dangerous command patterns
Cross-platform (code execution)
python, python3, node, deno, tsx, ruby, perl, php, lua, npx, bunx, npm run, yarn run, bash, sh, ssh
Unix additions
zsh, fish, eval, exec, env, xargs, sudo
Zsh-specific (20 builtins)
zmodload, emulate, sysopen, syswrite, ztcp, zsocket, zf_rm, zf_mkdir, zf_rmdir, zf_ln, zf_mv, zf_chmod…
Destructive command warnings
Git: git reset --hard, git push --force, git clean -f, git checkout ., git stash drop/clear, git branch -D, git commit --amend Filesystem: rm -rf, rm -r, rm -f Database: DROP TABLE/DATABASE, TRUNCATE TABLE, DELETE FROM Infrastructure: kubectl delete, terraform destroy These are informational warnings in the permission dialog — not security blocks.
PowerShell security (parallel system)
Mirrors the Bash system with platform-specific adaptations: per-cmdlet CmdletPathConfig, case-insensitive matching, GIT_SAFETY_WRITE_CMDLETS, archive extractor detection.
PowerShell dangerous patterns
pwsh, powershell, cmd, wsl, iex, invoke-expression, start-process, saps, start-job, sajb, register-objectevent
PowerShell auto-mode block categories (4)
1 Download-and-Execute: iex (iwr ...) — equivalent to curl | bash 2 Irreversible Destruction: Remove-Item -Recurse -Force — equivalent to rm -rf 3 Persistence: Modifying $PROFILE, Register-ScheduledTask, registry Run keys 4 Elevation: Start-Process -Verb RunAs, -ExecutionPolicy Bypass, disable AMSI/Defender 12 Security Design Principles
1. Defense in Depth Multiple independent layers each block attacks. Bypassing one layer doesn't compromise the others.
2. Fail-Safe Defaults Unknown parameters, parse failures, complex commands → ask. The system never assumes safety.
3. Misparsing Awareness Validators explicitly account for differences between security parser and actual bash tokenization.
4. Case-Insensitive Paths normalizeCaseForComparison() applied everywhere to prevent macOS/Windows case-based bypasses.
5. Symlink Resolution Both original path AND resolved target checked for all security decisions.
6. No Trust in User Controls excludedCommands is explicitly NOT a security boundary — security comes from the permission system.
7. Subprocess Env Scrubbing API keys and CI tokens stripped from subprocess envs in GitHub Actions contexts.
8. Bare Git Repo Defense Sandbox blocks writes to git-structural files. Post-command scrubbing removes any planted files.
9. Per-Process Nonces Bundled skills root uses randomBytes(16) to prevent pre-creation attacks on shared /tmp.
10. Compound Command Splitting &&, ||, ;, | operators parsed — each subcommand validated independently. Cap of 50 prevents CPU exhaustion.
11. Quote Context Tracking 3 views of each command enable precise detection of metacharacters hidden in quoted contexts.
12. Boundary Assertions Safe redirection stripping requires trailing boundaries (?=\s|$) to prevent prefix attacks.
i Output handling
Tool results > 30,000 chars are persisted to file; > 64 MB is the maximum persisted size.
The EndTruncatingAccumulator keeps the beginning and end of output, truncating the middle with
[... truncated ...]. Large outputs get a preview with a pointer to the full file for the Read tool.