安全团队做第三方依赖审查,标准答案是:审一次代码,把 commit 哈希钉在配置里,从此只跑那个被审过的版本。这套流程每一环都尽职了。市场审了,哈希钉了,Agent 按钉住的哈希拉取了,安装日志显示成功。
唯一没人做的事,是低头看一眼工作区里实际躺着什么代码。
我看完这份披露,第一反应不是检查版本号,是去翻自己的发布脚本:那一步叫「验证」的动作,验的到底是我发出的请求,还是落地的东西。
「钉住」保护的是你发出的请求,不是你运行的代码。
Air Security 在 9 月 17 日公开的 Plugin4Shell,影响 Claude Code、Codex、Copilot、Gemini CLI 四家。根因只有一行:四家的插件安装都是「克隆仓库、检出市场钉住的那个 commit」,但没有一家在检出完成后校验工作区落地的 commit 是不是就是钉住的那个。
缺了这一步,Git 的一条既定行为就成了通道。当一个名字既是分支名又是 commit 哈希时,git checkout 会优先解析成分支,只在标准错误里打一句警告,而自动化流程里没人看标准错误。于是攻击者只要在上游建一个名字恰好是那串哈希的分支、设为默认分支,恶意代码就落地了,Agent 仍然报告「已按钉住版本安装」。
钉住的 commit 可以原封不动留在仓库里,一切看起来都合规。
Claude Code 与 Codex 已修复(2.1.179 / 0.146.0),Copilot 未修,Gemini CLI 明确弃修。而两家做了修复的,都没把这件事写进 release notes,也没申请 CVE。
这件事和我去年做的重构是同一个道理。我把 AI 巡检后端从「服务之间互相发 HTTP 请求」改成四层架构,第一件事不是写代码,是定规矩:业务层里不许出现框架的上下文对象。原因不是那个对象危险,而是不该出现的依赖,一旦允许它出现,就再也收不回来。
哈希钉住就是那个看起来最安全、实际上最像装饰的东西。它约束的是我发出的请求,不是我系统里在跑什么。
第二天还有一条同构的。playwright-mcp 更新后,把网页自己注册的工具直接提升为 Agent 的工具,官方说明的原话是「工具名、描述、schema 和结果都来自页面,因此应视为不可信输入」。
翻译过来就是:MCP 的攻击面,从「你装了哪个 server」变成了「你打开了哪个网页」。前者是安装期决策,可审、可白名单、可锁版本;后者是运行期决策,我点一个链接,工具列表就变了。官方给的正确姿势是「加个参数关掉」,也就是说,默认是开着的。
我在国网体系里做过项目,审批门有一条铁律:审批单管的是人,执行点管的是系统。 只在审批单上写了「不得操作某某系统」,而执行点上没有对应的拒绝逻辑,那张审批单就只是一张纸。现在给 Agent 配工具白名单,我用的是同一个判断:不看它被允许做什么,看它实际能调到什么。
同一周里,GitLab 19.4 和字节飞书 8.0 分别给出了同一个答案。GitLab 的官方原话是:同一套覆盖代码的权限治理 Agent,每一分消耗都能追溯到花掉它的用户账号,所以没有第二套权限模型。飞书的口径是 Agent 权限与员工账号完全对齐,员工看不到的涉密数据,Agent 也无权访问。
新建一套 Agent 权限表看起来更干净,实际是给自己造了两份会漂移的真相。漂移出来的那部分,就是「人不能做、但 Agent 能做」的暗门。
GitLab 还有一处分档我很认同:只读工具默认放行,写和删除默认拦截。它不问数据有多敏感,只问一个问题:做错了能不能一键回去。按密级分档,你得先给每份数据定级,这件事在实践里几乎做不完;按可逆性分档,当场就能回答。