codev / docs /architecture /safety-and-permissions.md
chenbhao's picture
docs: document standalone AskUserQuestion channel and CCR forwarding
0f215fe
|
Raw
History Blame Contribute Delete
41.6 kB

安全与权限系统深度分析

本文基于 Codev (Claude Code) 源代码,深入分析其安全架构与权限子系统。 版本参考:commit 835ff5a / cdb3bdd


1. 设计哲学

1.1 Deny-First 原则

系统中所有权限检查的默认行为是拒绝。未在规则中明确允许的操作,最终都会向用户发起询问或直接被拒绝。这一原则贯穿整个权限管道(Authorization Pipeline),体现在:

  • PermissionResult 的默认行为是 ask(询问用户),而非 allow
  • 工具实现的 checkPermissions() 方法若返回 passthrough,上层会将其转换为 asksrc/utils/permissions/permissions.ts:1300-1310
  • bypassPermissions 模式是唯一能跳过所有检查的模式,但该模式可以通过 Statsig 门控(tengu_disable_bypass_permissions_mode)被完全禁用

1.2 人类决策权威

用户始终拥有最终决定权。在任何权限模式下,用户都可以通过终端对话框批准或拒绝操作。即使在 auto 模式下,当分类器(Classifier)判定需阻止操作时,用户仍可通过交互式对话框覆盖分类器决定。

系统通过以下机制保障人类决策权威:

  • 建议系统generateSuggestions, src/utils/permissions/filesystem.ts:1414-1473):当询问用户时,同时提供可操作的建议(如"允许本次会话的所有编辑"、"添加目录到工作区")
  • 权限解释器PermissionExplainer, src/utils/permissions/permissionExplainer.ts):使用 Haiku 模型解释命令的风险等级(LOW/MEDIUM/HIGH)、目的和潜在风险,辅助用户决策
  • 渐进式暴露:只对高风险操作发起询问,低风险操作在适当模式下自动批准

1.3 防御纵深

系统采用多层重叠的安全机制,而非依赖单一安全边界。这意味着即使某一层被绕过,后续层仍能提供保护。

┌─────────────────────────────────────────────────────┐
│                   防御纵深架构                         │
├─────────────────────────────────────────────────────┤
│ 1. 规则系统 (deny/allow/ask rules)                   │
│ 2. PreToolUse Hook (用户自定义拦截)                   │
│ 3. 路径安全检查 (Dangerous Files, Windows 模式)       │
│ 4. 模式驱动的权限处理 (Mode-based Decision)            │
│ 5. 自动模式分类器 (Auto Mode Classifier)              │
│ 6. 用户确认对话框 (User Confirmation Dialog)           │
│ 7. 沙箱 (Sandbox) 容器化执行                          │
│ 8. 拒绝跟踪 (Denial Tracking) 防滥用                  │
└─────────────────────────────────────────────────────┘

1.4 渐进信任

系统支持用户通过时间建立信任轨迹。随着用户批准更多操作,系统会逐步提高自动化程度:

  • 默认模式:auto-approve rate 起始约 20%,每一步都需确认
  • auto 模式改进:系统通过拒绝跟踪(denialTracking.ts)记录分类器连续拒绝次数,超过阈值(连续 3 次或总计 20 次)后回退到用户询问模式
  • 规则积累:用户可逐步添加 alwaysAllow 规则(如 Bash(ls:*)),减少未来对低风险操作的询问
  • acceptEdits 模式:一旦用户选择此模式,工作目录内的所有文件编辑操作自动批准

2. 七种权限模式

权限模式定义在 src/types/permissions.ts:16-36,运行时配置在 src/utils/permissions/PermissionMode.ts:42-91

模式总览

模式 内部名称 符号 自动批准 需要确认 风险等级
默认 default 所有操作 低(最安全)
接受编辑 acceptEdits ⏵⏵ 工作目录内文件编辑 Bash 命令、目录外写入、MCP 中低
计划 plan 读操作(文件读取、搜索) 写操作、Bash 命令
自动 auto ⏵⏵ 分类器批准的 + 安全放行列表 分类器阻止的
不询问 dontAsk ⏵⏵ 无(所有 ask → deny) 无法操作
绕过权限 bypassPermissions ⏵⏵ 所有操作(除安全检查外) 安全检查(.git/ 等) 最高
Bubble bubble Ant 内部使用 Ant 内部使用

2.1 Default(默认模式)

文件: src/utils/permissions/PermissionMode.ts:45-50

default: {
  title: 'Default',
  shortTitle: 'Default',
  symbol: '',
  color: 'text',
  external: 'default',
}
  • 行为: 每一步操作都需要用户确认
  • 自动批准: 无
  • 询问: 所有工具调用
  • 适用场景: 新项目、不信任 AI 操作时
  • 风险: 最低,但效率也最低

2.2 AcceptEdits(接受编辑模式)

文件: src/utils/permissions/PermissionMode.ts:59-65

acceptEdits: {
  title: 'Accept edits',
  shortTitle: 'Accept',
  symbol: '⏵⏵',
  color: 'autoAccept',
  external: 'acceptEdits',
}
  • 行为: 自动批准工作目录内的文件编辑操作(FileEditTool, FileWriteTool
  • 自动批准: 工作目录(getOriginalCwd() 已在 additionalWorkingDirectories 中的路径)内的文件写入
  • 询问: Bash 命令、工作目录外的文件写入、MCP 工具、网络操作
  • 实现参考: src/utils/permissions/filesystem.ts:1360-1375 — 当 mode === 'acceptEdits' 且路径在工作目录内时,直接返回 allow
  • 适用场景: 用户希望 AI 可以直接修改代码,但不想让其执行任意命令

2.3 Plan(计划模式)

文件: src/utils/permissions/PermissionMode.ts:52-58

plan: {
  title: 'Plan Mode',
  shortTitle: 'Plan',
  symbol: PAUSE_ICON,
  color: 'planMode',
  external: 'plan',
}
  • 行为: 只读模式 + 计划讨论
  • 自动批准: 所有读操作(文件读取、搜索、列表等)
  • 询问: 文件编辑、Bash 命令、网络操作
  • 内部机制: 当从 auto 模式进入 plan 时,auto 分类器仍在后台运行(prePlanMode 记录),退出 plan 时恢复 auto 状态(src/utils/permissions/permissionSetup.ts:1462-1493
  • 适用场景: 探索代码库、制定重构计划、代码审查

与 Auto 模式的联动permissionSetup.ts:1446-1455):

当用户已选择加入 auto 模式且 useAutoModeDuringPlan 启用时,plan 模式下分类器仍处于激活状态。这通过 shouldPlanUseAutoMode() 函数判断。

2.4 Auto(自动模式)

文件: src/utils/permissions/PermissionMode.ts:80-90

...(feature('TRANSCRIPT_CLASSIFIER')
  ? {
      auto: {
        title: 'Auto mode',
        shortTitle: 'Auto',
        symbol: '⏵⏵',
        color: 'warning',
        external: 'default',
      },
    }
  : {}),
  • 行为: 使用 AI 分类器自动审批操作
  • 内部依赖: feature('TRANSCRIPT_CLASSIFIER') — 编译期 feature flag,外部构建中通过 DCE(死代码消除)完全移除
  • 自动批准:
    1. acceptEdits 快速路径可批准的(工作目录内编辑)
    2. 安全放行列表中的工具(isAutoModeAllowlistedTool, classifierDecision.ts:96-100
    3. 分类器判定为安全的操作
  • 询问: 分类器判定为需阻止的操作(可被用户覆盖)
  • 拒绝限制: 连续 3 次阻止 → 回退到询问;总计 20 次阻止 → 重置并回退(denialTracking.ts:12-14

2.5 DontAsk(不询问模式)

文件: src/utils/permissions/PermissionMode.ts:73-79

dontAsk: {
  title: "Don't Ask",
  shortTitle: 'DontAsk',
  symbol: '⏵⏵',
  color: 'error',
  external: 'dontAsk',
}
  • 行为: 将所有 ask 决策转换为 deny
  • 转换: src/utils/permissions/permissions.ts:505-518
    if (appState.toolPermissionContext.mode === 'dontAsk') {
      return {
        behavior: 'deny',
        decisionReason: { type: 'mode', mode: 'dontAsk' },
        message: DONT_ASK_REJECT_MESSAGE(tool.name),
      }
    }
    
  • 适用场景: 测试、CI/CD 环境或不想让 AI 执行任何操作的场景

2.6 BypassPermissions(绕过权限模式)

文件: src/utils/permissions/PermissionMode.ts:66-72

  • 行为: 绕过所有权限检查(除安全检查和内容特定 ask 规则外)
  • 禁用机制: 可通过 Statsig 门控 tengu_disable_bypass_permissions_mode 或 settings 中 permissions.disableBypassPermissionsMode 完全禁用(permissionSetup.ts:695-711
  • 例外: bypassPermissions 不影响 requiresUserInteraction 检查(permissions.ts:1231-1236)、安全检查(permissions.ts:1255-1260)和内容特定 ask 规则(permissions.ts:1244-1250

2.7 Bubble(气泡模式)

  • 用途: Ant 内部使用模式,外部构建不可见(PermissionMode.ts:104process.env.USER_TYPE !== 'ant' 时被排除)
  • 文档: 无外部可用信息

3. 授权管道 (Authorization Pipeline)

核心实现位于 src/utils/permissions/permissions.tshasPermissionsToUseToolInner() 函数(第 1158-1319 行)和 hasPermissionsToUseTool()(第 473-956 行)。

管道全景图

用户输入工具调用
        │
        ▼
┌─────────────────────────────────────┐
│ 1. 预过滤                           │
│    1a. 全局 deny 规则检查            │
│    1b. 全局 ask 规则检查             │
│    1c. 工具特定 checkPermissions()   │
│    1d. 工具实现 deny                 │
│    1e. requiresUserInteraction 检查  │
│    1f. 内容特定 ask 规则检查          │
│    1g. 安全检查 (safety check)       │
└─────────────────┬───────────────────┘
                  │ (2a-2b)
                  ▼
┌─────────────────────────────────────┐
│ 2. 权限处理                         │
│    2a. bypassPermissions 模式放行    │
│    2b. 全局 allow 规则放行           │
│    2c. passthrough → ask 转换       │
└─────────────────┬───────────────────┘
                  │ (返回 ask/allow/deny)
                  ▼
┌─────────────────────────────────────┐
│ 3. 模式转换 (在 hasPermissionsToUse) │
│    3a. dontAsk 模式: ask → deny     │
│    3b. Auto 模式:                   │
│        ├ 非分类器可批准的安全检查: 返回 │
│        ├ PowerShell 默认拒绝         │
│        ├ acceptEdits 快速路径        │
│        ├ 安全放行列表                 │
│        └ YOLO 分类器评估              │
│    3c. 无提示模式: hooks → auto-deny│
└─────────────────┬───────────────────┘
                  │
                  ▼
         ┌─────────────────┐
         │ 用户确认对话框    │
         │ (useCanUseTool)  │
         └─────────────────┘

3.1 第 1 步:规则预过滤

hasPermissionsToUseToolInner()(第 1158-1260 行)中执行:

1a. 全局 Deny 规则(第 1171-1181 行):

const denyRule = getDenyRuleForTool(appState.toolPermissionContext, tool)
if (denyRule) { return { behavior: 'deny', message: `Permission to use ${tool.name} has been denied.` } }

1b. 全局 Ask 规则(第 1184-1206 行):检查工具是否在 alwaysAsk 列表中。例外:如果 sandbox 启用且 autoAllowBashIfSandboxed 为 true,sandbox 内运行的 Bash 命令可跳过此检查。

1c. 工具特定权限检查(第 1214-1223 行):

const parsedInput = tool.inputSchema.parse(input)
toolPermissionResult = await tool.checkPermissions(parsedInput, context)

每个工具实现自己的 checkPermissions() 方法。例如 BashTool 实现命令级规则(前缀匹配、通配符匹配)。

1d. 工具实现 Deny(第 1226-1228 行):如果工具的 checkPermissions 返回 deny,直接返回。

1e. 用户交互要求(第 1231-1236 行):如果工具标记为 requiresUserInteraction(),即使是 bypassPermissions 模式也必须询问。

1f. 内容特定 Ask 规则(第 1244-1250 行):如 Bash(npm publish:*) 这样的规则,即使是 bypassPermissions 也必须尊重。

1g. 安全检查(第 1255-1260 行):checkPathSafetyForAutoEdit 返回的安全检查(如 .git/.claude/、shell 配置文件)是 bypass-immune 的。

3.2 第 2 步:权限处理

2a. bypassPermissions 模式(第 1268-1281 行):直接放行所有操作(1a-1g 已过滤的危险操作除外)。

2b. 全局 Allow 规则(第 1284-1297 行):如果工具在 alwaysAllow 列表中,直接放行。

2c. passthrough → ask 转换(第 1300-1310 行):如果 checkPermissions 返回 passthrough,转换为 ask

3.3 第 3 步:模式转换

hasPermissionsToUseTool()(第 473-956 行)中执行:

3a. dontAsk: 将所有 ask 转换为 deny(第 505-518 行)。

3b. Auto 模式: 复杂的分类器驱动审批流程(第 520-926 行):

  1. 非分类器可批准的安全检查 → 保持询问
  2. requiresUserInteraction() 的工具 → 保持询问
  3. 拒绝跟踪检查
  4. PowerShell 默认拒绝(除非 POWERSHELL_AUTO_MODE 特性启用)
  5. acceptEdits 快速路径: 如果当前操作在 acceptEdits 模式下会被批准,直接放行(第 600-655 行)
  6. 安全放行列表: isAutoModeAllowlistedTool() 中的工具(第 660-685 行)
  7. 分类器评估: classifyYoloAction() 执行完整的安全评估(第 692-926 行)

3c. 无提示模式: shouldAvoidPermissionPrompts 为 true 时(后台/headless agent),执行 PermissionRequest hooks,若 hook 未决定则 auto-deny(第 932-952 行)。

3.4 CLI Hook 整合

src/hooks/useCanUseTool.tsx 中,结果经过:

  1. hasPermissionsToUseTool() 管道
  2. 如果 allow → 立即批准,记录分类器批准信息
  3. 如果 deny → 拒绝,记录 auto mode denial,发送通知
  4. 如果 ask → 进入交互式权限处理流程:
    • awaitAutomatedChecksBeforeDialog → 协调器处理
    • Swarm Worker 处理(handleSwarmWorkerPermission
    • 竞争性分类器检查(Bash 命令的投机性分类器评估,peekSpeculativeClassifierCheck
    • 最终 handleInteractivePermission 显示对话框

4. Auto-Mode 分类器

4.1 架构概览

Auto mode 分类器是系统的核心智能审批组件,使用 Claude 模型(默认使用主循环模型,可被 CLAUDE_CODE_AUTO_MODE_MODEL 环境变量或 GrowthBook 配置覆盖)对每个待审批的工具调用进行安全评估。

核心文件:

  • src/utils/permissions/yoloClassifier.ts — 分类器主逻辑、2 阶段 XML 分类器
  • src/utils/permissions/classifierDecision.ts — 安全放行列表
  • src/utils/permissions/classifierShared.ts — 共享解析工具
  • src/utils/permissions/yolo-classifier-prompts/ — 系统提示模板

4.2 两阶段分类器

系统支持三种分类器模式,通过 GrowthBook tengu_auto_mode_config.twoStageClassifier 配置:

模式 说明 适用场景
both (默认) Stage 1 (fast) + Stage 2 (thinking) 平衡速度与准确率
fast 仅 Stage 1,max_tokens=256,最终裁定 追求速度
thinking 仅 Stage 2,跳过 Stage 1 需要深度推理

Stage 1 - 快速判断yoloClassifier.ts:769-858):

  • max_tokens=64(fast-only 模式为 256)
  • stop_sequences=['</block>'] — 一旦输出 </block> 立即停止
  • 后缀:"\nErr on the side of blocking. <block> immediately."
  • 如果 stage 1 判定允许(<block>no</block>),直接返回
  • 如果 stage 1 判定阻止(<block>yes</block>),进入 stage 2

Stage 2 - 深度推理yoloClassifier.ts:861-940):

  • max_tokens=4096
  • 包含 xml_s2_t2 后缀,要求 chain-of-thought 推理
  • 可包含 <thinking> 标签
  • 最终输出 <block>yes/no</block> 和可选的 <reason>...</reason>

4.3 双阶段上下文预算

┌──────────────────────────────────────────────────────┐
│  上下文窗口管理                                       │
├──────────────────────────────────────────────────────┤
│ System Block (cache_control) ← 稳定缓存前缀            │
│   └ 基础提示 + 权限模板 + 用户 allow/deny 规则         │
│                                                       │
│ CLAUDE.md (cache_control) ← 静态用户指令               │
│                                                       │
│ Transcript Entries                                     │
│   ├ User: "..."                                       │
│   ├ Bash ls                                           │
│   ├ User: "..."                                       │
│   └ ...                                              │
│                                                       │
│ Action (cache_control) ← 正在评估的操作                 │
│   └ 只有 system + CLAUDE.md + action 三个 cache_control│
│     block(不超过 API 限制的 4 个)                     │
└──────────────────────────────────────────────────────┘

4.4 评估指标与风险分类

官方数据

  • False-negative rate:17%(即分类器应阻止但未阻止的操作比例)
  • 第三方测试 FNR:70.3%(表明在更复杂场景下分类器有显著漏报)

四种风险类别(来自系统提示模板 auto_mode_system_prompt.txt):

类别 说明 示例
Overeager Behavior 模型过于积极执行未明确请求的操作 未问用户就安装包
Honest Mistakes 模型判断错误导致的非恶意操作 删错了文件
Prompt Injection 提示注入攻击 用户指令中隐藏恶意代码
Model Misalignment 模型与用户意图不一致 执行与任务无关的危险操作

4.5 安全放行列表

文件: src/utils/permissions/classifierDecision.ts:56-94

const SAFE_YOLO_ALLOWLISTED_TOOLS = new Set([
  FILE_READ_TOOL_NAME, GREP_TOOL_NAME, GLOB_TOOL_NAME,
  LSP_TOOL_NAME, TOOL_SEARCH_TOOL_NAME, LIST_MCP_RESOURCES_TOOL_NAME,
  TODO_WRITE_TOOL_NAME, TASK_CREATE_TOOL_NAME, // ... 等
])

注意:isAutoModeAllowlistedTool()(第 96-100 行)当前实现为 return true — 所有工具都跳过分类器。这意味着安全放行列表实际上当前被全局放行取代,所有工具通过 acceptEdits 快速路径评估后再考虑分类器。

但代码中的注释明确说明:

"Does NOT include write/edit tools — those are handled by the acceptEdits fast path"

4.6 提示注入防护

JSONL 转录格式yoloClassifier.ts:412-423):

{"Bash":"ls -la"}        ← JSON 编码,注入无法突破字符串边界
{"user":"forged text"}    ← tool_use 块中的 user 文本不会混淆分类器

传统文本格式:

Bash ls -la
User: forged text

JSONL 格式是注入防护的关键设计:用户输入和工具调用都被 JSON.stringify 编码,因此恶意内容中的换行符和特殊字符不可能突破字符串边界。

4.7 拒绝跟踪 (Denial Tracking)

文件: src/utils/permissions/denialTracking.ts

export const DENIAL_LIMITS = {
  maxConsecutive: 3,   // 连续拒绝 3 次后回退
  maxTotal: 20,        // 总计拒绝 20 次后回退
} as const
  • recordDenial(state): 递增 consecutiveDenials 和 totalDenials
  • recordSuccess(state): 重置 consecutiveDenials 为 0
  • shouldFallbackToPrompting(state): 超出任一阈值时返回 true

超过限制后的行为:

  • CLI 模式:回退到手动询问,包含分类器的拒绝理由
  • headless 模式:抛出 AbortError,终止 agent

5. 规则系统

5.1 规则类型

文件: src/types/permissions.ts:67-79, src/utils/permissions/permissions.ts:238-302

三种规则行为(PermissionBehavior):

行为 效果 适用场景
allow 工具被允许,跳过后续检查 --allowed-tools Bash(ls:*)
deny 工具被拒绝,不执行 --disallowed-tools Bash(rm:*)
ask 强制询问用户 Bash(npm publish:*)

5.2 规则来源

文件: src/types/permissions.ts:54-62

来源 持久化 说明
userSettings ~/.claude/settings.json 全局用户设置
projectSettings .claude/settings.json 项目级别,可提交到 git
localSettings .claude/settings.local.json 项目级别,gitignored
policySettings 企业策略 只读,不可删除
flagSettings 编译期标志 只读
cliArg CLI 参数 --allowed-tools, --disallowed-tools
command 斜杠命令 命令前导块中的规则
session 内存 临时会话规则

规则加载流程(permissionsLoader.ts:120-133):

  1. 如果 allowManagedPermissionRulesOnly 为 true → 只加载 policySettings
  2. 否则加载所有已启用的设置源

5.3 路径模式匹配

文件: src/utils/permissions/filesystem.ts:960-1025

使用 gitignore 风格的 ignore 库进行路径匹配:

export function matchingRuleForInput(
  path: string,
  toolPermissionContext: ToolPermissionContext,
  toolType: 'edit' | 'read',
  behavior: 'allow' | 'deny' | 'ask',
): PermissionRule | null

关键要点

  • 路径被规范化为 POSIX 格式(relativePath 函数,第 170-179 行)
  • 双斜杠前缀 // 表示相对于根 / 的路径
  • 波浪号前缀 ~/ 表示相对于用户主目录的路径
  • 模式 /** 后缀被简化为匹配目录本身及其所有子项
  • 大小写标准化(normalizeCaseForComparison)防止大小写绕过

5.4 路径安全检查

文件: src/utils/permissions/filesystem.ts:620-665

checkPathSafetyForAutoEdit() 检查:

  1. Windows 可疑路径模式hasSuspiciousWindowsPathPattern, filesystem.ts:537-602):

    • NTFS Alternate Data Streams(: 号,仅 Windows/WSL)
    • 8.3 短文件名(~ 后跟数字)
    • 长路径前缀(\\?\, //?/ 等)
    • 尾部点和空格(.git., .claude.
    • DOS 设备名(CON, PRN, AUX 等)
    • 三个连续点(...
    • UNC 路径(\\server\share
  2. Claude 配置文件isClaudeConfigFilePath, filesystem.ts:225-242):

    • settings.json, settings.local.json
    • .claude/commands/, .claude/agents/, .claude/skills/
  3. 危险文件DANGEROUS_FILES, filesystem.ts:57-68):

    • .gitconfig, .gitmodules
    • .bashrc, .bash_profile, .zshrc, .zprofile, .profile
    • .ripgreprc, .mcp.json, .claude.json
  4. 危险目录DANGEROUS_DIRECTORIES, filesystem.ts:74-79):

    • .git, .vscode, .idea, .claude

安全检查也适用于解析后的符号链接路径,防止通过符号链接绕过。

5.5 危险权限检测

文件:

  • src/utils/permissions/permissionSetup.ts:94-285
  • src/utils/permissions/dangerousPatterns.ts

进入 auto 模式时,系统自动检测并剥离(strip)危险权限:

危险 Bash 规则isDangerousBashPermission):

// 完全通配: Bash, Bash(*), Bash() → 允许所有命令 → 危险
// 解释器通配: Bash(python:*) → 允许任意 Python 代码 → 危险
// 包管理器: Bash(npm run:*) → 可执行任意脚本 → 危险

匹配模式列表(dangerousPatterns.ts):

CROSS_PLATFORM_CODE_EXEC = [
  'python', 'python3', 'node', 'deno', 'ruby', 'perl', 'php', 'lua',
  'npx', 'bunx', 'npm run', 'yarn run', 'pnpm run', 'bun run',
  'bash', 'sh', 'ssh',
]
// ant-only: gh, curl, wget, git, kubectl, aws, gcloud, gsutil

危险 PowerShell 规则isDangerousPowerShellPermission, permissionSetup.ts:157-233): 额外包含 iex, invoke-expression, start-process, add-type, new-object 等。

危险 Agent 规则isDangerousTaskPermission): 任何 Agent 工具的 allow 规则都会绕过分类器对子 agent 的评估。

5.6 影子规则检测

文件: src/utils/permissions/shadowedRuleDetection.ts

检测 allow 规则是否被 ask/deny 规则"屏蔽"(即永远无法生效):

// 示例: allow 规则 Bash(ls:*) 被全局 ask 规则 Bash 屏蔽
// 因为 ask 规则在评估顺序中先于 allow 规则
  • Deny 屏蔽: 工具级别的 deny 规则使该工具的所有 allow 规则不可达
  • Ask 屏蔽: 工具级别的 ask 规则使带内容的 allow 规则不可达(总是会先询问)
  • 沙箱例外: 如果 sandbox 启用且 autoAllowBashIfSandboxed 为 true,个人设置的 ask 规则不屏蔽 allow 规则

6. Shell 沙箱

6.1 容器化执行环境

文件: src/utils/sandbox/sandbox-adapter.ts

SandboxManager 提供容器化的命令执行环境,支持:

  • 文件系统隔离: getFsReadConfig() / getFsWriteConfig() 控制可读写的路径
  • 网络隔离: getNetworkRestrictionConfig() 控制网络访问
  • 命令排除: excludedCommands 列表中的命令不被沙箱化

6.2 AutoAllowBashIfSandboxed

文件: src/utils/sandbox/sandbox-adapter.ts:469-472

function isAutoAllowBashIfSandboxed(): boolean {
  const settings = getSettings_DEPRECATED()
  return settings?.sandbox?.autoAllowBashIfSandboxed ?? true  // 默认开启
}

当此选项启用时:

  1. 所有 Bash 命令在沙箱内运行
  2. Bash 命令的权限检查被自动放行(permissions.ts:1189-1193
  3. 命令不在沙箱内运行时(dangerouslyDisableSandbox = trueexcludedCommands),仍遵循正常权限检查

6.3 平台支持

支持通过 enabledPlatforms 配置控制哪些平台启用沙箱:

// src/entrypoints/sandboxTypes.ts:108-112
// 为了 NVIDIA 企业部署,最初仅 macOS 启用沙箱
// Linux/WSL 沙箱支持较新,在扩展前需要更多验证

6.4 Sandbox 写允许列表

在路径验证中(pathValidation.ts:101-123),当沙箱启用时,沙箱配置的写允许列表作为额外的工作目录:

export function isPathInSandboxWriteAllowlist(resolvedPath: string): boolean {
  const { allowOnly, denyWithinAllow } = SandboxManager.getFsWriteConfig()
  // 检查路径是否在 allowOnly 中且不在 denyWithinAllow 中
}

7. 安全相关实现细节

7.1 Protected Paths

文件: src/utils/permissions/filesystem.ts

系统定义了多层保护路径,防止 AI 修改关键配置:

内部可编辑路径checkEditableInternalPath, filesystem.ts:1479-1605)——自动允许编辑:

  • 当前会话的计划文件(isSessionPlanFile
  • 临时目录(isScratchpadPath
  • 模板任务目录(CLAUDE_JOB_DIRfeature('TEMPLATES') 时)
  • Agent 记忆目录(isAgentMemoryPath
  • 自动记忆目录(isAutoMemPath,无覆盖路径时)
  • .claude/launch.json(桌面预览配置)

内部可读路径checkReadableInternalPath, filesystem.ts:1611-1777)——自动允许读取:

  • 会话记忆目录(isSessionMemoryPath
  • 项目目录(isProjectDirPath
  • 计划文件
  • 工具结果目录(getToolResultsDir
  • 临时目录
  • Agent 记忆目录
  • 任务目录(.claude/tasks/
  • 团队目录(.claude/teams/
  • 内置技能参考文件(getBundledSkillsRoot

7.2 Hook 系统

文件: src/utils/hooks.ts:4157-4192

PreToolUse Hooks

executePermissionRequestHooks() 在权限检查流程中调用,提供用户自定义的拦截逻辑:

export async function* executePermissionRequestHooks<ToolInput>(
  toolName: string,
  toolUseID: string,
  toolInput: ToolInput,
  toolUseContext: ToolUseContext,
  permissionMode?: string,
  permissionSuggestions?: PermissionUpdate[],
  signal?: AbortSignal,
  timeoutMs: number = TOOL_HOOK_EXECUTION_TIMEOUT_MS,
  // ...
)

Hook 可以返回:

  • { behavior: 'allow', updatedInput, updatedPermissions } → 放行(含输入修改和权限更新)
  • { behavior: 'deny', message, interrupt } → 拒绝(含可选的中断信号)

使用场景:

  • headless/async agent 不能显示权限提示框时通过 hook 授权
  • CI/CD 环境中的自动化策略执行

PostToolUse Hooks

代码中存在 PostToolUse 钩子类型(在 hook 类型定义中),用于工具执行后的审计、日志记录和副作用处理。

7.3 远程会话权限桥接

文件: src/utils/permissions/permissionSetup.ts:748-758

CLAUDE_CODE_REMOTE 环境变量设置时,运行在远程桥接模式,权限模式受限:

if (
  isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) &&
  !['acceptEdits', 'plan', 'default'].includes(settingsMode)
) {
  // 只有 acceptEdits、plan、default 模式支持 CCR
  logEvent('tengu_ccr_unsupported_default_mode_ignored', { mode: settingsMode })
}

这意味着在远程会话中,bypassPermissionsauto 模式被禁用,增强了对远程连接的安全控制。

7.4 权限解释器

文件: src/utils/permissions/permissionExplainer.ts

在询问用户时,系统可调用 Haiku 模型生成操作的风险评估:

export async function generatePermissionExplanation({
  toolName, toolInput, toolDescription, messages, signal,
}): Promise<PermissionExplanation | null>

输出包含:

  • riskLevel: LOW | MEDIUM | HIGH
  • explanation: 操作说明(1-2 句话)
  • reasoning: 执行此操作的原因
  • risk: 可能的风险(15 字以内)

此功能可通过 permissionExplainerEnabled 配置禁用。

7.5 权限更新的持久化

文件: src/utils/permissions/PermissionUpdate.ts, PermissionUpdateSchema.ts

权限变更(添加规则、删除规则、更改模式、添加目录)通过 permissionRuleParser.ts 进行序列化和反序列化,并写入到对应的设置文件。

export type PermissionUpdate =
  | { type: 'addRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior }
  | { type: 'replaceRules'; destination: ...; rules: ...; behavior: ... }
  | { type: 'removeRules'; destination: ...; rules: ...; behavior: ... }
  | { type: 'setMode'; destination: ...; mode: ExternalPermissionMode }
  | { type: 'addDirectories'; destination: ...; directories: string[] }
  | { type: 'removeDirectories'; destination: ...; directories: string[] }

7.6 特征标识与死代码消除

整个权限系统大量使用 Bun 编译期的 feature() 函数进行条件编译:

Feature Flag 控制的特性 文件
TRANSCRIPT_CLASSIFIER 整个 auto 模式系统(分类器、状态管理、危险权限剥离) 多处
BASH_CLASSIFIER Bash 命令提示词分类器 bashClassifier.ts
POWERSHELL_AUTO_MODE PowerShell 自动模式 yoloClassifier.ts
TEMPLATES 模板任务目录自动编辑 filesystem.ts

这样,外部构建中所有 auto 模式相关代码被完全消除,减小了二进制体积并简化了安全模型。

7.7 拒绝统计与滥用防护

文件: src/utils/permissions/denialTracking.ts

export const DENIAL_LIMITS = {
  maxConsecutive: 3,   // 连续 3 次拒绝 → 回退到手动
  maxTotal: 20,        // 总计 20 次拒绝 → 回退到手动
} as const

拒绝跟踪统计记录在 AppState.denialTracking 中,在 auto 模式下每次分类器判定后更新:

  • 成功(allow)→ recordSuccess() → 重置连续拒绝计数
  • 失败(block)→ recordDenial() → 递增两个计数器
  • 超出阈值 → shouldFallbackToPrompting() → 回退到交互式询问

headless agent 模式下,超出限制直接终止 agent(permissions.ts:1023-1027)。

7.8 CLI 与 Settings 初始化流程

文件: src/utils/permissions/permissionSetup.ts:872-1033

initializeToolPermissionContext() 的完整流程:

  1. 解析 --allowed-tools, --disallowed-tools, --base-tools CLI 参数
  2. 检测 bypassPermissions 模式是否可用(Statsig 门控 + settings 检查)
  3. 从磁盘加载所有权限规则(loadAllPermissionRulesFromDisk
  4. 检测危险和过宽的 Shell 权限
  5. 应用规则到 ToolPermissionContext
  6. 处理工作目录的符号链接(process.env.PWDgetOriginalCwd() 的差异)
  7. 验证并添加附加目录(--add-dir

附录 A:关键文件索引

文件 职责 关键行
src/types/permissions.ts 权限类型定义 16-36 (模式), 54-62 (规则来源), 75-79 (规则), 271-324 (DecisionReason)
src/utils/permissions/PermissionMode.ts 权限模式配置与 UI 展示 42-91 (模式配置), 97-105 (外部模式过滤)
src/utils/permissions/permissions.ts 核心权限检查管道 473-956 (hasPermissionsToUseTool), 1158-1319 (hasPermissionsToUseToolInner), 1060-1156 (checkRuleBasedPermissions)
src/utils/permissions/filesystem.ts 文件系统权限检查 57-68 (危险文件), 74-79 (危险目录), 620-665 (安全检查), 960-1025 (路径匹配), 1479-1605 (内部可编辑路径), 1611-1777 (内部可读路径)
src/utils/permissions/pathValidation.ts 路径验证 101-123 (沙箱写允许), 141-263 (isPathAllowed), 331-367 (危险删除路径), 373-485 (validatePath)
src/utils/permissions/yoloClassifier.ts Auto 模式分类器 711-996 (XML 2 阶段分类器), 1012-1306 (classifyYoloAction), 1484-1495 (formatActionForClassifier)
src/utils/permissions/classifierDecision.ts 分类器决策与放行列表 56-94 (SAFE_YOLO_ALLOWLISTED_TOOLS), 96-100 (isAutoModeAllowlistedTool)
src/utils/permissions/permissionSetup.ts 模式切换、危险权限剥离 94-147 (isDangerousBashPermission), 157-233 (isDangerousPowerShellPermission), 510-553 (stripDangerousPermissionsForAutoMode), 597-646 (transitionPermissionMode), 872-1033 (initializeToolPermissionContext)
src/utils/permissions/permissionsLoader.ts 规则加载与持久化 120-133 (loadAllPermissionRulesFromDisk), 229-296 (addPermissionRulesToSettings)
src/utils/permissions/shellRuleMatching.ts Shell 规则解析与匹配 43-48 (prefix 提取), 90-153 (通配符匹配)
src/utils/permissions/denialTracking.ts 拒绝跟踪 12-15 (DENIAL_LIMITS), 40-44 (shouldFallbackToPrompting)
src/utils/permissions/shadowedRuleDetection.ts 影子规则检测 193-234 (detectUnreachableRules)
src/utils/permissions/dangerousPatterns.ts 危险命令模式 18-42 (CROSS_PLATFORM_CODE_EXEC), 44-80 (DANGEROUS_BASH_PATTERNS)
src/utils/permissions/permissionExplainer.ts 权限解释器 147-250 (generatePermissionExplanation)
src/hooks/useCanUseTool.tsx React 权限 hook 28-203 (useCanUseTool)
src/utils/sandbox/sandbox-adapter.ts 沙箱适配器 469-472 (isAutoAllowBashIfSandboxed)
src/utils/hooks.ts Hook 执行系统 4157-4192 (executePermissionRequestHooks)
src/utils/permissions/bypassPermissionsKillswitch.ts 绕过权限禁用开关 19-47 (checkAndDisableBypassPermissionsIfNeeded), 74-117 (checkAndDisableAutoModeIfNeeded)

附录 B:数据流图(权限检查)

工具调用请求
    │
    ▼
┌──────────────────────────────────────┐
│  hasPermissionsToUseTool()           │
│  src/utils/permissions/permissions.ts│
│                                      │
│  1. 规则预过滤段                       │
│     ├── 全局 deny 规则                │
│     ├── 全局 ask 规则 (sandbox 快速放行)│
│     ├── tool.checkPermissions()       │
│     ├── tool.requiresUserInteraction  │
│     ├── 内容 ask 规则                  │
│     └── 安全检查                       │
│                                      │
│  2. 权限处理段                         │
│     ├── bypassPermissions / plan      │
│     └── 全局 allow 规则                │
└──────────┬───────────────────────────┘
           │
           ▼
┌──────────────────────────────────────┐
│  hasPermissionsToUseTool() — 模式后处理│
│                                      │
│  3. dontAsk → ask→deny               │
│  4. Auto 模式:                       │
│     ├─ acceptEdits 快速路径           │
│     ├─ 放行列表                       │
│     └─ YOLO 分类器评估                 │
│  5. Headless → hooks → auto-deny     │
└──────────┬───────────────────────────┘
           │
           ▼
┌──────────────────────────────────────┐
│  useCanUseTool (React Hook)          │
│  src/hooks/useCanUseTool.tsx         │
│                                      │
│  allow → 立即执行                     │
│  deny → 拒绝 + 通知                   │
│  ask → 交互式处理                      │
│     ├─ 协调器检查                      │
│     ├─ Swarm Worker 转发              │
│     ├─ 投机性分类器 (Bash)            │
│     └─ 用户确认对话框                  │
└──────────────────────────────────────┘

附录:AskUserQuestion 独立问答通道

AskUserQuestion 用于向用户提出多选题(澄清需求、在方案间做选择)。它不再借道权限系统的 ask 流程,而是走一条独立的问答通道,与权限确认队列解耦。

与权限 ask 的区别

维度 权限式 ask(早期实现) 独立问答通道(当前实现)
checkPermissions 返回 behavior: 'ask' behavior: 'allow'
阻塞方式 权限回调回填 answers callawait questionService.ask(...)
本地渲染 toolUseConfirmQueue,由 PermissionRequest 渲染 独立 overlay(QuestionPrompt),与 toolPermissionOverlay 并列
焦点协调 tool-permission 对话框 question 对话框 + useRegisterOverlay('question')

运行时组件

  • src/services/question/questionService.ts — 独立问答通道。ask(questions) 存入 pending: Map<id, ...> 并返回 Promise;reply / reject 解析对应 Promise;通过 EventEmitter 广播 asked / replied / rejected
  • src/tools/AskUserQuestionTool/AskUserQuestionTool.tsxcallawait questionService.ask(...),再把结果按题映射为 answers
  • src/components/question/QuestionPrompt.tsx — 独立 overlay,复用 QuestionView / SubmitQuestionsView / use-multiple-choice-state 渲染,并用 useKeybindingsTabs 上下文)绑定多题切换。
  • src/screens/REPL.tsx — 订阅 asked 弹出 overlay;订阅 replied / rejected 关闭 overlay。

桥接 / CCR 远程转发

桥接(BRIDGE_MODE)连接时,asked 事件会同时把问题作为 can_use_tool control_request 转发给远程用户(claude.ai),与本地 overlay 竞速:

  • 远程 allow 且带 updatedInput.answers → 按题映射后 questionService.reply(...);通用 allow(无 answers)降级为每题选第一个选项。
  • 远程 denyquestionService.reject(...)
  • 任一端先应答,都会清掉本地 overlay 并 cancelRequest 另一端的 prompt,避免残留。

注:因 checkPermissions 现返回 allow,AskUserQuestion 不再进入 handleInteractivePermission,故不会与桥接路径重复转发。