C Claude Code Internals
EN | ES

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.

5 defense layers 22+ security validators 12 design principles 3 sandbox platforms
! Defense in depth — not a single check
Each layer operates independently. The validator layer catches injection, the permission layer enforces allow/deny rules, the path layer prevents traversal, the filesystem layer blocks protected files, and the sandbox restricts the OS-level surface. All 5 run on every command.

Architecture — 5 Layers

1
Security Validators — 22+ checks: injection, metacharacters, misparsing attacks
2
Permission System — Allow/deny rules, read-only detection, prefix matching
3
Path Validation — Per-command path extraction, traversal prevention
4
Filesystem Permissions — Protected files/dirs, symlink resolution, case normalization
5
Sandbox — seatbelt (macOS) / bubblewrap (Linux), filesystem + network restriction

Command Execution Pipeline

1. Model emits tool_use: Bash({ command: "..." })
2. checkPermissions() → bashToolHasPermission() (Layers 1–4)
3. If allowed → call() → runShellCommand() → exec()
4. exec() resolves shell provider ($SHELL: zsh / bash / sh)
5. Optionally wrap with SandboxManager.wrapWithSandbox()
6. spawn() child process (detached: true, custom env)
7. File-based output: O_APPEND (atomic) + O_NOFOLLOW (symlink protection)
8. Capture stdout/stderr, apply truncation, return result

Environment variables injected

SHELLShell binary path
GIT_EDITOR'true' — prevent interactive git editors
CLAUDECODE'1' — signals CLIs they're inside Claude Code
CLAUDE_CODE_SESSION_IDSession ID (internal users only)

Timeout system

Progress reporting starts after 2,000ms
Auto-background budget: 15,000ms
sleep is in DISALLOWED_AUTO_BACKGROUND_COMMANDS — never auto-backgrounded

Security Validators (22+)

Early validators (short-circuit on allow)

# Validator What it catches
1validateEmptyEmpty commands → allow
2validateIncompleteCommandsFragments starting with tabs, flags, operators
3validateSafeCommandSubstitutionSafe heredoc-in-substitution patterns
4validateGitCommitgit commit with simple quoted messages

Main validators — misparsing attacks

# Validator What it catches
5validateJqCommandsystem(), -f/--from-file
6validateObfuscatedFlagsQuoted characters in flag names
7validateShellMetacharacters;, |, & in arguments
8validateDangerousVariablesVariables in redirection/pipe contexts
9validateCommentQuoteDesync# causing quote tracking desync
10validateQuotedNewlineNewlines inside quotes splitting commands
11validateCarriageReturnCR shell-quote / bash tokenization differential
12validateNewlinesNewlines separating commands (non-misparsing)
13validateIFSInjection$IFS variable usage
14validateProcEnvironAccess/proc/*/environ access
15validateDangerousPatternsBackticks, $(), $${}, $[], Zsh expansions
16validateRedirectionsInput < and output > redirections (non-misparsing)
17validateBackslashEscapedWhitespaceBackslash-space bypasses
18validateBackslashEscapedOperators\;, \| etc.
19validateUnicodeWhitespaceNon-ASCII whitespace characters
20validateMidWordHash# adjacent to quotes
21validateBraceExpansionBrace expansion patterns {a,b}
22validateZshDangerousCommandszmodload, emulate, sysopen, etc. (20 cmds)
23validateMalformedTokenInjectionUnbalanced 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
withDoubleQuotesSingle-quoted removed, double-quoted preserved
fullyUnquotedAll quoted content removed
unquotedKeepQuoteCharsContent 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, tailPositional args after flag stripping
rm, rmdir, mkdir, touchAll non-flag arguments
mv, cpSource and destination arguments
findFirst argument (search root)
grep, rgFile arguments after pattern
sed-i target file
gitSubcommand-specific extraction
jqInput 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 namesCON, PRN, AUX, NUL, etc.
Triple dots... in path components
Symlink resolutionBoth original path AND resolved target checked
Case normalizationnormalizeCaseForComparison() lowercased everywhere

Sandbox & Environment Security

Sandbox platforms

Platform Technology Status
macOSseatbelt (sandbox-exec)Built-in
Linuxbubblewrap (bwrap)Requires install
WSL2bubblewrapRequires install
Windows nativeNot 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
AnthropicANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_AUTH_TOKEN
AWSAWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_BEARER_TOKEN_BEDROCK
GCP/AzureGCP/Azure credential variables
GitHub ActionsACTIONS_ID_TOKEN_REQUEST_TOKEN, runtime tokens
OTELHeaders that may contain auth tokens
INPUT_*All INPUT_ prefixed variants of the above

Variables NEVER safe to strip

Category Variables Risk
ExecutionPATH, LD_PRELOAD, LD_LIBRARY_PATH, DYLD_*Binary/library hijacking
Module loadingPYTHONPATH, NODE_PATH, CLASSPATH, RUBYLIBCode injection via imports
Code executionGOFLAGS, RUSTFLAGS, NODE_OPTIONSArbitrary code via flags
SystemHOME, TMPDIR, SHELL, BASH_ENVBehavior 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.