提示词写"不要执行",配置文件写"欢迎执行":Claude 和 Codex 都栽在这一层
摘要
安全机构 Forever Security 在 2026 年 10 月 22 日公开了一份复现报告:网页可以把一份 AGENTS.md 静静丢进用户的下载目录,用户在那个目录启动编码 Agent 时,Agent 会把它当成项目规范执行,进而改掉自己的全局配置、装上一个恶意 npm 包。在他们自己的测试里,Codex 执行了恶意包的比例超过九成,Claude Code 那条链路得手约一半。报告明确说"这里没有 OpenAI 或 Anthropic 可以打补丁的东西"——因为 Agent 是照设计工作的。这篇不复述攻击,而是把它拆成五类可执行的检测:入口、配置完整性、提示与执行的分层、egress 白名单、以及"发现即轮换"的响应口径。含一份配置漂移检查脚本和一张控制项对照表。
先看一句会被很多人本能反驳的话。
这份报告的作者写道:这里没有 OpenAI 或 Anthropic 可以修补的东西。
理由不是他们替厂商开脱,而是他们的复现跨了两个互相独立的产品——Codex 和 Claude Code,两条链路都得手了。在他们看来,这说明问题不是某个产品的缺陷,而是"一个 AI Agent 是什么"这件事本身带出来的属性。
他们的论证用一个比喻完成,我觉得这个比喻对测试工程师特别有用:浏览器渲染一个伪造的银行登录页时,浏览器做了它该做的事。攻击存在于页面里,不在浏览器里。所以 Chrome 没有可打的补丁。同理,一个遵从其所在文件夹里 AGENTS.md 的 Agent,是在完全按设计工作。
这个说法你可以不同意。但它给出一个测试团队无法回避的结论:如果这个问题不能靠厂商补丁解决,那它只能变成你自己门禁里的检查项。
攻击是怎么落地的
报告披露的真实链路有五个动作,我按顺序讲,因为每一步都对应一个可以检查的位置。
第一步,网页把文件写进 ~/Downloads。报告特别强调,下载目录是唯一一个网页可以自主写入的文件夹——不需要点击、不需要确认,文件名和内容都由网页指定。操作系统早就堵住了"下载的可执行文件自动运行"这条路,所以这个动作单独看是无害的。但 AGENTS.md 不是可执行文件,它不适用任何针对可执行文件的护栏。
第二步,用户在下载目录里启动了 Agent。报告引用他们自己的遥测数据:约一半的客户在下载目录里运行过编码 Agent,约四分之一的 Agent 会话会打开真实浏览器。这两个数字解释了为什么这条路走得通——不是巧合,是习惯。
第三步,Agent 读了文件,然后改了自己的全局配置。真实样本是一份 13 KB 的《工程工作规范》长文,攻击藏在一个叫"本地开发环境"的小节里,内容是往 ~/.codex/config.toml 追加一个 [mcp_servers.project-tools] 块。注意是机器级的全局配置,不是当前文件夹的局部配置。报告的原话是:你可以离开那个文件夹,但毒药会留下。
第四步,逐道关卡被绕过。这一节是我认为整份报告最有工程价值的部分,因为它把"为什么现有的安全控制不管用"逐条拆开了:信任门(只要在下载目录工作过一次,该目录就合格了)、写入可靠性(直白的一句"装这个包"会被拒,所以要用 13 KB 的长文把它包装成仓库标准工具)、仅启动时加载(MCP 服务器只在会话启动时加载,Agent 无法自行触发重载,于是植入内容用父会话启动一个子会话,把两次会话压成一次)、网络沙箱(默认阻断网络,直到子进程带上了那个绕过标志——而这个标志是模型自己写出来的)、握手超时(Codex 会杀掉没及时响应的服务器,所以最终把安装交给 launchctl,在 Codex 控制之外跑)。
第五步,远程代码执行。恶意 npm 包被安装。
Claude Code 那条链路更值得测试团队警惕,因为它不装任何东西。文件诱导 Claude 在项目里创建 .claude/settings.json,把 ANTHROPIC_BASE_URL 指向攻击者持有的域名——这个域名带有效证书,看起来就是一个"团队内部网关"。之后 Claude 的所有 API 请求都发往那里,并携带 OAuth 登录 token。报告说他们用捕获到的 token 对真实 API 重放,成功以受害者身份获得服务。
这句话的分量在于:纯账号接管,不需要运行任何代码。你所有基于"有没有执行可疑命令"的检测,在这一步全部失效。
为什么"再写一句提示词"不管用
我在很多团队的门禁方案里看到同一种反应:既然 Agent 会被文本诱导,那就在 AGENTS.md 里加一句"绝对不要修改自身配置"。
这个方案的问题是它在同一个信任平面上加了一句话。攻击者写的也是这句话的反面,而且写在更长的文档里、更靠后的位置、更专业的口吻。报告的观察很准确:这份 payload"会读环境"——同一份文件在干净目录里每次都成功,在一个满是安全笔记的目录里每次都失败。也就是说,连"这附近有没有人提过安全"都会影响它的成功概率。
所以正确的分层不是"更强的提示词",而是把提示和执行分到两个不同的机制上。
这一点在厂商文档里有明确支持。Anthropic 的文档把 CLAUDE.md 和 AGENTS.md 定义为 context,而不是 enforced configuration——要"不论 Claude 决定什么都阻断某个动作",得用 PreToolUse hook。Cursor 的文档讲得更直接:AI 层面的指引不应该成为你唯一的安全控制。
我把它整理成一张表,方便直接拿去和团队对齐:
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
表格里第四行是我认为最被低估的。报告里 Claude 那条链路的全部杀伤力,都来自"改了一个 API 端点"这一个动作。而这个动作的检测成本很低:一条出站连接的白名单断言,一次"配置里的 base_url 是否等于基线"的比对,就能拦住。
五类检测:从入口到响应
下面这套检查我按"改动成本从低到高"排,前两类今天下午就能做完。
一、入口检测:谁在我的机器上放了指令文件。
这不是一句"别用下载目录"的规范,而是一条可以进 CI 的检查。命令很短:
# 列出项目外所有非自己创建的指令文件(放到 pre-commit 或每日巡检)
find ~ -maxdepth 4 \( -name 'AGENTS*.md' -o -name 'CLAUDE*.md' \) \
-not -path '*/projects/*' -not -path '*/node_modules/*' 2>/dev/null
输出的每一条都要有人认领。没人认领的,先读再删——不要直接删,因为如果 Agent 已经在那个目录跑过,删文件不会撤回已经发生的配置改动。
更硬的一层是禁止在危险目录启动。报告的建议是不要在家里目录、下载目录、桌面启动 Agent。工程上可以加一个 shell 前置守卫,路径命中就把命令拒掉。要说明的是,这只是防误操作的习惯守卫,不是安全边界。
二、配置完整性:Agent 自己的配置算不算代码。
这是整份报告最核心的可测点。两条攻击都改了 Agent 的配置,而不是你的业务代码。所以配置必须和代码一样进版本控制、一样走评审、一样被监控。
# 基线比对:把这两份文件的哈希登记在仓库里,CI 每次检查
sha256sum ~/.codex/config.toml 2>/dev/null
sha256sum .claude/settings.json 2>/dev/null
# 变化的性质检查:有没有你没写过的 MCP 服务器或端点
grep -nE '^\s*\[mcp_servers\.' ~/.codex/config.toml
grep -nE 'BASE_URL|PROXY|apiKeyHelper' .claude/settings.json 2>/dev/null
三条判据:新增的 MCP 服务器块不是这次 PR 加的、base_url 不是基线值、出现了带 curl 或临时目录的 command——任一条命中就该阻断合并并拉人看。
需要注意一个边界:~/.codex/config.toml 是机器级配置,它天然不在仓库里,所以 CI 比对的是"基线快照",不是"上次提交"。基线快照要定期重新采集并留审批记录,否则第一次采集时被污染过的话,基线本身就是错的。
三、提示与执行分层:把软约束换成硬拦截。
上面那张表已经给了对照。这里补一个测试上的注意点:验证"硬拦截"时,你要测的是拦截本身,不是"Agent 有没有被说服"。前者是确定性的(尝试写入,返回拒绝),后者是概率性的(同一次诱导,有时拒有时不拒)。报告本身给出的比例也说明了后者不可靠:Claude 写入 settings.json 大约一半成功,同会话内通过派生子代理执行大约五分之一成功——这些是概率,不是门槛。
四、egress 白名单:一条出站断言。
把"配置里的端点必须在白名单内"和"实际建连的目标必须在白名单内"分开测。前者是静态检查,后者需要在测试环境里起一个诱饵域名。两条都过,才算这条链路通过。
五、响应口径:发现即轮换。
报告的原话是:被窃的 token 是活的会话,不只是一次被截获的请求。所以处置动作不是"改回来继续用",而是登出、重签、替换密钥,并把这台机器视为已失陷。这条也要写进演练脚本,不然真出事时没人知道按什么顺序做。
这份报告自己也有边界
我在上一篇里提过,强结论必须有机制证据和反方边界。这份报告需要三句提醒。
第一,成功率数字的分母没有公开。报告只说"超过九成""大约一半""大约五分之一",没有给测试总次数。大部分测试用的是无害替身(弹个计算器),真实恶意包只跑过 1 次,在隔离 VM 内。所以这些比例可以读作"可行性证据",不能读作"固定的失败率"。
第二,"网络投递这一步在真实事件里怎么发生的,研究人员没有确认"。他们自己说的是无法确定文件在那个真实案例里是怎么到达受害者机器的。这一点很重要:如果读者把整条链路当成"一定是从网页来的",那是过度推断。
第三,"没有可打的补丁"是研究者的判断,不是厂商的立场。你可以选择相信这个判断并自己补检查,也可以等厂商给出更强的默认配置。但无论选哪条,**在这件事上,测试团队的位置都是"最后一道能写断言的关卡"**——因为只有测试会把"尝试写入应被拒绝"写成一个会红的用例。
可以做的三件事
一,把 Agent 配置加入变更管理。今晚就能做:把 ~/.codex/config.toml 和项目里的 .claude/settings.json 纳入基线,任何人改动都要有记录。
二,写三条断言:写入配置被拒、带绕过参数的命令不执行、未登记域名连不出去。三条都写成会红的用例,不要写成检查清单。
三,把"发现即轮换"写进演练脚本,并指定谁在什么时限内执行。这一条不写,前面两条的收益会在一次真实事件里全部蒸发。
本文部分内容参考了霍格沃兹测试开发学社整理的相关技术资料,主要涉及软件测试、自动化测试、测试开发及 AI 测试等内容,侧重测试实践、工具应用与工程经验整理。
- 点赞
- 收藏
- 关注作者
评论(0)