# 权限系统完全解析

权限系统是 Claude Code 安全模型的核心中枢——每一次工具调用（"工具调用"指 AI 请求执行某个具体操作，比如读文件、执行命令、编辑代码）都必须经过它的裁决。它决定了 AI 能做什么、不能做什么、以及什么需要人类确认。本章将带你走完权限判断链路的完整三阶段（拒绝优先 → 模式判断 → 默认询问），理解 6 种权限模式的设计意图，以及 Auto 模式下 AI 分类器如何替代人类决策。我们还将深入分析一个极具行业价值的设计：远程权限关闭开关（killswitch）。

> **源码位置**：`src/utils/permissions/`（24 个文件）、`src/hooks/toolPermission/`、`src/tools.ts`

> 💡 **通俗理解**：权限系统就像小区门禁系统——持万能钥匙的人（bypassPermissions）自由进出，普通访客（default 模式）需要在门卫处登记确认，快递员（auto 模式）由 AI 门卫判断是否放行。某些区域（bypass-immune 路径，如配电房和监控室）即使持万能钥匙也不能进入。而物业公司总部（Anthropic 服务器）保留了远程锁门的权力——一旦发现安全隐患，可以远程收回所有万能钥匙。

> 🌍 **行业背景**：AI 代码工具的权限控制是一个快速演进的领域，不同产品体现了截然不同的安全哲学。
>
> | 工具 | 权限模型 | 安全哲学 | 关键差异 |
> |------|---------|---------|---------|
> | **Claude Code** | 多步判断链 + AI 分类器 + 多来源规则层级（userSettings / projectSettings / localSettings / flagSettings / policySettings / cliArg / command / session 共 8 个来源，见下文 `PERMISSION_RULE_SOURCES`） | 软判断 + 细权限 | 独立 CLI，需自建全部安全层；AI 分类器做动态判断 |
> | **Cursor** | 信任工作区 + tool-level approval + yolo allowlist/blocklist | IDE 沙箱兜底 | 依赖 VS Code extension sandbox 作为底层安全保证 |
> | **Codex（OpenAI）** | 三档模式（suggest / auto-edit / full-auto）+ 沙箱化执行环境 | 硬隔离 + 宽权限 | full-auto 档依赖宿主侧沙箱与网络隔离做底层兜底（具体实现以 OpenAI 官方文档为准，不同宿主平台差异较大），底层以 Rust 重写带来内存安全 |
> | **Aider** | `--auto-commits` + Architect 模式（推理/编辑分离） | 信任用户 | AST 级 Repo Map 降低上下文风险，但不提供 AI 分类器 |
> | **GitHub Copilot** | Agent Mode 全面 GA + 企业级 MCP 注册表 | 合规+效率平衡 | 内置 Explore/Plan/Task 专职智能体，深度融合企业安全防火墙策略 |
>
> 最值得注意的对比是 **Claude Code vs Codex** 的安全哲学分歧：Codex 的 full-auto 模式依赖 OS 级网络隔离做硬隔离——即使 AI 做了错误决策，系统边界也能兜底；而 Claude Code 的 auto 模式依赖 AI 分类器做软判断，没有硬隔离层，但换来了更细粒度的权限控制和更低的环境要求。这是 **硬隔离+宽权限 vs 软判断+细权限** 的经典 trade-off。

---

## 系统概览

权限系统（`src/utils/permissions/`）决定每个工具调用是否被允许。它是 Claude Code 安全模型的核心，有约 1500 行代码。

**核心入口**：`hasPermissionsToUseTool(tool, input, context)` — 每次工具调用前必须经过这个函数。

**输出类型**（每次权限判断只会给出以下三种结果之一，看注释即可理解含义）：
```typescript
type PermissionResult =
  | { type: 'allow' }       // 允许，不显示确认框
  | { type: 'ask'; reason }  // 需要用户确认
  | { type: 'deny'; reason } // 拒绝，显示错误
```

---

## 六种权限模式

```typescript
// PermissionMode.ts
type PermissionMode =
  | 'default'            // 标准模式：需要确认时弹窗
  | 'plan'               // 计划模式：只读操作，不允许写操作
  | 'acceptEdits'        // 自动接受文件编辑，其他操作仍需确认
  | 'bypassPermissions'  // 绕过所有权限（除 bypass-immune 外）
  | 'dontAsk'            // 不询问，但仍遵守 always-allow/always-deny 规则
  | 'auto'               // 自动模式：AI 分类器替代用户确认
```

以上 6 种是用户层面会接触到的模式（对应 `src/types/permissions.ts` 中的 `EXTERNAL_PERMISSION_MODES` 5 项 `acceptEdits / bypassPermissions / default / dontAsk / plan` 加上受 `TRANSCRIPT_CLASSIFIER` feature gate 控制的 `auto`）。另外类型系统中还存在一个内部用的 `bubble` 模式，仅用于 transcript classifier 的中间态，用户不会直接选择，此处不展开。

其中 `plan`、`acceptEdits`、`bypassPermissions`、`auto` 是用户可直接选择的四种模式，`dontAsk` 是系统内部子 Agent（"子 Agent"即 AI 自动创建的分身，用于并行处理子任务）使用的模式，`default` 是未指定时的默认值。

**进入方式**：
- `plan`：通过 `/plan` 命令或 `--plan` CLI 标志
- `acceptEdits`：`acceptEditsContext` 配置，或 IDE 集成模式
- `bypassPermissions`：`--dangerously-skip-permissions` 标志（需要非生产环境），**但受远程 killswitch 控制**——即使传了此标志，如果 Anthropic 激活了 `tengu_disable_bypass_permissions_mode` 开关，bypass 模式也会被降级为 default 模式（详见下文"远程权限关闭开关"一节）
- `dontAsk`：内部子 Agent 使用（如 hook agent）——不弹窗但仍遵守规则
- `auto`：`--auto` 标志，或 Yolo 模式

---

## 权限判断链路（三阶段）

`hasPermissionsToUseToolInner()` 实现了一条多步判断链路。源码中的步骤编号为 1a-1g、2a-2b、3，共计 **10 个子步骤**，分属三个逻辑阶段。多步权限检查链本身是企业软件的标准做法——就像进入大型医院需要经过测温、挂号、分诊等多个检查站，每个检查站负责一项具体的安全判断。Claude Code 的真正创新在于 **将 AI 分类器嵌入权限链**（让 AI 自己判断操作是否安全）以及 **远程 killswitch 控制**（Anthropic 总部可以远程关闭某些权限）。

> 💡 **通俗理解**：这条判断链的核心逻辑是"先查黑名单，再查白名单，都没匹配就问人"——和你进大楼的安检流程一样：先看你是不是被拉黑的人（deny），再看你有没有提前登记的通行证（allow），都没有就请保安确认。

### 阶段一：拒绝与安全检查（步骤 1a - 1g）

**1a. 全局拒绝规则**：检查所有来源的 `deny` 规则，命中即拒绝。

**1b. 全局询问规则**：检查 `ask` 规则——即使 auto 模式也会强制弹窗询问。这是管理员对特定工具"强制人工审批"的手段。

**1c. 工具自检（checkPermissions）**：调用工具自身的 `checkPermissions()` 方法。每个工具可以基于输入参数做额外检查：
- FileEditTool：检查路径是否在 `.git/` 等敏感目录
- BashTool：检查命令模式（bashPermissions.ts）
- WebFetchTool：检查 URL 是否在允许范围内

**1d. 工具自检返回 deny**：如果工具自身判定拒绝，立即拒绝。

**1e. 需要用户交互**：检查工具是否标记为 `requiresUserInteraction`（如权限对话框本身），是则强制询问。

**1f. 内容特定 ask 规则**：用户显式配置的内容级 ask 规则（如 `Bash(npm publish:*)`）在 bypass 模式下仍然生效。这是一个关键的安全设计：即使用户开启了 `--dangerously-skip-permissions`，他自己配置的 ask 规则仍然会弹窗。

**1g. Bypass-Immune 安全检查（关键！）**：某些路径和操作在**任何模式**下都必须询问：
- `.git/config`、`.git/hooks/`——修改后可以注入恶意代码到 git 操作中
- `.claude/settings.json`、`.claude/settings.local.json`——修改后可以篡改 Claude Code 自身的权限规则
- 系统敏感目录

这些路径的共同特征是：**修改它们能影响 Claude Code 自身的安全行为**（元配置文件）。这一层是硬编码的，不受任何配置控制——它守护的是权限系统自身不被绕过。

### 阶段二：模式判断（步骤 2a - 2b）

**2a. bypassPermissions 模式**：如果当前是 `bypassPermissions` 模式，直接允许（前提是通过了阶段一所有安全检查）。

**2b. 全局允许规则**：检查 `alwaysAllowRules` 里的 `allow` 规则。规则按来源优先级排序，**第一个匹配即停止**（first-match-wins）。

### 阶段三：默认行为（步骤 3）

如果以上都没有明确结果，返回 `{ type: 'ask' }`——在 default 模式下弹窗询问用户，在 auto 模式下转交 AI 分类器。

---

## 权限规则优先级与冲突解决

当多个来源都有匹配规则且结果矛盾时（比如 project 级别 allow、user 级别 deny），Claude Code 采用 **first-source-wins** 策略——按固定的来源优先级顺序遍历，第一个匹配的规则即为最终结果。

源码中 `PERMISSION_RULE_SOURCES` 的遍历顺序为：

```
userSettings → projectSettings → localSettings → flagSettings → policySettings → cliArg → command → session
（用户设置 → 项目设置   → 本地设置   → 远程开关  → 企业策略    → 命令行参数 → 命令级 → 本次会话）
```

> 💡 举个例子：同一类规则（比如都是 allow）冲突时，系统会按从左到右的顺序查找——如果 userSettings 里写了 `allow Read`、projectSettings 里写了 `allow Read(public/**)`，系统先命中 userSettings 的 allow 就停下来。注意这条顺序只对"同类型规则"生效，deny 和 allow 是分开检查的（详见下方）。

但 deny 规则和 allow 规则是**分开检查**的，且 deny 检查在 allow 之前（阶段一的步骤 1a 先于阶段二的步骤 2b）。这意味着：

- **deny 绝对优先于 allow**：任何来源的 deny 规则都会在 allow 规则被检查之前生效
- **同类型规则内部**：按 `PERMISSION_RULE_SOURCES` 顺序，第一个匹配的来源获胜
- **session 级别规则**排在遍历顺序的最后，但由于用户的临时决定通常是最新的意图表达，系统在实践中通过权限弹窗的"仅本次"选项将规则添加到 session 级别

> 💡 **通俗理解**：这就像公司的审批流程——"不准做"（deny）永远比"可以做"（allow）优先级高。先查黑名单，再查白名单。黑名单上有你的名字，白名单再怎么写也没用。

对于 `policy`（企业策略）级别的规则，虽然在遍历顺序中不是最先，但它有一个特殊属性：**不可被用户覆盖**。企业管理员通过 `managed-settings.json` 或远程 API 设定的 deny 规则，用户无法通过任何本地配置解除。这体现了 Claude Code 同时服务个人开发者和企业客户的产品定位——个人用户享有高度自由，企业管理员保留铁律权。

---

## 远程权限关闭开关（bypassPermissionsKillswitch）

这是全章最具行业价值的设计，值得深入分析。

### 机制解析

`bypassPermissionsKillswitch.ts` 实现了一个远程控制开关：即使用户传了 `--dangerously-skip-permissions` 启动 Claude Code，如果 Anthropic 服务端激活了 killswitch，bypass 模式也会被**强制降级为 default 模式**。

```typescript
// bypassPermissionsKillswitch.ts — 核心逻辑
export async function checkAndDisableBypassPermissionsIfNeeded(
  toolPermissionContext, setAppState
): Promise<void> {
  // 仅在首次查询前执行一次
  if (bypassPermissionsCheckRan) return;
  bypassPermissionsCheckRan = true;

  const shouldDisable = await shouldDisableBypassPermissions();
  if (!shouldDisable) return;

  // 强制降级：mode → default, isBypassPermissionsModeAvailable → false
  setAppState(prev => ({
    ...prev,
    toolPermissionContext: createDisabledBypassPermissionsContext(
      prev.toolPermissionContext,
    ),
  }));
}
```

技术实现细节：

1. **远程配置服务**：通过 Statsig/GrowthBook 的 feature gate `tengu_disable_bypass_permissions_mode` 控制。这不是一个简单的 HTTP 请求，而是利用了 GrowthBook SDK 的**缓存 + 异步刷新**机制——本地有缓存时走缓存（毫秒级），没有缓存时异步请求远程配置。
2. **执行时机**：在用户发出第一条查询之前检查一次（`bypassPermissionsCheckRan` 标志保证只执行一次）。登录状态变更后（`/login` 命令）会重置标志，重新检查——因为不同组织可能有不同的 killswitch 状态。
3. **降级行为**：不是直接禁用工具，而是将 `bypassPermissions` 模式降级为 `default` 模式，同时将 `isBypassPermissionsModeAvailable` 设为 `false`，防止用户通过模式切换重新进入。
4. **离线行为**：当 GrowthBook 不可用（无网络）且没有本地缓存时，`checkSecurityRestrictionGate` 返回 `false`——即**不禁用** bypass 模式。这是一个"fail-open"设计：离线时宁可让用户保留权限，也不阻塞工作。

### 安全哲学：CLI 工具应该有多少"打电话回家"的能力？

这个设计触及了一个深层的工程哲学问题。一个安装在用户本地的 CLI 工具，通过远程开关控制本地的最高权限模式——这意味着 Anthropic 保留了在紧急情况下（如发现 0-day 漏洞、模型被越狱利用）**远程锁定所有用户的 bypass 模式**的能力。

这不是 Anthropic 的独创。行业中有多个先例：

| 机制 | 产品 | 行为 |
|------|------|------|
| SafeBrowsing | Chrome | 远程推送恶意网站黑名单，浏览器本地拦截 |
| Extension Kill Switch | VS Code | 微软可以远程禁用有安全问题的扩展 |
| App Revocation | iOS/Android | 苹果/谷歌可以远程撤销已安装应用的签名 |
| **bypassPermissionsKillswitch** | **Claude Code** | **Anthropic 可以远程禁用本地的 bypass 模式** |

但 Claude Code 的实现有一个关键区别：它是 **fail-open** 的（离线时不禁用），而上述大多数安全机制是 **fail-closed** 的（不确定时倾向于拒绝）。这个选择反映了 Anthropic 在 **安全性 vs 用户自主权** 之间的权衡——对于一个面向专业开发者的 CLI 工具，阻塞用户工作的代价比安全风险更高。

> 💡 **通俗理解**：这就像汽车厂商在你的车里装了远程锁止系统——如果他们发现你的车型有严重安全缺陷（比如刹车可能失灵），可以远程限制你的最高时速。你仍然能开车（default 模式），但不能飙车（bypass 模式）了。但如果你在没有手机信号的荒野里开车（离线），这个限制不会生效——因为把你困在荒野里比让你开快车更危险。

---

## Auto 模式详解

当 `mode === 'auto'` 时，阶段三的默认询问不是弹窗，而是 AI 分类器。

### 两条真正的快路径 + 分类器路径

"快路径"意指在进入 AI 分类器之前就做出决策、省掉模型推理开销。严格说**只有两条**真正的快路径：

**快路径 1：acceptEdits 检查**
如果工具是文件编辑类（FileEditTool 等），直接允许（不调用 AI 分类器）。

**快路径 2：工具白名单**
只读工具（Read、Glob、Grep 等）直接允许。

### 分类器路径：classifyYoloAction

如果两条快路径都未命中，才进入分类器路径——调用 AI 分类器，使用小模型（Haiku）做权限判断。这条路径仍然涉及模型推理，本质上不是"快路径"（延迟通常在数十到数百毫秒），但对于 auto 模式是比弹出人工确认更"快"的选择。

### Iron Gate（铁门）规则

> 📚 **课程关联**：Iron Gate 的设计是**分布式系统**课程中"断路器模式（Circuit Breaker）"的经典应用——当下游服务（这里是 AI 分类器）连续失败到达阈值时，自动切换到备用路径（回退到人工确认），防止级联故障。这也与**计算机网络**中 TCP 的拥塞控制类似：连续丢包后降速重传。

```typescript
// denialTracking.ts
const DENIAL_LIMITS = {
  maxConsecutive: 3,   // 连续 3 次拒绝
  maxTotal: 20,        // 累计 20 次拒绝
}
```

如果分类器连续 3 次或累计 20 次拒绝了工具调用，系统自动回退到 `default` 模式，不管用户是否开启了 auto 模式。这防止了 AI 在自动模式下被无限锁死（例如因为 system prompt 注入攻击导致分类器连续拒绝一切）。

---

## 权限规则格式

允许规则支持通配符匹配：

```
Bash(git *)         # 允许所有以 git 开头的 bash 命令
Read(src/*)         # 允许读取 src/ 下的所有文件
Edit                # 允许所有文件编辑
Bash                # 允许所有 bash 命令（危险！）
```

**持久化写入位置**（按用户选择从高到低，仅列出 3 个可写入的磁盘位置）：
1. `session`：本次会话动态添加（内存中，不写磁盘，重启后消失）
2. `local`：`.claude/settings.local.json`（不提交到 git）
3. `project`：`.claude/settings.json`（可提交到 git）
4. `user`：`~/.claude/settings.json`（全局用户级）

> **完整的规则来源优先级顺序**（first-match-wins 遍历）见上方"权限规则优先级与冲突解决"一节——共 **8 项**（`PERMISSION_RULE_SOURCES` = `SETTING_SOURCES` 5 项 + `cliArg` / `command` / `session` 3 项）。此处列出的 4 个只是**用户交互可写入磁盘的位置**，与完整遍历顺序不是同一概念。企业策略层（`policy`）不在此列是因为它由管理员通过 `managed-settings.json`/远程 API 写入，不受用户权限弹窗影响，但在冲突解决遍历中位于 SETTING_SOURCES 末端且**不可被下游覆盖**。

---

## 权限更新（PermissionUpdate）

用户在权限弹窗里做出决定后，结果通过 `applyPermissionUpdate()` 和 `persistPermissionUpdate()` 持久化：

- **"仅本次"**：添加到 session 级别规则（不写磁盘）
- **"本项目"**：写入 `.claude/settings.json`
- **"本用户"**：写入 `~/.claude/settings.json`

---

## 权限系统 vs MCP 工具

MCP 服务器提供的工具走同样的权限系统。工具名格式 `mcp__server__toolname` 可以作为权限规则的匹配目标：

```json
{
  "allow": ["mcp__my-server__read_file"]
}
```

---

## 关键文件

| 文件 | 内容 |
|------|------|
| `src/utils/permissions/permissions.ts` | 核心逻辑，约 1500 行 |
| `src/utils/permissions/PermissionMode.ts` | 6 种权限模式类型定义 |
| `src/utils/permissions/denialTracking.ts` | Iron gate 的拒绝计数逻辑 |
| `src/utils/permissions/PermissionResult.ts` | PermissionResult 类型 |
| `src/utils/permissions/PermissionUpdate.ts` | 权限更新的应用和持久化 |
| `src/utils/permissions/permissionRuleParser.ts` | 规则字符串解析（Bash(git *)等） |
| `src/utils/permissions/permissionSetup.ts` | 权限上下文初始化 |
| `src/utils/permissions/bypassPermissionsKillswitch.ts` | bypassPermissions 模式的远程关闭开关 |
| `src/hooks/toolPermission/` | 工具权限 hook 集成 |
| `src/tools.ts` | 工具注册与权限声明 |

---

## 代码落点

- `permissions.ts`，第 1158 行：`hasPermissionsToUseToolInner()`——三阶段权限判断链路完整实现
- `permissions.ts`，第 473 行：`hasPermissionsToUseTool()`——外层函数，处理 dontAsk/auto/headless
- `permissions.ts`，约 850 行：`classifyYoloAction()`——auto 模式 AI 分类器
- `src/utils/permissions/denialTracking.ts`，第 12 行：`DENIAL_LIMITS` 常量定义
- `src/types/permissions.ts`，第 29 行：`PermissionMode` 类型定义（`src/utils/permissions/PermissionMode.ts` 负责对外 re-export，不含原始定义）

---

## 设计取舍与批判分析

### 判断链路的复杂性代价

权限判断链路涉及 10 个子步骤（三个逻辑阶段），新贡献者理解完整链路的学习成本很高。任何一步的顺序变动都可能引入安全漏洞——比如如果 bypass-immune 检查（步骤 1g）被不小心移到了 bypassPermissions 判断（步骤 2a）之后，`.git/hooks/` 等路径就失去了保护。这种"顺序敏感"的设计在可维护性上是一把双刃剑。但也应该看到，多步骤的权限检查链是企业级安全系统的标准做法——Spring Security 的 FilterChain 有 15+ 个过滤器。Claude Code 的真正复杂性不在于步骤数量，而在于将 AI 判断嵌入了传统上纯规则驱动的权限链。

### Auto 模式的 AI 分类器：创新还是隐患？

`classifyYoloAction` 使用小模型（Haiku）做权限判断——用一个不同的模型来审查另一个模型的行为，这触及了 AI 安全中的"正交性"原则：审查者和被审查者的独立性。用小模型的好处是延迟低、成本低；代价是准确率有上限。Iron Gate 的 3 次/20 次拒绝阈值是硬编码的，无法根据项目风险等级调整——高安全场景可能需要更保守的阈值。

作为对比：Codex 的 full-auto 模式完全不需要 AI 分类器，因为它有 OS 级网络隔离做硬隔离兜底；Cursor 的 `.mdc` 条件规则引擎则走了另一条路——通过 globs 匹配特定文件类型触发不同策略，结合 Background Agents 的 VM 级隔离。三种方案代表了三种不同的安全哲学：硬隔离（Codex）、软判断（Claude Code）、条件规则+VM 隔离（Cursor）。

### 权限规则的通配符匹配过于简单

`Bash(git *)` 这种前缀匹配无法表达"允许 git push 但禁止 git push --force"这类否定条件。缺乏正则或 glob 否定语法限制了规则的精细度，用户可能被迫在"过于宽松"和"过于频繁的确认弹窗"之间做选择。这可能是刻意的简化而非工程债务——如果引入否定规则（如 `Bash(!git push --force)`），规则之间的冲突解决会变得显著复杂，用户配置出错的风险也更高。

### 远程 Killswitch 的信任问题

`bypassPermissionsKillswitch` 让 Anthropic 保留了远程控制本地权限的能力，这是一个"安全性 vs 用户自主权"的经典张力。目前用户无法 opt-out 这个机制（除非离线使用），且没有公开文档说明这个能力的存在。对比之下，Chrome 的 SafeBrowsing 允许用户关闭，VS Code 的 extension kill switch 有公开的事件通知机制。未来 Anthropic 可能需要在透明度上做更多工作，以维护专业开发者社区的信任。

### 权限疲劳（Permission Fatigue）

Android 和 iOS 的权限弹窗历史已经证明，过于频繁的权限确认会导致用户无脑点"允许"。Claude Code 的 auto 模式本质上是对权限疲劳的技术回应——用 AI 替代人类做决策。但这引入了新的风险：用户对 AI 决策的过度信任。Cursor 的 yolo mode、Codex 的 full-auto 都在用不同方式应对同一个问题，目前没有哪种方案被证明是最优解。
