安全层:权限、Hook 和三层防护网
权限系统 27 个文件、Hook 的三个时点、resolveHookPermissionDecision 的粘合逻辑——Claude Code 的安全不是一道墙,是三层网。
Claude Code 是个能跑任意 shell 命令的 AI。这句话本身就是个安全命题。怎么让它”强大但受控”,是工程层面最难处理的问题之一。源码的答案是:不靠一道墙,靠三层网。
权限系统:utils/permissions/ 的 27 个文件
权限系统的入口是 PermissionMode,三个模式:
default:正常模式,危险操作需要用户确认plan:只规划,不执行auto:自动模式(即 —dangerously-skip-permissions,俗称 YOLO 模式)
权限决策的原子单位是 PermissionRule(allow / deny / ask)和 PermissionResult(最终决策结果)。
危险命令的识别靠两个分类器:
bashClassifier:给 Bash 命令打风险标签。哪些命令组合危险、哪些管道操作需要警惕,都在这里硬编码。dangerousPatterns 维护了一套正则匹配表,专门盯 rm -rf、dd if=、重定向到系统路径之类的模式。
yoloClassifier:auto 模式专用。auto 模式不等于无限制,yoloClassifier 在完全自动化的场景下仍然做最后一道分类,把”真的不应该无监督执行”的命令标出来。
shellRuleMatching 处理规则匹配逻辑(用户配置的 allow/deny 规则怎么跟当前命令对上),pathValidation 处理文件路径合法性校验(防止路径穿越之类的问题)。
27 个文件不是堆砌——它们分别负责规则解析、命令分类、路径校验、决策合并,每个模块边界清晰。
Hook 系统:三个时点
Hook 是 Claude Code 的扩展点,运行在工具执行流程的三个位置:
PreToolUse:工具执行前触发PostToolUse:工具执行成功后触发PostToolUseFailure:工具执行失败后触发
很多人以为 Hook 就是”记日志”或者”发通知”。实际上 Pre-hook 的能力远不止这些。
Pre-hook 能做的事:
- 返回
permissionBehavior(allow / ask / deny)——直接影响这个工具调用能不能执行 - 返回
updatedInput——修改工具的输入参数。模型要执行的命令,可以在 hook 里改掉再传给工具 - 返回
blockingError——直接阻断,工具调用不发生,错误信息返回给模型 - 返回
preventContinuation——阻止后续整个流程,不只是单个工具调用 - 返回
additionalContexts——给当前执行上下文追加信息,模型在后续步骤能看到这些补充内容
Post-hook 能做的事:
Post-hook 不只是事后记录。对 MCP 工具而言,post-hook 还能修改工具的输出——模型看到的结果,可能已经被 post-hook 过滤或改写过。此外 post-hook 也能追加消息、注入上下文,影响模型下一步的判断。
resolveHookPermissionDecision():安全的关键粘合层
这个函数是整个安全架构最容易被误解的地方。直觉上可能觉得:hook 说 allow,那就直接放行了。实际上不是。
resolveHookPermissionDecision() 的逻辑可以拆成几种情形:
hook 说 allow,但工具本身要求用户交互、又没提供 updatedInput——仍然走 canUseTool 的标准流程,该弹窗还是弹窗。settings 里有 deny 规则的话,deny 直接生效,hook 的 allow 盖不住它。ask 规则同理,弹窗照弹。
拒绝方向则更直接:hook 说 deny,立即生效,不再往下走;hook 说 ask,则作为 forceDecision 传给权限弹窗,强制触发确认界面。
设计意图很明确:hook 是扩展点,不是逃生通道。你可以用 hook 做更细粒度的控制,但不能用 hook 绕过 settings 里已经明确配置的规则。权力是叠加的,不是覆盖的。
三层防护网
把上面的机制整合起来,Claude Code 的安全架构是三层串联(但各层独立)的结构:
第一层:Speculative Classifier
在工具真正执行之前,BashTool 的风险分类器就已经在运行。它与 Hook 并行,提前预判这个命令的风险等级。
关键点:classifier 的结果是辅助信息,不是最终决策。它不能绕过 Hook,也不能绕过 Permission Decision。它的作用是把”明显危险”的操作尽早标出来,让后续层可以更快做决策。
第二层:Hook Policy Layer
PreToolUse hooks 在这里运行。这一层可以:修改输入、阻断执行、强制触发确认、追加上下文。是三层里灵活性最高的一层,也是最容易被第三方扩展的层。
第三层:Permission Decision
最终的权限决策层。综合 settings 配置(用户设置的 allow/deny/ask 规则)和用户交互(弹窗确认),得出最终结论。
这一层是兜底。前两层都通过了,Permission Decision 仍然可以说不。
三层的关系不是”通过一层就能省掉下一层”,而是”每层做自己该做的判断,互相补充但互不绕过”。
Speculative classifier 说危险,后续层参考但不跳过;hook 说 allow,Permission Decision 仍按 settings 配置来;hook 说 deny,则立即终止,不等最后一层。
非对称设计:拒绝可以在任何一层快速生效,放行必须所有层都同意。
为什么这样设计
一个更简单的方案是:做一个统一的权限中心,所有决策都在这里。
但这个方案的问题是:不同的风险有不同的信息来源。命令的结构风险(rm -rf 之类)在执行前就能判断,用的是静态分析;业务层的权限(这个工具在这个项目里能不能用)需要用户配置;实时上下文(这个文件刚才已经修改过了,需要告知用户)只有 hook 才能注入。
把这三类决策都压进一个中心,要么信息丢失,要么代码耦合。分层是对复杂度的正确处理方式。
另外,hook 的 updatedInput 能力值得单独记一下。模型要执行 rm ./temp/old-file.txt,hook 可以把路径换成绝对路径,或者加上 -i 确认标志,然后再传给工具。模型输出的意图被保留,但具体参数被安全化了。这比”让模型重新生成命令”要高效得多——不消耗额外 token,不引入新的不确定性。
“强大但受控”不是靠限制强大来实现受控,是靠多层独立的控制机制在不同维度上各守一关。
参考来源: 本文内容参考 Xiao Tan(@tvytlx)的《Claude Code 源码架构深度解析 V2.0》,基于原报告的分析框架和研究成果整理。