Claude CodePermissions
Permissions
5 layersClaude Code has a sophisticated 5-layer permission system that gates every tool execution. From input validation to ML classifiers to user confirmation dialogs.
Permission Modes
defaultAsk user for each potentially unsafe operation
SafeplanAll tools require explicit plan approval first
SafeacceptEditsAuto-approve file edits without asking
ModeratebypassPermissionsAuto-approve everything (dangerous!)
DangerousdontAskAuto-deny everything silently
RestrictiveautoML 'yoloClassifier' auto-approval
AI-gatedThe ML classifier is literally named "yoloClassifier" in the source code. File: utils/permissions/yoloClassifier.ts. You can't make this up.
Permission Decision Flow
1
validateInput()
Pre-checks before permission evaluation. Validates file exists, isn't stale, path is safe. Returns early with error if validation fails.
2
checkPermissions()
Tool-specific permission rules. BashTool has the most complex rules — AST parsing of shell commands, dangerous pattern detection, redirect analysis. Returns allow/deny/ask/passthrough.
3
ML Classifier (opt-in)
AI-based safety evaluation with confidence scoring. Can auto-approve safe patterns (git status, ls). Integrated via tryClassifier() in PermissionContext. Only available in 'auto' mode.
4
Permission Hooks
Custom permission logic from settings.json. Users can define hooks that run shell commands to evaluate tool safety. Hook results can allow, deny, or escalate to user.
5
User UI Dialog
Interactive confirmation dialog if no auto-decision reached. Shows tool name, input, and asks user to allow/deny. Can save decision as persistent rule.
5
Security layers
300KB+
BashTool security code
6
Permission modes
3
HackerOne patches
Permission Rules
Rules are defined in settings.json with three possible behaviors: allow, deny, ask.
// Permission rule examples:
// Tool name targeting
{ "tool": "Bash", "behavior": "ask" }
// Tool + pattern targeting
{ "tool": "Bash", "pattern": "git *", "behavior": "allow" }
// → Only allow git commands automatically
// File path targeting
{ "tool": "Edit", "pattern": "~/.claude/*", "behavior": "allow" }
// → Auto-approve edits only in .claude folder
// Wildcard patterns
{ "tool": "Bash", "pattern": "*test*", "behavior": "allow" }
// → Allow any command containing "test"
// Bash permission rule matching order:
1. Exact match: "git"
2. Prefix match: "git *" (any git subcommand)
3. Wildcard: "*test*" (contains pattern)Permission Result Types
type PermissionResult =
| { behavior: 'allow'; message?: string } // Auto-approve
| { behavior: 'deny'; message: string } // Block with reason
| { behavior: 'ask'; message: string } // Show user dialog
| { behavior: 'passthrough' } // No special handling
// PermissionContext provides:
tryClassifier() // Await ML-based safety results
runHooks() // Execute permission hooks
persistPermissions() // Save updates to disk
buildAllow() / buildDeny() // Decision factories
handleUserAllow() // Log + state update
pushToQueue() // Queue management for UIFilesystem Permission Checks
File operations go through additional path-level security checks:
// filesystem.ts — checkWritePermissionForTool()
Blocked paths:
.gitconfig, .bashrc, .zshrc, .profile // Shell configs
.git/*, .vscode/*, .idea/* // IDE/VCS internals
/etc/*, /usr/* // System directories
Blocked patterns:
Files matching deny rules from toolPermissionContext
Files outside allowed working directories
// checkReadPermissionForTool()
Respects deny rules
Enforces file read ignore patterns (node_modules, .git)
// FileReadTool blocked devices (would hang process):
/dev/zero, /dev/random, /dev/urandom // Infinite output
/dev/stdin, /dev/tty // Blocking input
/proc/self/fd/* // stdio aliases