# 多层防线不是偏执，是必要

Claude Code 里"安全"这件事不是一个模块，而是一个贯穿整个系统的设计哲学。

> 🌍 **行业背景**：多层安全防御在信息安全领域有一个经典名称——**纵深防御（Defense in Depth）**——其思想可追溯到军事领域的多层阵地防御。在现代信息安全领域，美国国家安全局（NSA）在 2000 年代初的《Defense in Depth》白皮书系统化了这一理念（具体年份以 NSA 公开文档为准）；在软件安全中，OWASP 的安全设计原则、微软的 SDL（Security Development Lifecycle）都将纵深防御列为核心原则。在 AI 编码助手领域（以下事实性判断为作者本项目撰写时（2026 年 4 月）基于各家官方文档、发布博客和社区讨论的观察，未穿透到各自源码做独立审计）：**Cursor** 采用沙箱加用户确认的模型，Agent 模式引入了工具级权限审批（yolo 模式的 allowlist/blocklist），具体发布日期和细节以 Cursor 官方文档为准；**Aider** 在安全方面较为薄弱，主要依赖用户手动审查 diff——这是其作为开源 CLI 工具的定位使然；**GitHub Copilot** 最初只做代码补全（不执行命令），攻击面从根本上小得多，2024-2025 年的 Agent Mode 引入了新的安全面，具体以 GitHub 官方文档为准——不是"安全模型更轻量"，而是根本不需要这六层中的大部分。Claude Code 选择了"更强大但更危险"的路线——它能执行任意 Bash 命令、读写文件系统——这个根本性的能力-风险 trade-off 解释了为什么它需要比同行更多的防线。这不是 Claude Code 发明了纵深防御，而是 AI Agent 的能力范围（尤其是代码执行权限）让纵深防御从"推荐做法"变成了"生存必需"。

---

## 同一个问题，六个层次的回答

以"是否允许执行这个 Bash 命令"为例，系统会在六个不同层次做出判断：

**层次 1：全局策略（policySettings）**

企业 MDM 或本地配置文件可以在系统级别声明"某类工具永远不允许"。这一层在其他所有判断之前执行，不可绕过。对应源码中 `hasPermissionsToUseToolInner()`（`src/utils/permissions/permissions.ts`）的步骤 1a——整个工具被 deny 规则拦截。

> **关于 "1a / 1g" 编号**：`hasPermissionsToUseToolInner()` 的源码中用块级注释把决策流程明确编号为 1a / 1b / 1c / 1d / 1e / 1f / 1g / 2a / 2b / 3 共 10 个子步骤，是 Anthropic 源码自带的编号体系，不是本书自创。Part 2 第 7 章「安全架构」§4.1 将其合并成"九步高层检查链"的叙事粒度；Part 3 第 9 章保留源码原编号。两章行文粒度不同，但底层同一套 10 个编号子步骤。

**层次 2：工具自检（checkPermissions）**

每个工具都有自己的 `checkPermissions()` 方法。Bash 工具会通过模式匹配（逐字符比对已知危险指令）检查命令是否包含已知危险模式（如 `rm -rf <path>`（强制递归删除——根据 `<path>` 不同后果从"删一个项目目录"到"删整个用户家目录"甚至"删整个文件系统"；检查的是命令模式本身而非具体目标）、`chmod 777`（把文件权限完全开放，任何人都能读写执行）等，见 `bashPermissions.ts`）；Edit 工具会检查路径是否在 `.git/`、`.claude/`、`.vscode/`、`.idea/` 等敏感目录里，或者是否匹配 `.bashrc`、`.gitconfig` 等危险文件名（`filesystem.ts` 中的 `DANGEROUS_DIRECTORIES` 和 `DANGEROUS_FILES` 列表）。

> **注意**：这里说的是"模式匹配"而非"理解意图"——`checkPermissions()` 不包含任何 AI 推理，纯粹是规则驱动的字符串检查。

**层次 3：Safety Check（bypass-immune）**

权限系统里有一层特殊的 Safety Check，它位于 `bypassPermissions` 检查**之前**（源码步骤 1g）：

```typescript
// permissions.ts 步骤 1g：
// Safety checks (e.g. .git/, .claude/, .vscode/, shell configs) are
// bypass-immune — they must prompt even in bypassPermissions mode.
// checkPathSafetyForAutoEdit returns {type:'safetyCheck'} for these paths.
if (
  toolPermissionResult?.behavior === 'ask' &&
  toolPermissionResult.decisionReason?.type === 'safetyCheck'
) {
  return toolPermissionResult
}
```

这意味着即使用户开启了"绕过所有权限检查"的最高权限模式，以下路径依然无法被自动修改：

- **危险目录**：`.git/`、`.vscode/`、`.idea/`、`.claude/`（整个目录树，不只是配置文件）
- **危险文件**：`.gitconfig`、`.gitmodules`、`.bashrc`、`.bash_profile`、`.zshrc`、`.zprofile`、`.profile`、`.ripgreprc`、`.mcp.json`、`.claude.json`
- **Claude 自身配置**：所有 `.claude/settings.json`、`.claude/settings.local.json`、`.claude/commands/`、`.claude/agents/`、`.claude/skills/` 下的文件

这不是"两三个路径"的保护——是一个完整的、覆盖多个攻击向量的硬编码安全网。关于这一层为什么是整个模型中最深刻的设计思想，我们在后面单独展开。

**层次 4/5：用户确认 或 Auto 模式分类器（运行时二选一）**

这里需要坦诚说明一个重要事实：**层次 4（用户确认）和层次 5（Auto 模式分类器）在运行时是互斥的，不是叠加的。**

- 在默认模式下，如果前几层都没有明确答案，系统弹出权限确认对话框，让用户做最终决定——这是层次 4。
- 在 auto 模式下，AI 分类器替代用户确认来判断工具调用是否安全——这是层次 5。

两者不会同时生效。严格来说，在任意给定时刻，最多只有五层防线同时工作。从"设计完备性"角度，系统确实覆盖了六种不同的安全检查机制；但"六层叠加、每一层的漏洞由下一层兜底"这个说法在层次 4 和 5 之间不成立——它们是同一个决策点的两种实现模式，各自服务不同的使用场景。

> 💡 **通俗理解**：把这条规则单独拎出来看，像银行柜台的两种审核方式——人工柜员（用户确认）和 ATM 自助（AI 分类器）。同一笔交易只走其中一种，两者不会并行。

**层次 5 的铁门规则（Iron Gate）**

Auto 模式分类器还有一个安全阀门：`denialTracking.ts` 跟踪分类器的拒绝次数。如果连续拒绝 3 次（`maxConsecutive: 3`）或累计拒绝 20 次（`maxTotal: 20`），系统自动回退到人工审批模式。这就是所谓的"铁门"——当自动审批机制表现异常时，系统自动降级到更保守的人工模式。

需要区分两种完全不同的场景：**分类器主动判断拒绝**（它理解了操作内容，认为不安全）和**分类器不可用**（API 错误、网络问题）。前者触发上述拒绝计数逻辑；后者由 `tengu_iron_gate_closed` 特性开关控制——默认是 fail-closed（直接拒绝），但可以通过远程配置切换为 fail-open（回退到用户确认）。

**层次 6：Hooks（可扩展的防线）**

用户可以通过 `PreToolUse` hook，在工具执行前注入自定义的安全检查：

```bash
# 阻止删除生产环境的任何文件
if echo "$INPUT_ARGS" | grep -q "production"; then
  echo "不允许在 production 目录执行操作" >&2
  exit 2
fi
```

Hooks 是六层中唯一由用户定义的层。这意味着 Claude Code 承认自己无法预见所有业务场景的安全需求，选择了将防线的定义权交给用户。这是一种**安全即平台（Security as a Platform）** 的思路——和 AWS IAM 允许用户自定义策略、Kubernetes 的 Admission Webhook 允许用户注入自定义准入逻辑是同一设计哲学。

---

## 为什么需要这些层？

> 💡 **通俗理解**：这些防线更像**机场安检流程**——值机台查护照（企业策略）→ 安检门金属探测（工具自检）→ X 光机扫描行李（Safety Check）→ 人工开包或自助通道（用户确认/AI 分类器）→ 登机口二次核验（Hooks）。不是每层都在拦同一种东西——护照查的是身份，X 光查的是违禁品，人工开包是处理不确定的情况。

**每一层针对不同的威胁模型：**

| 层次 | 防御的威胁 | 运行时是否始终生效 |
|------|-----------|------------------|
| policySettings | 合规要求、企业策略 | 是 |
| checkPermissions | AI 误操作（模式匹配已知危险命令） | 是 |
| bypass-immune | 最坏情况：即使 bypass 模式也要保护的资产 | 是 |
| 用户确认 | AI 不应该单独做的高风险决策 | 仅默认模式 |
| 分类器 | auto 模式下的自动审核 | 仅 auto 模式 |
| hooks | 用户定义的业务逻辑安全规则 | 是（如果配置了） |

没有任何一层能覆盖所有威胁。企业策略不能预测所有业务场景；工具自检不能理解操作的语义；用户确认在 auto 模式下缺失；Hooks 需要用户主动配置……

这些层的价值不在于"六层叠加"的数字，而在于每一层防御不同类型的威胁——规则覆盖不了的交给 AI 判断，AI 判断不了的交给人类，而某些底线谁都不能绕过。

---

## bypass-immune：当用户自己是威胁源

bypass-immune 是整个防御模型中最具工程价值的设计之一（"最有工程智慧"是作者基于与同类产品对比的主观判断：Cursor/Aider/Copilot 均无类似"即使用户主动开启 bypass 也不让你改 `.git/config` / `.bashrc`"的硬性底线机制，这条底线在"信任模型遭到颠覆"场景下的防御价值显著），值得深入理解。

传统安全模型假设用户是可信的——权限系统服务于"帮助用户做正确的事"。但 bypass-immune 承认了一个 AI Agent 时代的现实：**用户可能在不理解后果的情况下开启最高权限模式，系统需要保护用户免受自己的伤害。**

想象这个场景：用户对反复弹出的权限确认感到烦躁，开启了 `bypassPermissions` 模式。此时如果 AI 被恶意 prompt 注入误导，试图修改 `.git/config` 来配置一个恶意的远程仓库，或者修改 `.bashrc` 注入后门命令——没有 bypass-immune，这些操作会直接通过，因为用户已经主动关闭了所有安全检查。

bypass-immune 的设计回答了一个深刻的问题：**有些东西，不应该因为用户说"我信任你"就变得可以修改**。

这个思想在操作系统中有明确的先例：

- **macOS 的 SIP（System Integrity Protection）**：即使 root 用户也无法修改 `/System`、`/usr`（`/usr/local` 除外）等系统目录。要关闭 SIP 需要重启进入恢复模式——故意设计成"不可能在正常操作流中关闭"。
- **Linux 的 immutable 文件属性**：`chattr +i` 标记的文件，即使 root 也需要先 `chattr -i` 才能修改。这是一个有意的减速机制——多出来的一步操作迫使管理员确认"我确实要修改这个文件"。
- **Windows 的 UEFI Secure Boot**：固件级别的启动验证，操作系统自身无法绕过。

Claude Code 的 bypass-immune 是同一哲学在 AI Agent 中的应用。从源码来看，它的实现分布在权限管线的多个位置：

1. **步骤 1g**（`hasPermissionsToUseToolInner`）：在 `bypassPermissions` 模式判断之前，`safetyCheck` 类型的决策直接返回 `ask`，绕过后续的自动允许逻辑。
2. **auto 模式入口**（`hasPermissionsToUseTool`）：即使在 auto 模式下，`safetyCheck` 且 `classifierApprovable: false` 的路径也不会交给分类器——这些路径对所有自动审批路径都免疫。
3. **`checkPathSafetyForAutoEdit()`**（`filesystem.ts`）：具体判断哪些路径是"危险的"，返回 `classifierApprovable` 标记区分"需要人工确认"和"分类器可以处理"。

**如果你在构建自己的 AI Agent**，确定 bypass-immune 清单的原则是：找出那些"一旦被修改，安全模型本身就会失效"的文件。对 Claude Code 来说，`.claude/settings.json` 控制权限规则、`.git/config` 控制代码来源、`.bashrc` 控制 shell 环境——修改这些文件等于釜底抽薪，让所有其他安全层都建立在被篡改的基础上。

---

## 失效关闭（Fail Closed）

> 📚 **课程关联**：Fail Closed（失效关闭）vs Fail Open（失效开放）是**计算机网络**和**操作系统**课程中安全设计的基础概念。防火墙的默认策略是 "deny all"（失效关闭）；OAuth token 过期后拒绝访问；TLS 证书验证失败时断开连接——这些都是 fail-closed。

在安全工程中，Fail Closed 是**基线要求**而非创新——做不到这一点的安全系统不配叫安全系统。Claude Code 做了 fail-closed 是正确的，但真正值得讨论的不是"它做了 fail-closed"本身，而是**在 AI Agent 场景下，fail-closed 的具体实现细节**。

从源码来看，Claude Code 的 fail-closed 实现有三种具体策略，对应不同的失败场景：

**场景一：分类器 API 不可用**（网络错误、服务故障）

```typescript
// permissions.ts：分类器不可用时的处理
if (classifierResult.unavailable) {
  if (getFeatureValue_CACHED_WITH_REFRESH(
    'tengu_iron_gate_closed', true,
    CLASSIFIER_FAIL_CLOSED_REFRESH_MS  // 30 分钟刷新一次
  )) {
    // Fail closed: 直接拒绝，不回退到用户确认
    return { behavior: 'deny', ... }
  }
  // Fail open: 回退到正常的权限处理流程（用户确认）
  return result
}
```

注意这里的 `tengu_iron_gate_closed` 是一个远程特性开关，默认值为 `true`（fail-closed）。这意味着 Anthropic 可以在分类器出现大面积故障时远程切换到 fail-open 模式，避免所有用户的 auto 模式完全瘫痪。这是运维层面的务实——纯粹的 fail-closed 在大规模服务中会导致"安全机制本身成为可用性的单点故障"。

**场景二：分类器上下文窗口溢出**（对话历史太长）

这是一个确定性错误——对话只会越来越长，不会自动恢复。系统直接跳过铁门逻辑，回退到用户确认。在 headless（无人值守）模式下则直接抛出 `AbortError` 终止任务。

**场景三：分类器反复拒绝**（连续 3 次或累计 20 次）

这暗示了两种可能：要么任务确实在做危险的事，要么分类器过于保守。无论哪种情况，自动化处理都不再可靠。系统降级到人工审批，让人类判断。

> 💡 **通俗理解**：这就像自动驾驶的接管逻辑——传感器看不清路况（分类器不可用）时紧急刹车；连续多次犹豫不决（反复拒绝）时把方向盘交还给人类。不同的失败模式需要不同的降级策略。

---

## 性能代价：六层检查要多久？

纵深防御的经典困境是：层数越多越安全，但也越慢。六层安全检查（尤其是层次 5 的 AI 分类器推理）对延迟有多大影响？

**前三层（策略 / 工具自检 / Safety Check）加上 auto 模式下层次 5 的"规则匹配快速路径"几乎无开销**——它们全部是内存中的字符串匹配和规则查表，执行时间在微秒量级。注意层次 4（用户确认）本身需要人的交互，量级上完全不同（秒级~分钟级）——不在"微秒级"这一类里；我们分析"六层检查要多久"时关注的是"CPU 实际花了多少时间"，人类等待时间单独在下文讨论。

**层次 5（AI 分类器）是唯一的重开销环节**。每次在 auto 模式下调用需要安全审批的工具时，系统都要发起一次额外的 LLM API 调用（`sideQuery`）。从源码的遥测字段可以看到，系统精确追踪了分类器的性能指标：

```typescript
// permissions.ts 中的遥测记录
classifierInputTokens: classifierResult.usage?.inputTokens,
classifierOutputTokens: classifierResult.usage?.outputTokens,
classifierDurationMs: classifierResult.durationMs,
classifierCostUSD,  // 分类器单次调用的美元成本
```

系统做了两件事来缓解这个开销：

1. **快速路径跳过分类器**。在调用 AI 分类器之前，系统会先检查两个快速路径：`acceptEdits` 模式模拟（如果操作在该模式下本来就会被允许，比如在工作目录内编辑文件，就直接放行）和安全工具白名单（`isAutoModeAllowlistedTool`，如 Read 等只读工具直接放行）。大量日常操作会被这两条路径拦截，根本不会触发分类器 API 调用。

2. **两阶段分类器设计**。源码中的 `classifyYoloActionXml` 实现了两阶段判断：Stage 1 是一个快速的初步判断（较少 token），只有在初判不确定时才进入 Stage 2 的深度分析。`cache_control` 参数让系统提示和对话前缀在同一 session 内可以被缓存复用（1 小时 TTL），进一步减少重复 token 消耗。

**分类器延迟的累积效应是可观的**。在频繁执行 Bash 命令的 auto 模式场景中（比如连续 `git add`、`npm install`、编译运行），每次不在快速路径上的命令都会增加一次 LLM 往返延迟。源码中 `addToTurnClassifierDuration()` 跟踪的是每个 turn 内分类器的累计耗时，这个数值会和主循环的 token 消耗一起上报给分析系统——说明 Anthropic 自己也在密切关注这个 overhead 比例。

这是一个真实的 trade-off：auto 模式用速度换安全。用户选择 auto 模式是为了减少手动确认的打断，但每次分类器调用都增加了延迟。在安全关键的场景中这个代价是值得的，但在低风险的批量操作中，合理配置 allow 规则以利用快速路径，是优化体验的关键。

---

## 插件系统里的同样模式

插件安全也是多层的。本节列的这 5 条是插件子系统**在权限层之外**的额外防线——**不与主文 "六层" 直接对应**（主文的六层是针对"是否允许执行这个工具调用"的决策链；这里的五条是"能否加载这个插件"的生命周期守卫），两套分层服务于不同问题：

1. **工作区信任**：插件 hooks 在信任建立前不执行
2. **名称防护**：防止官方名称仿冒
3. **黑名单**：已知恶意插件直接拒绝
4. **企业管控**：`allowManagedHooksOnly` 可以禁止所有用户插件
5. **优先级降级**：插件 hooks 的优先级始终低于用户设置

---

## 从 Claude Code 到你的系统：三级实施建议

如果你在构建需要控制 AI Agent 行为的系统，以下是基于源码分析的分层实施建议——不是泛泛的"多层比单层好"，而是根据团队规模和风险等级的具体优先级。

### 最小可行安全模型（三层，适合个人开发者/小团队）

**第一优先级：工具自检（对应 checkPermissions）**

每个工具实现自己的 `checkPermissions()` 方法，用模式匹配拦截已知危险操作。这是投入产出比最高的一层——实现成本低（纯字符串匹配），但能拦截大部分误操作。

参考 Claude Code 的做法：维护一个 `DANGEROUS_FILES` 和 `DANGEROUS_DIRECTORIES` 列表。你不需要照搬 Claude Code 的列表，但核心原则相同——找出你的系统中"被修改后会导致安全模型失效"的文件。

**第二优先级：bypass-immune 路径**

硬编码一个不可绕过的保护列表。确定标准：如果文件 X 被篡改，你的安全检查还能正常工作吗？如果答案是"不能"，X 就应该在 bypass-immune 列表里。对于大多数系统，至少包括：
- 安全配置文件本身（如权限规则定义文件）
- 版本控制配置（`.git/config`）
- Shell/环境配置（`.bashrc`、`.zshrc` 等）

**第三优先级：用户确认对话框**

对所有不在"已知安全"列表中的操作弹出确认。这是最简单的兜底——不确定的操作交给人类判断。

### 完整安全模型（六层，适合企业级/高风险部署）

在最小模型基础上增加：
- **企业策略层**（policySettings）：让运维团队通过 MDM 或集中配置控制可用工具范围
- **AI 分类器层**（auto 模式）：用一个独立的 LLM 调用对操作做语义级安全判断。需要额外的 API 成本和延迟，但能理解规则匹配无法覆盖的上下文
- **Hooks 可扩展层**：把安全规则的定义权交给最了解业务的人——最终用户

### 实施成本参考（作者经验估算 · 非 benchmark）

> ⚠️ 下表的开发时间是**作者基于类似安全系统工程经验的量级估算**（假设 1 名熟悉 TypeScript + AI Agent 的高级工程师、从零开始实现且已有清晰规范）。真实项目中会因团队规模、既有代码库成熟度、测试覆盖要求、合规审批流程等因素差异显著，请以本团队实测为准。

| 层 | 开发量级估算 | 运行时开销 | 维护复杂度 |
|---|------------|----------|----------|
| 工具自检 | 数天（取决于危险模式列表规模）| 几乎为零 | 低（更新危险模式列表） |
| bypass-immune | 半天至一天（硬编码清单）| 几乎为零 | 低（列表很少变化） |
| 用户确认 | 数天（含 UI）| 无（等待用户输入） | 低 |
| 企业策略 | 一到两周（含 MDM 集成与管理界面）| 几乎为零 | 中（策略管理界面） |
| AI 分类器 | 数周（含 prompt 调优 + eval 测试集）| 高（每次调用一次 LLM） | 高（prompt 调优、误判处理） |
| Hooks 系统 | 一到两周（含安全审查和文档）| 取决于 hook 实现 | 中（需要文档和示例） |

关键决策点：如果你的 Agent 不需要 auto 模式（所有敏感操作都由用户确认），那么 AI 分类器这一层完全可以省略——它是专门为"无人值守自动执行"场景设计的。

---

## 代码落点

- `src/utils/permissions/permissions.ts` — 权限管线核心：`hasPermissionsToUseToolInner()` 实现步骤 1a-2b 的完整流程，`hasPermissionsToUseTool()` 在外层处理 auto/dontAsk 模式转换和分类器调用
- `src/utils/permissions/denialTracking.ts` — 铁门规则：拒绝计数跟踪（连续 3 次 / 累计 20 次阈值），`shouldFallbackToPrompting()` 判断是否触发降级
- `src/utils/permissions/yoloClassifier.ts` — Auto 模式分类器：`classifyYoloAction()` 入口，两阶段 XML 分类器 `classifyYoloActionXml()`，含 cache_control 和快速路径优化
- `src/utils/permissions/filesystem.ts` — bypass-immune 实现：`checkPathSafetyForAutoEdit()`、`DANGEROUS_FILES`、`DANGEROUS_DIRECTORIES` 列表、`isDangerousFilePathToAutoEdit()` 具体判断逻辑
- `src/utils/permissions/permissionSetup.ts` — 权限初始化：从 CLI 参数和配置文件解析工具权限规则
- `src/hooks/` — Hook 系统：`PreToolUse` / `PermissionRequest` 等事件，构成权限的可编程扩展层
