第20章:七层防御:一条命令的安检之旅¶
生活类比:机场安检
机场不会只靠一道门保护你。行李要过 X 光,人要过安检门,可疑物还要开箱复查,最后还有隔离区和登机口复核。Claude Code 的安全设计也不是“靠 AI 自己别犯错”,而是假设它一定会犯错,于是把风险拆成多层来挡。
这一章先回答一个问题
一个能执行 rm -rf、改文件、联网、开子 Agent 的 AI 助手,为什么还能被人放心地放进真实工程里?
Claude Code 的答案不是一句“我们模型很聪明”,而是一整条源码可追踪的安全链。从权限模式、规则匹配、工具自检,到 Hook、分类器、沙箱、策略与认证,任何一层出错,后面还有别的层补上。
20.1 先别急着看代码,先看它在防什么¶
源码里反复出现的不是“能力”,而是“约束”。这通常意味着作者面对的是几类真实威胁:
- 误操作:模型本意是好的,但把路径、参数、上下文理解错了。
- 越权操作:模型能做的事太多,顺手越过了当前任务边界。
- 提示词攻击:恶意文件、日志、网页内容诱导模型执行危险动作。
- 持久化污染:把风险写进
.git、.claude、shell 配置或凭据目录。 - 环境逃逸:应用层判断失手后,命令直接碰到系统资源。
mindmap
root((Claude Code 安全威胁))
误操作
路径选错
命令拼错
上下文理解偏差
越权
本不该写文件
本不该联网
本不该派生子Agent
提示词攻击
恶意README
恶意日志
恶意网页
持久化污染
dotfiles
.git
.claude
环境逃逸
沙箱外执行
认证令牌滥用
远程会话劫持
如果只靠“执行前弹个确认框”,这些风险根本挡不住。因为很多危险操作看起来像普通操作,很多普通操作连着做也会变危险。
20.2 从源码角度,可以把它归纳成七层防御¶
这里的“七层”不是某一个枚举常量的名字,而是我们根据调用链整理出的分析框架。你会在不同文件里看到这些层一起工作。
flowchart TD
L1["第1层<br/>模式与入口约束<br/>permissionSetup.ts"] --> L2["第2层<br/>规则判定<br/>permissions.ts"]
L2 --> L3["第3层<br/>工具自检<br/>bashPermissions.ts / Tool.checkPermissions"]
L3 --> L4["第4层<br/>Hook拦截<br/>toolHooks.ts"]
L4 --> L5["第5层<br/>分类器与拒绝追踪<br/>classifierDecision.ts / yoloClassifier.ts / denialTracking.ts"]
L5 --> L6["第6层<br/>操作系统沙箱<br/>sandbox-adapter.ts"]
L6 --> L7["第7层<br/>策略与身份边界<br/>settings.ts / oauth / jwt"]
style L1 fill:#17324d,stroke:#64b5f6,color:#fff
style L2 fill:#21415f,stroke:#4db6ac,color:#fff
style L3 fill:#264d73,stroke:#ffd54f,color:#fff
style L4 fill:#305a87,stroke:#ff8a65,color:#fff
style L5 fill:#35679a,stroke:#ba68c8,color:#fff
style L6 fill:#3d74ad,stroke:#81c784,color:#fff
style L7 fill:#467fbf,stroke:#ef5350,color:#fff
第 1 层:模式与入口约束¶
权限模式不是 UI 装饰,而是第一层开关。PermissionMode.ts 定义了 default、plan、acceptEdits、bypassPermissions,以及在特性开启时才出现的 auto 模式;permissionSetup.ts 再把 CLI 参数、settings、GrowthBook 门控合并成实际模式。这样一来,很多风险在“命令还没跑”之前就被模式收窄了。
第 2 层:规则判定¶
permissions.ts 先查整工具 deny,再查 ask,再交给工具自己的 checkPermissions。这是 Claude Code 最核心的一层:它先问“这类行为该不该做”,而不是先跑完再回头解释。
第 3 层:工具自检¶
最典型的是 Bash。bashPermissions.ts 不是一句正则,而是把路径、sed、只读命令、安全模式、沙箱自动放行、最终提示拆成多个阶段。也就是说,即使用户已经给过某些大类权限,工具自己还会继续做二次筛查。
第 4 层:Hook 拦截¶
toolHooks.ts 允许在工具前后插入外部治理逻辑。但它又特别小心:Hook 即使返回 allow,也不会绕过 settings 里的 deny/ask 规则。这是很成熟的安全思路。
第 5 层:分类器与拒绝追踪¶
自动模式不是“全部自动通过”。classifierDecision.ts 先区分一批天然安全工具,剩下的交给 yoloClassifier.ts 做转录压缩、系统提示拼装和风险分类;denialTracking.ts 则负责在连续拒绝太多时回退到人工确认,避免自动模式越跑越偏。
第 6 层:操作系统沙箱¶
sandbox-adapter.ts 把 Claude Code 自己的权限规则翻译成底层沙箱配置,包括可写目录、禁止写入的设置文件、网络域名等。它不是“更漂亮的提示框”,而是系统级隔离。
第 7 层:策略与身份边界¶
最后还有 MDM、managed settings、OAuth、Bridge JWT 刷新这些“外围护城河”。它们不直接判断 rm -rf,但决定了谁能进入某种模式、谁能拿到某种令牌、哪些组织策略不可绕过。
20.3 一条 Bash 命令要过哪些检查¶
把源码摊开看,一条命令的安全旅程差不多是这样的:
sequenceDiagram
participant A as Agent
participant P as permissions.ts
participant B as bashPermissions.ts
participant H as toolHooks.ts
participant C as Classifier/Tracking
participant S as Sandbox
participant O as OS
A->>P: 请求使用 BashTool
P->>P: 查 deny / ask / allow 规则
P->>B: 调用 tool.checkPermissions()
B->>B: 路径 / sed / 只读 / 模式检查
B-->>P: allow / ask / deny / passthrough
P->>H: 触发 PreToolUse Hook
H-->>P: 可能改输入 / ask / deny / allow
P->>C: auto 模式下补充分级与拒绝追踪
C-->>P: 是否需要回退到人工确认
P->>S: 生成沙箱配置
S->>O: 在受限环境执行
O-->>A: 输出或失败
如果我们把关键代码片段压缩成一句话,大概就是:
checkRuleBasedPermissions()先看“规则层面是否反对”。tool.checkPermissions()再看“这个具体工具、这次具体输入有没有问题”。resolveHookPermissionDecision()再确保“外部 Hook 的结论不能冲掉内建规则”。- 真要跑 Bash 时,再决定是否进入沙箱。
20.4 纵深防御的关键,不是层数多,而是层与层独立¶
很多系统也喜欢说“我们有很多安全措施”,但其实几层都在做同一件事,失效时会一起失效。Claude Code 这套设计更有价值的地方,是它让不同层解决不同问题:
| 层 | 主要回答的问题 | 典型文件 |
|---|---|---|
| 模式层 | 这次会话总体有多激进? | PermissionMode.ts |
| 规则层 | 这个工具/模式在当前上下文下该不该做? | permissions.ts |
| 工具层 | 这个具体命令或路径安全吗? | bashPermissions.ts |
| Hook 层 | 组织或扩展逻辑要不要追加治理? | toolHooks.ts |
| 分类层 | 自动模式下是否需要再判断一次? | yoloClassifier.ts |
| 沙箱层 | 就算判断错了,系统还能拦什么? | sandbox-adapter.ts |
| 策略层 | 哪些配置和身份边界根本不让你碰? | settings.ts / oauth |
flowchart LR
A["同一种风险"] --> B["不要只用一种手段挡"]
B --> C1["规则层:语义判断"]
B --> C2["工具层:输入检查"]
B --> C3["Hook层:外部治理"]
B --> C4["沙箱层:系统隔离"]
B --> C5["策略层:组织限制"]
style A fill:#4a1d1f,stroke:#ff6b6b,color:#fff
style B fill:#17324d,stroke:#64b5f6,color:#fff
style C1 fill:#23415d,stroke:#4db6ac,color:#fff
style C2 fill:#23415d,stroke:#ffd54f,color:#fff
style C3 fill:#23415d,stroke:#ff8a65,color:#fff
style C4 fill:#23415d,stroke:#81c784,color:#fff
style C5 fill:#23415d,stroke:#ba68c8,color:#fff
这也是为什么 Claude Code 的安全代码看起来“不优雅”地分散在很多目录里。它不是偷懒,而是在避免“一个总开关失效,全盘失守”。
20.5 为什么它不追求零弹窗,也不追求零风险¶
安全产品最难的不是“更严”,而是“既严又能用”。源码里能看到两个平衡动作:
- 一方面,它给只读命令、安全工具、沙箱内 Bash 留了快速路径。
- 另一方面,它对危险规则、危险路径、危险 sed、危险 shell 特性采取了绕不过去的强约束。
quadrantChart
title 安全与摩擦的取舍
x-axis 低使用摩擦 --> 高使用摩擦
y-axis 低安全性 --> 高安全性
quadrant-1 安全但笨重
quadrant-2 理想区域
quadrant-3 看起来顺手
quadrant-4 又慢又不安全
"全部 bypass": [0.15, 0.1]
"每步都弹窗": [0.82, 0.88]
"Claude Code 默认策略": [0.52, 0.78]
"只读快速路径": [0.24, 0.66]
深入一点看,你会发现它并不是把“快”和“安全”当成对立面,而是努力把低风险操作快速化,把高风险操作精细化。这是比“永远先问用户”更成熟的产品观。
🔭 深水区(架构师选读)
这一章最值得带走的思想是:安全系统最好不要只有一个“聪明的大脑”。Claude Code 把治理拆成模式、规则、工具、Hook、分类器、沙箱、策略这几类相对独立的部件。这样就算其中一块判断失误,其他块还有机会补救。对任何带执行能力的 AI 系统来说,这种“可失败但不易失控”的设计,比追求某个单点模型判断 100% 正确更现实。
本章小结
Claude Code 的安全性不是来自“模型不会犯错”,而是来自一条多层、独立、互相补位的防线。你后面读权限系统、BashTool、沙箱、认证时,都可以把它们放回这七层框架里理解。
关键源码索引
PermissionMode模式定义:PermissionMode.ts- 规则判定主流程:
permissions.ts - 最终权限决策:
permissions.ts - 自动模式危险权限剥离:
permissionSetup.ts - 危险权限扫描:
permissionSetup.ts - Bash 二次检查顺序:
bashPermissions.ts - Hook 权限合流:
toolHooks.ts - 拒绝追踪回退:
denialTracking.ts - 沙箱配置翻译:
sandbox-adapter.ts
逆向提醒
“七层防御”是本书根据多处源码整理出的分析框架,不是源码里某个现成常量名。真实实现是分散的:有的在权限引擎里,有的在工具内部,有的在沙箱和认证层。