# 权限系统是怎么在灵活性和安全性之间走钢丝的？

AI 工具的权限管理是一个两难困境：太严格则效率低下，太宽松则安全堪忧。Claude Code 用一个 10 步状态机、6 种权限模式和 8 个规则来源，构建了一个既能全自动运行、又能保护关键资源不被篡改的权限体系。本章深入拆解这套"走钢丝"的设计，帮助你理解为什么 `bypassPermissions` 模式下仍有某些操作无法绕过。

> 💡 **通俗理解**：就像小区门禁——业主刷卡自动开门，外卖小哥扫码验证，陌生人需要业主确认。

### 🌍 行业背景：AI 编程工具的权限与安全模型

AI 代码工具的权限管理是一个活跃的设计难题，各家方案反映了不同的安全哲学：

- **Cursor**：在推出 Background Agents 后，权限模型有了显著进化。本地编辑仍通过 diff 预览由用户逐个 Accept/Reject，但云端后台智能体在隔离 VM 中运行，拥有更宽松的执行权限——其安全性靠 VM 级别的环境隔离保证，而非逐步的人工审批。此外，`.cursor/rules/` 的 `.mdc` 条件规则引擎可通过 globs 精确匹配特定文件类型来触发不同的权限策略。
- **Aider**：默认自动应用代码编辑（`--auto-commits`），不做权限检查。用户可以通过 `--no-auto-commits` 关闭自动提交，但没有细粒度的工具级权限控制。安全哲学是"信任用户在正确的环境中运行"。
- **Codex（OpenAI）**：提供三种模式——`suggest`（只建议不执行）、`auto-edit`（自动编辑但命令需确认）、`full-auto`（全自动），同时实现了操作系统级别的网络出口限制（OS-level egress rules），取代了早期脆弱的环境变量控制。安全粒度比 Aider 更细致，但不如 Claude Code 有 10 步状态机和安全免疫路径。
- **Windsurf**：通过 IDE 集成的 diff 视图和终端确认提供权限控制，Cascade Engine 的连续状态感知能够追踪开发者的操作轨迹，在预测性编辑中内置了一定的安全判断。
- **Google 的 AI 编程工具（产品名以官方为准）**：通过 Allow List 和 Deny List 实施严格的环境权限控制，其"Artifacts（中间产物）"机制要求在实际修改文件前产出可审查的实现计划书，是一种前置审批的安全哲学。
- **Docker 沙箱方案**（Codex、部分 Aider 配置）：用容器隔离替代权限检查——AI 在沙箱中可以做任何事，但沙箱外的系统不受影响。这是与 Claude Code 截然不同的安全路线。

Claude Code 的权限系统在几个方面做了独特的工程选择：(1) **安全免疫路径**——即使在最高权限模式下，`.git/`、`.claude/` 等路径仍受保护，这在同类工具中不常见；(2) **auto 模式用 AI 判断权限**——用分类器替代人工审批，是一个有争议但实用的创新；(3) **8 层规则来源的优先级体系**——支持企业级部署场景（MDM 策略覆盖本地设置），其他工具多数不考虑企业管理需求。整体而言，Claude Code 的权限系统在同类工具中属于复杂度和细粒度最高的实现之一。

---

## 问题

Claude Code 有一个看起来矛盾的设计：它既有 `bypassPermissions` 模式（跳过所有权限检查），又有某些路径永远无法被绕过的安全机制。既要让自动化场景能流畅运行，又要防止 AI 修改 `.git/` 或配置文件。这两件事如何共存？

---

## 你可能以为……

你可能以为权限系统就是个简单的"开关"——要么开启权限检查，要么全部关闭。或者你可能以为权限是单层的——一个总开关决定一切。

实际上权限系统是一个有 10 个步骤的状态机，不同的"安全等级"在不同的步骤被处理，有些步骤甚至对最高权限模式免疫。

---

## 实际上是这样的

### 核心：10 步权限状态机

> **[图表预留 2.5-A]**：权限十步状态机流程图（从"工具调用请求"到"Allow/Ask/Deny"的完整判断链，用流程图可视化每个步骤的分支条件）

函数 `hasPermissionsToUseToolInner` 是权限系统的心脏。它对每次工具调用按顺序执行这些检查：

```
步骤 1a: 全局 deny 规则？→ 立即拒绝
步骤 1b: 全局 ask 规则？→ 询问（sandbox 例外）
步骤 1c: 工具的 checkPermissions() → 获取工具级别判断
步骤 1d: 工具说 deny？→ 拒绝
步骤 1e: 工具需要用户交互？→ 强制询问（bypass 模式无效）
步骤 1f: 内容级 ask 规则（如 Bash(npm publish:*)）→ 强制询问（bypass 模式无效）
步骤 1g: 敏感路径（.git/, .claude/...）→ 强制询问（bypass 模式无效）
步骤 2a: bypassPermissions 模式 → 允许
步骤 2b: 全局 allow 规则 → 允许
步骤 3:  其他情况 → 询问
```

**关键洞察：步骤 1e、1f、1g 在步骤 2a（bypass 模式）之前执行。** 这就是"有些路径永远无法绕过"的实现机制——它们在 bypass 模式生效之前就已经做出了强制决定。

> 📚 **课程关联 · 操作系统 / 计算机安全**：这个 10 步状态机实现了 OS 安全课程中的**强制访问控制**（MAC, Mandatory Access Control）与**自主访问控制**（DAC, Discretionary Access Control）的混合模型。步骤 1e-1g 是 MAC——系统强制执行的安全策略，即使资源"所有者"（这里是拥有 bypass 权限的用户）也无法覆盖。步骤 2a-2b 是 DAC——用户可以通过配置规则自主决定哪些操作被允许。这与 Linux 的 SELinux / AppArmor 设计理念一致：MAC 策略优先于 DAC，`root` 用户也受 SELinux 策略约束。

### 安全豁免路径（bypass-immune）

步骤 1g 保护的"安全免疫"路径来自 `src/utils/permissions/filesystem.ts` 中的两张清单（`DANGEROUS_DIRECTORIES` + `DANGEROUS_FILES`），原因是这些路径可能被利用于代码执行或数据外泄：
- 目录：`.git`、`.claude`、`.vscode`、`.idea`（注释：`Dangerous directories that should be protected from auto-editing`）
- 文件：`.gitconfig`、`.gitmodules`、`.bashrc`、`.bash_profile`、`.zshrc`、`.zprofile`、`.profile`、`.ripgreprc`、`.mcp.json`、`.claude.json`

这些不仅保护 Claude Code 自己的配置（`.claude/`、`.claude.json`、`.mcp.json`），也保护**宿主 shell / 编辑器 / git 相关的执行入口**——任何能让 AI 在下次 shell 启动或编辑器打开时偷偷执行命令的文件，都被纳入 bypass-immune 范围。

这不是偶然的——这个设计的核心思想是：**即使在最高权限模式下，AI 也不应该在没有人类确认的情况下修改"影响 AI 自身行为"的配置文件。**

### 6 种权限模式

> **[图表预留 2.5-B]**：六种权限模式金字塔图（从顶部最严格的 plan 到底部最宽松的 bypassPermissions，每层标注适用场景和切换方式）

系统不是一个开关，而是 6 种模式，每种对应不同的使用场景：

| 模式 | 行为 | 适用场景 |
|------|------|----------|
| `default` | 所有非只读操作需要批准 | 日常使用 |
| `plan` | 禁止写操作（只读） | 探索规划 |
| `acceptEdits` | 文件编辑自动允许，Bash 仍需批准 | 快速写代码 |
| `bypassPermissions` | 跳过所有权限检查（除安全豁免）| 完全自动化 |
| `dontAsk` | 把所有"询问"变成"拒绝" | CI/无人值守 |
| `auto` | AI 分类器决定是否允许 | 智能自动化 |

### Auto 模式：用 AI 判断 AI 是否安全

`auto` 模式（ant-only 功能）是最有趣的设计：当工具需要权限确认时，系统不弹出人工审批对话框，而是启动一个**独立的 AI 分类器**，用完整的对话历史作为上下文来判断"这个操作应该被允许吗"。

为了节省分类器 API 调用的成本，系统设计了两条**快路径**：

```
快路径 1: 模拟 acceptEdits 模式检查
  → 如果在 acceptEdits 模式下这个操作会被允许 → 直接通过，跳过分类器

快路径 2: 安全工具白名单
  → 如果工具在安全白名单中 → 直接通过，跳过分类器

↓ 只有以上都不匹配，才运行

分类器（两阶段）: classifyYoloAction()
  → 分析完整对话历史
  → shouldBlock=false → 允许
  → shouldBlock=true → 记录拒绝次数
```

### 自动回退：防止无限拒绝循环

当 AI 分类器持续拒绝某个操作时，系统会主动回退到人工审批：

- **连续拒绝 3 次** → 回退到人工审批
- **累计拒绝 20 次** → 回退到人工审批（`DENIAL_LIMITS.maxConsecutive=3`, `maxTotal=20`）

逻辑是：如果分类器一直说不行，要么任务真的有问题，要么分类器理解有偏差。两种情况都值得人工看一眼，而不是无限地在自动拒绝循环里浪费 token。

### 失效关闭（Iron Gate）

当 AI 分类器 API 调用失败时（网络错误、过载等），`tengu_iron_gate_closed` feature 标志决定行为：
- 默认：**失效关闭** — 拒绝操作，告知用户稍后重试
- 显式关闭时：失效开放 — 回退到标准权限提示

这是防御性工程的体现：关键安全组件失效时，默认拒绝比默认放行更安全。这一原则在安全工程中被称为 **fail-closed**（失效关闭），与之对应的 fail-open（失效开放）适用于可用性优先的场景（如消防通道的门锁断电时自动打开）。Claude Code 在安全组件上选择 fail-closed，在非安全组件上允许 fail-open 降级，体现了对不同组件采用不同失效策略的成熟工程判断。

---

## 权限规则的语法

权限规则支持多层次的精确控制：

```
Bash              — 所有 Bash 命令（整个工具）
Bash(git *)       — 只允许 git 开头的 Bash 命令（内容匹配）
Read              — 所有文件读取
mcp__server1      — 某 MCP 服务器的所有工具（服务器级）
Agent(Explore)    — 禁止 Explore 类型 agent（agent 类型匹配）
```

规则来自 8 个来源，优先级从高到低：企业策略 > feature 标志 > 本地设置 > 用户设置 > 项目设置 > CLI 参数 > 命令 > session 临时规则。

---

## 这个设计背后的取舍

**平衡点找得很准：**

系统用步骤编号（1a~3）明确划分了"什么级别的权限可以覆盖什么级别的保护"。bypass 模式只能影响步骤 2a 之后的逻辑——它无法影响步骤 1e、1f、1g 设置的硬性保护。这让"最高权限"也有边界。

**复杂性的代价：**

10 个步骤 + 6 种模式 + 8 个规则来源，权限系统的总状态空间相当大。对于开发者来说，理解"为什么我在 bypassPermissions 模式下还是被拦住了"需要了解这个完整的状态机才能排查。这是权衡灵活性的必然代价。

**auto 模式的递归趣味：**

AI 调用 AI 来判断 AI 是否有权限做某事。这是一个有意思的递归结构，同时也意味着 auto 模式下每次权限判断都可能触发额外的 API 调用（尽管三条快路径尽量避免）。

---

## 从这里能学到什么

**权限系统应该是多层的，不同层的保护有不同的"突破门槛"。**

Claude Code 把权限检查分成了三个区域：
1. **绝对安全区**（步骤 1e-1g）：任何模式都无法绕过的保护
2. **规则/模式区**（步骤 2a-2b）：可以通过 bypass 模式或 allow 规则覆盖
3. **默认询问区**（步骤 3）：标准的用户确认流程

这个分层设计让系统能够同时满足"完全自动化"和"永远保护关键资源"这两个看似矛盾的需求。

---

## 代码落点

- `src/utils/permissions/permissions.ts`，第 1158 行：`hasPermissionsToUseToolInner()`，完整 10 步状态机
- `src/utils/permissions/permissions.ts`，第 473 行：`hasPermissionsToUseTool()`，外层函数（处理 auto/dontAsk 模式逻辑）
- `src/utils/permissions/PermissionMode.ts` — 6 种模式定义及元数据
- `src/utils/permissions/denialTracking.ts` — 拒绝追踪，`DENIAL_LIMITS = {maxConsecutive: 3, maxTotal: 20}`
- `src/utils/permissions/yoloClassifier.ts` — auto 模式 AI 分类器实现
- `src/hooks/toolPermission/` — 工具权限相关的 hook 实现

---

## 还可以追问的方向

- auto 模式的两阶段分类器是怎么工作的？stage1 和 stage2 分别做什么？
- `.git/` 等路径的安全检查（safetyCheck）在工具层是怎么实现的？是 FileEditTool 的 `checkPermissions()` 里？
- `shouldAvoidPermissionPrompts` 在哪里被设置为 true？与 `isAsync` 参数有什么关系？

---

