InkdownInkdown
Start writing

Claude-Code

62 files·4 subfolders

Shared Workspace

Claude-Code
codex

12-permission-system

Shared from "Claude-Code" on Inkdown

Permission System Deep Dive

Overview

The permission system is a multi-layered security framework that controls what Claude can do. It ranges from simple allow/deny rules to sophisticated AI-powered classification.

Plain text
┌─────────────────────────────────────────────────────────────────────────────┐
│                    PERMISSION SYSTEM LAYERS                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  LAYER 5: AI CLASSIFICATION (Auto Mode)                                     │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │  LLM evaluates: "Is this operation safe?"                          │   │
│  │  Input: Tool name, arguments, file paths, git status                │   │
│  │  Output: SAFE / ASK / DENY                                           │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                              │                                              │
│  LAYER 4: MODE OVERRIDE                                                      │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │  Mode: default / auto / bypass                                       │   │
│  │                                                                      │   │
│  │  default: Ask for dangerous operations                               │   │
│  │  auto:   AI classifies, then auto-allow/deny                         │   │
│  │  bypass: Allow everything (enterprise policy permitting)             │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                              │                                              │
│  LAYER 3: RULES ENGINE                                                       │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │  alwaysAllowRules:  Glob patterns for trusted paths/commands         │   │
│  │  alwaysDenyRules:   Glob patterns for forbidden operations           │   │
│  │  alwaysAskRules:    Force prompts for specific patterns            │   │
│  │                                                                      │   │
│  │  Example: { pattern: "**/node_modules/**", action: "deny" }           │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                              │                                              │
│  LAYER 2: TOOL PERMISSION LEVEL                                              │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │  background: Always allow (reading files, searching)                │   │
│  │  user:       Show indicator (moderate risk)                          │   │
│  │  dangerous:  Require approval (write, delete, execute)             │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                              │                                              │
│  LAYER 1: POLICY LIMITS (Enterprise)                                        │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │  Organization-defined global restrictions:                          │   │
│  │  - allow_remote_control: false                                         │   │
│  │  - allow_auto_mode: false                                            │   │
│  │  - blocked_domains: ["evil.com"]                                       │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘
0000_start_here_index_and_recommended_reading_order.md
0100_project_overview_tech_stack_runtime_modes_and_folder_map.md
0200_startup_flow_entry_points_and_cold_start_sequence.md
0300_codebase_modules_layers_state_models_and_schemas.md
0400_system_architecture_and_design_rationale.md
0500_interactive_repl_request_flow_end_to_end.md
0600_headless_sdk_and_print_mode_request_flow_end_to_end.md
0700_mcp_integration_connection_and_tool_call_flow.md
0800_external_services_sdks_storage_and_local_dependencies.md
0900_environment_variables_settings_feature_flags_and_failure_modes.md
1000_non_obvious_patterns_gotchas_and_debugging_traps.md
1100_full_codebase_file_inventory_grouped_by_directory.md
kimi
00-overview.md
01-entrypoints.md
02-state-management.md
03-query-system.md
04-tools-system.md
05-tasks-system.md
06-ui-components.md
07-bridge-remote.md
08-services.md
09-skills-plugins.md
10-commands.md
11-testing-architecture.md
12-permission-system.md
13-build-system.md
14-ink-internals.md
15-git-internals.md
16-context-compaction.md
17-vim-mode.md
18-mailbox-notifications.md
19-session-persistence.md
20-hooks-system.md
21-error-recovery.md
README.md
qwen
00-overview.md
01-entry-points.md
02-query-engine.md
03-tools-and-tasks.md
04-commands-and-skills.md
05-state-management.md
06-ink-rendering.md
07-bridge-remote.md
08-mcp-services.md
09-services-overview.md
10-multi-agent.md
11-system-prompt-constants.md
12-tool-interface.md
13-memory-system.md
14-buddy-companion.md
15-keybindings.md
16-stop-hooks.md
17-vim-mode.md
18-upstreamproxy.md
19-cost-tracking-history.md
20-contexts-styles-onboarding.md
21-hooks.md
22-screens.md
tweets-explain
claude-code-memory-analysis.md
compact
memory-system
agentic-architecture

Core Files

FilePurpose
utils/permissions/permissions.tsCore permission checking
utils/permissions/permissionSetup.tsInitialization and mode setting
utils/permissions/autoModeState.tsAuto mode state machine
utils/permissions/classifier.tsAI classification
utils/permissions/denialTracking.tsTrack and learn from denials
utils/permissions/PermissionMode.tsMode definitions
constants/tools.tsTool permission levels
types/permissions.tsTypeScript types

Permission Modes

TypeScript
// utils/permissions/PermissionMode.ts
export type PermissionMode =
  | 'default'    // Ask for dangerous tools
  | 'auto'       // AI classifies, then auto-decides
  | 'bypass'     // Allow everything (policy permitting)

export const PERMISSION_MODES: PermissionMode[] = ['default', 'auto', 'bypass']
Mode Transitions
Plain text
┌─────────┐    User selects     ┌─────────┐
│ default │ ───────────────────►│  auto   │
│         │◄────────────────────│         │
└────┬────┘    User toggles    └────┬────┘
     │                               │
     │ Enterprise policy             │ Enterprise policy
     │ allow_bypass: true           │ allow_bypass: true
     │                               │
     └──────────────┬────────────────┘
                    ▼
              ┌─────────┐
              │ bypass  │
              │         │
              └─────────┘

Permission Check Flow

TypeScript
// utils/permissions/permissions.ts
export async function canUseTool(
  tool: Tool,
  input: unknown,
  context: ToolUseContext
): Promise<PermissionResult> {
  const { toolPermissionContext: ctx } = context.getAppState()

  // 1. POLICY CHECK (Enterprise)
  const policyCheck = checkPolicyLimits(tool, input)
  if (!policyCheck.allowed) {
    return policyCheck
  }

  // 2. TOOL PERMISSION LEVEL
  if (tool.permissionLevel === 'background') {
    return { allowed: true }
  }

  // 3. RULES ENGINE
  const ruleResult = checkRules(tool, input, ctx)
  if (ruleResult.action !== 'continue') {
    return ruleResult
  }

  // 4. MODE CHECK
  switch (ctx.mode) {
    case 'bypass':
      return { allowed: true, auto: true }

    case 'auto':
      return await classifyAndDecide(tool, input, context)

    case 'default':
    default:
      if (tool.permissionLevel === 'dangerous') {
        return await showPermissionDialog(tool, input)
      }
      return { allowed: true }
  }
}

Rules Engine

Rule Structure
TypeScript
// types/permissions.ts
type PermissionRule = {
  // Glob pattern for matching
  pattern: string

  // What to match against
  matchType: 'tool_name' | 'tool_input' | 'file_path' | 'command'

  // Action
  action: 'allow' | 'deny' | 'ask'

  // Optional: only for specific directories
  scope?: string[]
}

type ToolPermissionRulesBySource = {
  // Rules from different sources
  settings?: PermissionRule[]      // ~/.claude/settings.json
  project?: PermissionRule[]        // .claude/CLAUDE.md
  session?: PermissionRule[]      // Runtime /config
}
Rule Sources (Priority Order)
Plain text
┌─────────────────────────────────────────────────────────────┐
│               RULE SOURCE PRIORITY                           │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  1. SESSION (Highest)                                        │
│     - Set via /config or --flags                             │
│     - "always allow in this session"                        │
│                                                              │
│  2. PROJECT (.claude/CLAUDE.md)                             │
│     - Team-shared rules                                       │
│     - Committed to git                                       │
│                                                              │
│  3. SETTINGS (~/.claude/settings.json)                      │
│     - User personal preferences                              │
│                                                              │
│  4. DEFAULTS (Lowest)                                        │
│     - Built-in safe defaults                                 │
│                                                              │
│  Later sources override earlier ones.                        │
│                                                              │
└─────────────────────────────────────────────────────────────┘
Rule Matching
TypeScript
function checkRules(
  tool: Tool,
  input: unknown,
  ctx: ToolPermissionContext
): RuleResult {
  // Get rules from all sources
  const allRules = [
    ...(ctx.alwaysDenyRules.session || []),
    ...(ctx.alwaysDenyRules.project || []),
    ...(ctx.alwaysDenyRules.settings || []),
    // ... allow rules, ask rules
  ]

  for (const rule of allRules) {
    const matches = doesRuleMatch(rule, tool, input)

    if (matches) {
      switch (rule.action) {
        case 'deny':
          return {
            action: 'deny',
            reason: `Matched rule: ${rule.pattern}`,
          }
        case 'allow':
          return {
            action: 'allow',
            auto: true,
          }
        case 'ask':
          return {
            action: 'ask',
            rule,
          }
      }
    }
  }

  return { action: 'continue' }
}
Example Rules
JSON
// ~/.claude/settings.json
{
  "permissions": {
    "alwaysAllow": [
      { "pattern": "**/node_modules/**", "action": "deny" },
      { "pattern": "**/.env*", "action": "deny" },
      { "pattern": "git status", "action": "allow" },
      { "pattern": "ls *", "action": "allow" }
    ],
    "alwaysDeny": [
      { "pattern": "rm -rf /", "action": "deny" },
      { "pattern": "*> /dev/null", "action": "ask" }
    ]
  }
}

Auto Mode (AI Classification)

Classification Flow
TypeScript
// utils/permissions/classifier.ts
export async function classifyOperation(
  tool: Tool,
  input: unknown,
  context: OperationContext
): Promise<SafetyClassification> {
  // Build context for classifier
  const classificationPrompt = buildClassificationPrompt({
    tool: tool.name,
    input: JSON.stringify(input),
    cwd: context.cwd,
    gitStatus: await getGitStatus(),
    recentChanges: context.recentFileChanges,
  })

  // Call lightweight classifier model
  const result = await callClassifierModel(classificationPrompt)

  // Parse response
  return parseClassification(result)
}
Classification Prompt
TypeScript
function buildClassificationPrompt(context: ClassificationContext): string {
  return `You are a security classifier for AI assistant operations.
Classify this operation as SAFE, ASK, or DANGEROUS.

Operation: ${context.tool}
Arguments: ${context.input}
Directory: ${context.cwd}
Git Status: ${context.gitStatus}

Consider:
1. Does this modify system files? (DANGEROUS)
2. Does this delete uncommitted work? (ASK)
3. Does this read sensitive files (.env, .ssh)? (ASK)
4. Is this a safe read operation in the project? (SAFE)
5. Does this match recent user patterns? (contextual)

Respond with exactly one word: SAFE, ASK, or DANGEROUS.
Then explain your reasoning in one sentence.`
}
Auto Mode State Machine
Plain text
┌─────────────────────────────────────────────────────────────────┐
│                  AUTO MODE STATE MACHINE                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────┐   Classifier        ┌─────────┐                   │
│  │ PENDING │ ──► returns ───────►│  SAFE   │                   │
│  │         │                      │         │ ──► Auto-allow   │
│  └────┬────┘                      └─────────┘                   │
│       │                                                        │
│       │ Classifier                                              │
│       │ returns ASK                                             │
│       ▼                                                        │
│  ┌─────────┐   User              ┌─────────┐                   │
│  │  ASK    │ ◄─── approves ─────│ AWAITING│                   │
│  │ (dialog)│                      │ DIALOG  │                   │
│  └────┬────┘                      └─────────┘                   │
│       │                                                        │
│       │ User denies                                             │
│       ▼                                                        │
│  ┌─────────┐                                                   │
│  │  DENY   │ ──► Block operation                               │
│  │         │   Learn from denial                               │
│  └─────────┘                                                   │
│                                                                 │
│  ┌─────────┐   Classifier        ┌─────────┐                   │
│  │ PENDING │ ──► returns ───────►│DANGEROUS│                   │
│  │         │                      │         │ ──► Auto-deny    │
│  └─────────┘                      └─────────┘   (with warning) │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
Auto Mode State
TypeScript
// utils/permissions/autoModeState.ts
type AutoModeState =
  | { status: 'idle' }
  | {
      status: 'classifying'
      tool: string
      input: unknown
      startTime: number
    }
  | {
      status: 'awaiting_decision'
      classification: SafetyClassification
      pendingPromise: Promise<PermissionResult>
    }
  | {
      status: 'approved'
      auto: true
      classification: SafetyClassification
    }
  | {
      status: 'denied'
      auto: true
      classification: SafetyClassification
      reason: string
    }

Denial Tracking & Learning

TypeScript
// utils/permissions/denialTracking.ts
type DenialTrackingState = {
  // Recent denials (last 10)
  recentDenials: Array<{
    tool: string
    input: unknown
    timestamp: number
    reason: string
  }>

  // Pattern frequency
  patternDenials: Map<string, number>

  // Suggested rules based on patterns
  suggestedRules: PermissionRule[]
}

// Track a denial
export function recordDenial(
  state: DenialTrackingState,
  tool: string,
  input: unknown,
  reason: string
): DenialTrackingState {
  const newDenial = { tool, input, timestamp: Date.now(), reason }

  // Add to recent
  const recentDenials = [newDenial, ...state.recentDenials].slice(0, 10)

  // Update pattern frequency
  const pattern = extractPattern(tool, input)
  const patternDenials = new Map(state.patternDenials)
  patternDenials.set(pattern, (patternDenials.get(pattern) || 0) + 1)

  // Suggest rule if pattern seen 3+ times
  const suggestedRules = [...state.suggestedRules]
  if (patternDenials.get(pattern)! >= 3) {
    suggestedRules.push({
      pattern,
      action: 'ask',
      reason: `You've denied this pattern ${patternDenials.get(pattern)} times`,
    })
  }

  return { recentDenials, patternDenials, suggestedRules }
}

Permission Dialog

TypeScript
// components/PermissionDialog.tsx
export function PermissionDialog({
  tool,
  input,
  onDecision,
}: PermissionDialogProps) {
  const [rememberChoice, setRememberChoice] = useState(false)
  const [ruleScope, setRuleScope] = useState<'session' | 'project' | 'settings'>('session')

  return (
    <Box borderStyle="double" borderColor="yellow" padding={1}>
      <Text bold color="yellow">
        ⚠️ Permission Required
      </Text>

      <Box marginY={1}>
        <Text>Claude wants to run:</Text>
        <Box paddingLeft={2}>
          <Text bold color="cyan">{tool.name}</Text>
          <Text dimColor>{JSON.stringify(input, null, 2)}</Text>
        </Box>
      </Box>

      <Text>Allow this action?</Text>

      <Box marginTop={1}>
        <Button onPress={() => onDecision('deny')}>
          Deny (d)
        </Button>
        <Button onPress={() => onDecision('allow', rememberChoice && ruleScope)}>
          Allow (a)
        </Button>
      </Box>

      <Box marginTop={1}>
        <Checkbox
          checked={rememberChoice}
          onChange={setRememberChoice}
        >
          Remember this choice
        </Checkbox>

        {rememberChoice && (
          <Select value={ruleScope} onChange={setRuleScope}>
            <Option value="session">This session only</Option>
            <Option value="project">This project (.claude/)</Option>
            <Option value="settings">All projects (~/.claude/)</Option>
          </Select>
        )}
      </Box>
    </Box>
  )
}

Enterprise Policy Integration

TypeScript
// services/policyLimits/index.ts
export async function loadPolicyLimits(): Promise<PolicyLimits> {
  // Fetch from enterprise management system
  const response = await fetch(POLICY_ENDPOINT, {
    headers: { Authorization: getEnterpriseToken() },
  })

  const policy = await response.json()

  return {
    // Feature allowlisting
    allow_remote_control: policy.allowRemoteControl ?? false,
    allow_auto_mode: policy.allowAutoMode ?? false,
    allow_bypass_mode: policy.allowBypassMode ?? false,
    allow_mcp_servers: policy.allowMcpServers ?? true,

    // Data exfiltration protection
    blocked_domains: policy.blockedDomains || [],
    allowed_git_orgs: policy.allowedGitOrgs || [],

    // Rate limits
    max_requests_per_hour: policy.maxRequestsPerHour || 1000,
    max_tokens_per_day: policy.maxTokensPerDay || 1000000,
  }
}

// Check before operation
export function isPolicyAllowed(feature: keyof PolicyLimits): boolean {
  const limits = getLoadedPolicyLimits()
  return limits[feature] ?? true
}

Tool Permission Levels

TypeScript
// constants/tools.ts
export const TOOL_PERMISSION_LEVELS = {
  // Background: Always allowed
  background: [
    'FileReadTool',      // Reading files
    'GlobTool',          // Finding files
    'GrepTool',          // Searching
    'TaskGetTool',       // Reading task output
    'TaskListTool',      // Listing tasks
    'ListMcpResourcesTool',  // Listing MCP resources
    'ReadMcpResourceTool',   // Reading MCP resources
  ],

  // User: Show indicator
  user: [
    'WebSearchTool',     // Web search
    'WebFetchTool',      // Fetching URLs
    'AskUserQuestionTool', // User interaction
  ],

  // Dangerous: Require approval
  dangerous: [
    'BashTool',          // Shell commands
    'PowerShellTool',    // Windows shell
    'FileWriteTool',     // Creating files
    'FileEditTool',      // Editing files
    'AgentTool',         // Spawning agents
    'TaskCreateTool',    // Creating tasks
    'NotebookEditTool',  // Editing notebooks
    'MCPTool',           // MCP tool execution
    'EnterWorktreeTool', // Git worktree
    'ExitWorktreeTool',  // Git worktree
  ],
} as const

Permission Context

TypeScript
// Tool.ts
export type ToolPermissionContext = DeepImmutable<{
  mode: PermissionMode

  // Rules from different sources
  alwaysAllowRules: ToolPermissionRulesBySource
  alwaysDenyRules: ToolPermissionRulesBySource
  alwaysAskRules: ToolPermissionRulesBySource

  // Mode availability (enterprise)
  isBypassPermissionsModeAvailable: boolean
  isAutoModeAvailable?: boolean

  // Background agents can't show UI
  shouldAvoidPermissionPrompts?: boolean

  // Coordinator workers need async checks
  awaitAutomatedChecksBeforeDialog?: boolean

  // Restore previous mode after plan
  prePlanMode?: PermissionMode
}>

Key Security Principles

  1. Defense in Depth: Multiple layers (policy → rules → classification → dialog)
  2. Default Deny: Unknown = ask
  3. Transparency: Users see exactly what will happen
  4. Learning System: Tracks denials to suggest rules
  5. Enterprise Control: Centralized policy overrides local settings
  6. Audit Trail: All permission decisions logged

Debugging Permissions

Bash
# See permission context
claude config get permissions

# Verbose permission logging
CLAUDE_CODE_DEBUG_PERMISSIONS=1 claude

# Check effective rules
claude /config permissions

# Test a specific operation
claude --dry-run "Write a test file"