Permissions
The permission system controls which tools can execute and under what conditions. It combines modes, rules, classifiers, and hooks to provide layered security.
The permission system is security-critical. Changes to permission logic should be reviewed carefully, as bypasses could allow unintended file modifications, command execution, or data exfiltration.
Permission Modes
Claude Code supports several permission modes that control the baseline behavior:
const EXTERNAL_PERMISSION_MODES = [
'acceptEdits',
'bypassPermissions',
'default',
'dontAsk',
'plan',
] as const
type InternalPermissionMode =
| ExternalPermissionMode
| 'auto' // Transcript classifier-based (feature-gated)
| 'bubble' // Propagated from parent contextTRANSCRIPT_CLASSIFIER.The bypassPermissions mode disables all safety checks. Only use it in sandboxed environments where Claude Code cannot access sensitive data or systems.
Permission Rules
Rules define allow/deny/ask behavior for specific tools and content patterns:
type PermissionRuleValue = {
toolName: string
ruleContent?: string // Optional content filter
}
type PermissionRule = {
source: PermissionRuleSource
ruleBehavior: PermissionBehavior // 'allow' | 'deny' | 'ask'
ruleValue: PermissionRuleValue
}Rule Sources
Rules can originate from multiple sources, applied in a defined precedence order:
type PermissionRuleSource =
| 'userSettings' // ~/.claude/settings.json
| 'projectSettings' // .claude/settings.json
| 'localSettings' // .claude/settings.local.json
| 'flagSettings' // Feature flags
| 'policySettings' // Enterprise managed policies
| 'cliArg' // --allowedTools CLI argument
| 'command' // Skill-level tool restrictions
| 'session' // Runtime session-scoped rulesRule Syntax
Rules use a ToolName(content pattern) syntax:
Bash(git *) # Allow git commands in Bash
Write(src/*.ts) # Allow writing to TypeScript files in src/
Read # Allow all Read operations (no content filter)The permissionRuleValueFromString() and permissionRuleValueToString() functions handle parsing and serialization.
Permission Decisions
Every tool invocation produces a PermissionDecision:
type PermissionDecision =
| PermissionAllowDecision // Proceed
| PermissionAskDecision // Prompt the user
| PermissionDenyDecision // Block with messagetype PermissionAllowDecision = {
behavior: 'allow'
updatedInput?: Record<string, unknown>
userModified?: boolean
decisionReason?: PermissionDecisionReason
}May include updatedInput if a hook modified the tool's input before approval.
Permission Updates
Rules can be added, replaced, or removed at runtime:
type PermissionUpdate =
| { type: 'addRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
| { type: 'replaceRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
| { type: 'removeRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
| { type: 'setMode'; destination: PermissionUpdateDestination; mode: ExternalPermissionMode }
| { type: 'addDirectories'; destination: PermissionUpdateDestination; directories: string[] }
| { type: 'removeDirectories'; destination: PermissionUpdateDestination; directories: string[] }Updates are persisted via applyPermissionUpdate() and persistPermissionUpdates().
Bash Command Classification
The bash classifier evaluates shell commands for risk. The external build (bashClassifier.ts) is a stub, but the full implementation provides:
type ClassifierResult = {
matches: boolean
matchedDescription?: string
confidence: 'high' | 'medium' | 'low'
reason: string
}
type ClassifierBehavior = 'deny' | 'ask' | 'allow'Classifier Functions
Path Validation
The permission system validates file paths through additional working directories:
type AdditionalWorkingDirectory = {
path: string
source: WorkingDirectorySource
}Tools that read or write files are checked against the project root and any additional directories specified in settings. Paths outside these boundaries require explicit permission.
Denial Tracking
The system tracks permission denials to detect when the model is stuck in a loop:
type DenialTrackingState = { ... }
function recordDenial(state: DenialTrackingState): void
function recordSuccess(state: DenialTrackingState): void
function shouldFallbackToPrompting(state: DenialTrackingState): booleanAfter repeated denials, shouldFallbackToPrompting() returns true, which triggers a fallback to user prompting instead of automatic rejection.
Hook Integration
The permission system integrates with hooks via executePermissionRequestHooks(). PermissionRequest hooks can intercept permission checks and return allow/deny decisions programmatically, enabling automated approval workflows.
Shadowed Rule Detection
When multiple rules from different sources apply to the same tool, lower-priority rules may be "shadowed" by higher-priority ones. The deletePermissionRuleFromSettings() function handles cleanup, and shouldAllowManagedPermissionRulesOnly() enforces enterprise policy restrictions that prevent user rules from overriding managed settings.