# 安全与权限系统深度分析 > 本文基于 Codev (Claude Code) 源代码,深入分析其安全架构与权限子系统。 > 版本参考:commit `835ff5a` / `cdb3bdd` --- ## 1. 设计哲学 ### 1.1 Deny-First 原则 系统中所有权限检查的**默认行为是拒绝**。未在规则中明确允许的操作,最终都会向用户发起询问或直接被拒绝。这一原则贯穿整个权限管道(Authorization Pipeline),体现在: - `PermissionResult` 的默认行为是 `ask`(询问用户),而非 `allow` - 工具实现的 `checkPermissions()` 方法若返回 `passthrough`,上层会将其转换为 `ask`(`src/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` ```typescript default: { title: 'Default', shortTitle: 'Default', symbol: '', color: 'text', external: 'default', } ``` - **行为**: 每一步操作都需要用户确认 - **自动批准**: 无 - **询问**: 所有工具调用 - **适用场景**: 新项目、不信任 AI 操作时 - **风险**: 最低,但效率也最低 ### 2.2 AcceptEdits(接受编辑模式) **文件**: `src/utils/permissions/PermissionMode.ts:59-65` ```typescript 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` ```typescript 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` ```typescript ...(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` ```typescript dontAsk: { title: "Don't Ask", shortTitle: 'DontAsk', symbol: '⏵⏵', color: 'error', external: 'dontAsk', } ``` - **行为**: 将所有 `ask` 决策转换为 `deny` - **转换**: `src/utils/permissions/permissions.ts:505-518` ```typescript 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:104` 中 `process.env.USER_TYPE !== 'ant'` 时被排除) - **文档**: 无外部可用信息 --- ## 3. 授权管道 (Authorization Pipeline) 核心实现位于 `src/utils/permissions/permissions.ts` 的 `hasPermissionsToUseToolInner()` 函数(第 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 行): ```typescript 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 行): ```typescript 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=['']` — 一旦输出 `` 立即停止 - 后缀:`"\nErr on the side of blocking. immediately."` - 如果 stage 1 判定允许(`no`),直接返回 - 如果 stage 1 判定阻止(`yes`),进入 stage 2 **Stage 2 - 深度推理**(`yoloClassifier.ts:861-940`): - `max_tokens=4096` - 包含 `xml_s2_t2` 后缀,要求 chain-of-thought 推理 - 可包含 `` 标签 - 最终输出 `yes/no` 和可选的 `...` ### 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` ```typescript 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` ```typescript 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` 库进行路径匹配: ```typescript 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`): ```typescript // 完全通配: Bash, Bash(*), Bash() → 允许所有命令 → 危险 // 解释器通配: Bash(python:*) → 允许任意 Python 代码 → 危险 // 包管理器: Bash(npm run:*) → 可执行任意脚本 → 危险 ``` 匹配模式列表(`dangerousPatterns.ts`): ```typescript 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 规则"屏蔽"(即永远无法生效): ```typescript // 示例: 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` ```typescript function isAutoAllowBashIfSandboxed(): boolean { const settings = getSettings_DEPRECATED() return settings?.sandbox?.autoAllowBashIfSandboxed ?? true // 默认开启 } ``` 当此选项启用时: 1. 所有 Bash 命令在沙箱内运行 2. Bash 命令的权限检查被自动放行(`permissions.ts:1189-1193`) 3. 命令不在沙箱内运行时(`dangerouslyDisableSandbox = true` 或 `excludedCommands`),仍遵循正常权限检查 ### 6.3 平台支持 支持通过 `enabledPlatforms` 配置控制哪些平台启用沙箱: ```typescript // src/entrypoints/sandboxTypes.ts:108-112 // 为了 NVIDIA 企业部署,最初仅 macOS 启用沙箱 // Linux/WSL 沙箱支持较新,在扩展前需要更多验证 ``` ### 6.4 Sandbox 写允许列表 在路径验证中(`pathValidation.ts:101-123`),当沙箱启用时,沙箱配置的写允许列表作为额外的工作目录: ```typescript 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_DIR`,`feature('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()` 在权限检查流程中调用,提供用户自定义的拦截逻辑: ```typescript export async function* executePermissionRequestHooks( 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` 环境变量设置时,运行在远程桥接模式,权限模式受限: ```typescript 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 }) } ``` 这意味着在远程会话中,`bypassPermissions` 和 `auto` 模式被禁用,增强了对远程连接的安全控制。 ### 7.4 权限解释器 **文件**: `src/utils/permissions/permissionExplainer.ts` 在询问用户时,系统可调用 Haiku 模型生成操作的风险评估: ```typescript export async function generatePermissionExplanation({ toolName, toolInput, toolDescription, messages, signal, }): Promise ``` 输出包含: - `riskLevel`: `LOW` | `MEDIUM` | `HIGH` - `explanation`: 操作说明(1-2 句话) - `reasoning`: 执行此操作的原因 - `risk`: 可能的风险(15 字以内) 此功能可通过 `permissionExplainerEnabled` 配置禁用。 ### 7.5 权限更新的持久化 **文件**: `src/utils/permissions/PermissionUpdate.ts`, `PermissionUpdateSchema.ts` 权限变更(添加规则、删除规则、更改模式、添加目录)通过 `permissionRuleParser.ts` 进行序列化和反序列化,并写入到对应的设置文件。 ```typescript 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` ```typescript 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.PWD` 与 `getOriginalCwd()` 的差异) 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` | `call` 内 `await questionService.ask(...)` | | 本地渲染 | 进 `toolUseConfirmQueue`,由 `PermissionRequest` 渲染 | 独立 overlay(`QuestionPrompt`),与 `toolPermissionOverlay` 并列 | | 焦点协调 | `tool-permission` 对话框 | `question` 对话框 + `useRegisterOverlay('question')` | ### 运行时组件 - **`src/services/question/questionService.ts`** — 独立问答通道。`ask(questions)` 存入 `pending: Map` 并返回 Promise;`reply` / `reject` 解析对应 Promise;通过 EventEmitter 广播 `asked` / `replied` / `rejected`。 - **`src/tools/AskUserQuestionTool/AskUserQuestionTool.tsx`** — `call` 内 `await questionService.ask(...)`,再把结果按题映射为 `answers`。 - **`src/components/question/QuestionPrompt.tsx`** — 独立 overlay,复用 `QuestionView` / `SubmitQuestionsView` / `use-multiple-choice-state` 渲染,并用 `useKeybindings`(`Tabs` 上下文)绑定多题切换。 - **`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)降级为每题选第一个选项。 - 远程 `deny` → `questionService.reject(...)`。 - 任一端先应答,都会清掉本地 overlay 并 `cancelRequest` 另一端的 prompt,避免残留。 > 注:因 `checkPermissions` 现返回 `allow`,AskUserQuestion 不再进入 `handleInteractivePermission`,故不会与桥接路径重复转发。