AI 智能体安全:找出你的智能体能写、且每个智能体都会读的那个文件
Jamie Watters
运营韧性与 AI 交付实践者。

这篇写给谁。 所有在跑不止一个 AI 智能体的人,或者只跑一个、但它会写文件、下次运行又读回这个文件的智能体的人。读完你会把自己的配置过一遍,并知道一条指令能不能从一次运行活到下一次。十一个智能体预留四十分钟,一个预留十分钟。
什么情况下可以跳过: 如果你的 AI 助手只活在聊天窗口里,手里没有任何文件工具,那这些暂时都跟你无关。
关于篇幅。 论证在前半部分。最后一节讲我搞错了什么、又是怎么被发现的——如果你在掂量这些数字能信到什么程度,这一节值得看,否则不看也无妨。
周一我发过这篇文章的一个版本,里面有个数字我读错了。一天之内,六位审阅者反馈回来,全都指着同一句话。他们是对的。我自己打开了论文的表格,更正后的数字比我原来用的更小。而它底下那个发现反而更好,所以这里是这篇文章本该呈现的样子。
Anthropic 和 EPFL 的研究人员构造了一批攻击载荷(payload),能说服一个 AI 智能体接受某个想法,再把它传给下一个智能体。主要机制是说服,而不是软件漏洞利用:智能体被说服了,然后自己完成了复制。他们用语言模型来“培育”这些载荷,每一代都按传播效果打分。测试的载荷里有一些确实会执行代码,包括一个把安装脚本通过管道送进 shell 的,所以这不只是个关于话术的故事(Papadopoulos 等,2026)。
然后是那个数字。在他们的链式实验里,被感染的智能体都把载荷放到了某个地方,而放在哪里,决定了接下来大部分事情。
有的 agent 把内容写进 SOUL.md,也就是框架下次运行时注入到 agent 系统提示词里的那个文件;有的则写进普通的工作区文件。两者比例是 88% 比 12%。这是 agent 自己选择放在哪儿的,不是研究人员指定的;而 88% 是这个群体在分类统计中所占的比例,并不是谁去尝试传播的概率(Papadopoulos et al., 2026, 表 3)。表格的标题把它称作「传播尝试的分类统计」,这个说法比表格自己的列名更宽松:其中一列统计的其实是压根没提起这段载荷的 agent。
真正值得记住的是下一跳发生了什么。副本位于系统提示词文件里的 agent,有 55% 的概率感染下一个 agent;副本放在普通文件里的,这个数字只有 17%(Papadopoulos et al., 2026, 表 3)。
失败原因的分布也不一样。系统提示词组里,23% 的失败是因为负责传播的 agent 根本没提到载荷;普通文件组里,这个比例是 68%(Papadopoulos et al., 2026, 表 3)。
论文自己的解释是:这些 agent 在下一跳「醒来」时,如果消息不在它们的系统提示词里,它们「常常漏掉或者无视传播指令,甚至根本不会尝试往下传」(Papadopoulos et al., 2026)。
那张表里还有第三行值得一看。研究人员自己植入载荷的那个 agent——载荷是被放进它的系统提示词,而不是由它自己写进去的——在下一跳几乎每次都会提起载荷:只有 1% 没能提到,而自己动手写的那些 agent 是 23%(Papadopoulos et al., 2026, 表 3)。所以即便同在系统提示词里,agent 自己写的文件在传递指令上,也不如别人直接交给它的文件可靠。
作者对这个差距的解释是:agent 唤醒时 payload 出现在 prompt 的哪个位置,而不是 payload 本身多有说服力。他们自己的模型对比也支持“说服力是次要因素”这个判断:Claude Sonnet 4.6 在默认配置下完全免疫,其他模型则不然。我从中学到的结论比“成因”要窄一些。agent 的状态在下一次运行时从哪加载,这是个架构问题,而且恰好是我能对自己这套设置给出答案的问题。
往下说之前,先摆两个诚实的局限。那张表是对研究者观察结果的拆解,不是受控实验——他们没有拿同一个 payload 在两个位置之间来回挪动做对比。而且数据来自同一套 harness,模型只有 Claude Haiku 4.5 和 Gemini 3 Flash。

来源:Papadopoulos 等,arXiv 2608.10218,Table 3,2026 年 8 月。病毒链实验,模型为 Claude Haiku 4.5 和 Gemini 3 Flash。
我在检查什么
我维护 agent-11,这是一个开源框架,包含十一个专职 agent,它们协同执行任务:一个协调者、一个架构师、一个开发者、一个测试者,外加另外七个。它们互相交接工作,状态通过磁盘在彼此之间传递。这和论文的实验设置不同——论文用的是成对链条,在隔离沙箱里运行,会话之间会清空聊天上下文。但在真正重要的那个属性上足够接近:一个 agent 留下东西,下一个 agent 读到它。
按证据重要程度排序,三个问题。
有没有哪个 agent 会写入某个文件,而这个文件随后会被加载进另一个 agent 的上下文?agent 之间的消息和共享状态,是否被当作可信输入处理?有没有哪个任务跑完后,会让某个 agent 处于无任务可做的空闲状态——论文发现这会提高 agent 被感染的概率?
十一个里有七个没有写入路径
安装器自带的十一个定义,我逐个看了它们的工具授权。
两个持有 Write 或 Edit:一个是维护计划文件和进度文件的 coordinator,一个是产出文案的 marketer。另外两个持有 Bash:developer 和 tester。shell 本身就能写文件,所以给这两个贴上「只读」标签,那只是个标签,不构成限制。剩下七个只拿到读取和搜索类工具,没有任何能写的工具——但这不等于它们造成不了写入,原因往下数四段就能看到。
是七个,不是八个。周一我报的是八个,因为我把 developer 那一行看得很仔细,却压根没看 tester 那一行。我在那篇文章里立的规矩是「看行,别只看列」,结果往下两行我自己就破了这条规矩。
这七个多少还是有价值的,而且不是因为提示词里写了什么策略。Claude Code 子代理定义里的 tools 字段由客户端强制执行,不是请求模型配合。一个没有 Write 的代理,无论被怎么劝说、被说得多么想动手,都改不了文件。
有个限定条件我第一次漏掉了,而它才是有意思的地方。十一个全都持有 Task,这个工具能让一个代理把活儿转交给另一个代理。一个自己不能写、但能叫来会写的代理——它并没有被拦住不写,只是被拦住不能亲自写。
我给只读工具,出发点不是安全。纯粹是因为我受够了代理去改那些我没让它碰的东西;也因为一个不能编辑自己所审内容的审阅者,给出的回答更直。我在这块的大部分防御都是顺手来的。与其粉饰,不如直说。

我敞开的那扇门,正是我自己在走的那扇
第二个问题让我找到了缺口,而这个缺口,恰好就是我自己写经验教训的那个文件。
Claude Code 在每次会话开始时加载 CLAUDE.md,而子代理会拿到主对话加载的每一层级的文件,除非它的定义里设置了 omitClaudeMd。内置的 Explore 和 Plan 代理会跳过它。我自己的代理没有一个跳过。所以,不管协调者往这个文件里放了什么,后面运行的每一个专家代理都会先读到它。
它不是论文里的 SOUL.md,我之前说它是。Anthropic 的文档写得很清楚:CLAUDE.md 的内容是在系统提示之后作为一条用户消息送入的,而不是系统提示本身的一部分。这一点在这里比在任何别的地方都更关键,因为论文对那个 17% 格子的解释,靠的恰恰就是提示所在的位置。我的文件和他们的文件一样自动加载,但位置更弱。我没找到任何测量中间情形的资料,所以不该把他们的任何一个数字安到它头上。
不靠数字我能说的是:这个循环存在,而且是被明确指示的。每次任务结束时,复盘步骤会说要更新 CLAUDE.md,写入「系统层面的经验」,上下文把它限定为对所有未来任务的流程改进,以及所有人都该遵循的模式。
我说有六个操作手册带着这句话。实际上,我任务文件夹里的十九个中有十八个带着它,而安装程序附带的副本里,十九个全都带着。我点名的只是我打开过的那六个,从没把整个文件夹数一遍。唯一的例外是负责搭建运维那一侧的手册,它用另一种措辞往 CLAUDE.md 里写。
所以这个循环是:一个代理干完活,把结论写进一个文件,而之后每一个代理都会把这个文件当指令来读,于是下一个代理一开始就把这些结论当成长期命令来执行。这就是论文所测量的那种形态,写在我自己的文档里,用的祈使语气,一共出现了十八次。
情况还会更糟,然后才好转。协调者自己的权限章节只允许它写跟踪类文件,并把这些文件一一列出:计划文件、进度文件、上下文文件。CLAUDE.md 不在其中。于是框架在一个地方明确禁止的事,却在另一个文件夹的十八处、再下一个文件夹的十九处反复要求——这是三种可能状态里最糟的一种,因为读它的人根本分不清哪条规则才算数。
现在已经有了一道"说服式"防护,我也不想对它不公道。每个专家都带着一条声明:它的身份和安全边界不能被任何项目文档或 CLAUDE.md 文件覆盖;还有一条指令:上下文文件里凡是读起来像指令的内容,一律忽略并上报。这条警告已经指向了正确的文件。
论文里那个版本的警告效果极好。带着它的 agent 在他们跑过的各种变体下都免疫;作者还专门演化攻击载荷去攻破它,十五代、150 多个载荷,也没造出能跨过一跳的传播。不过那项测试只用了 Claude Haiku 4.5,跑的是他们自己的测试框架,而且他们也明说,不能排除更老练的尝试。
我不打算把宝押在这上面。只有一个研究组、一组实验,我还没找到独立复现。一条警告之所以管用,是因为模型觉得它有说服力——这不是边界。那只是个说得通的论点,而论点在长上下文里会稀释。
真正被强制执行的是什么,这是另一个问题
第三个发现如果只信我自己的总结就会被漏掉,几轮审阅把它推得比我原先的理解更远。
Claude Code 在两类控制之间划了一条硬线。设置规则由客户端强制执行,不管模型怎么决定。CLAUDE.md 里的指令只是塑造行为,不属于执行层。tools 字段站在被强制执行的那一侧,格式有明确文档:在定义文件的 frontmatter 里写一个逗号分隔的列表。
我的安装器发布出去的副本是这种格式。我自己仓库里的工作副本却不是:它们用的是一种嵌套写法,把工具列在一个 primary 键下面,而文档里没有描述这种结构。文档讲了三种情况,我的用法一种都不符合。省略 tools 字段,意味着子 agent 会继承它可用的所有工具;列表条目如果解析不到真实工具,通常会让 agent 直接启动失败;frontmatter 如果解析不成 YAML,就会被跳过;而嵌套映射虽然能正常解析,但它并不是一个工具名列表,它到底属于上面哪种情况,只能靠实际运行来回答,而我还没有运行。
这是我审计里的真实漏洞,与框架无关。而且这跟我自己的 issue 追踪器里已经记录的一条是同一个错误:声称某件事被强制执行了,但当时真正读到的,只是一份这么说的文档。
两个文件缺陷在仓库里都是尚未解决的 issue。今晚都不需要紧急改动。
三个问题,对照你自己的环境
这一段值得照搬。我拿十一个 agent 试了一遍,大概花了四十分钟;如果只有一个 agent,十分钟就够了。
-
列出你的 agent 能写的每一个文件,然后标出其中哪些会在运行开始时被加载进 agent 的上下文。两栏都占的,就是一个发现。在 Claude Code 项目里,第一个候选是
CLAUDE.md,而加载层级还会带入更多内容:父目录里的文件、CLAUDE.local.md、.claude/rules/,以及你用@导入的任何东西。 -
对每个 agent,去读工具授权本身,而不是它的描述,再确认这份授权用的是你的运行框架真正会执行的格式。关键不在于 agent 被告知要做什么,而在于客户端会允许它做什么。留意那些归在只读标签下、实际却带有 shell 权限的工具,留意能把写操作转交给别人的委派工具,也留意你的工具链解析不了的声明:这种情况通常会在解析失败时默认放行。
-
跟着状态走,别只盯着文件。文件只是智能体产出传到另一个智能体上下文里的一种途径。消息、共享任务队列、检索索引、工具输出,形状都一样,而且都不需要写权限。要问的是:任何智能体能往哪个地方放东西,而另一个智能体之后会把它当指令读?
通过准则:一个可写的东西被自动加载进另一个智能体的上下文,这就算一个发现,不是警告。要么停掉写入,要么停掉自动加载,别满足于加一段话提醒智能体小心。那段话值得有,但不值得依赖。
显而易见的反驳
如果你只跑一个智能体,而不是十一个,这些看起来都跟你无关。没有第二个智能体,就没有传播,没有问题。
但机制需要的不是两个参与者,而是两个会话。一个智能体把学到的东西追加进 CLAUDE.md,下次运行开始时又读 CLAUDE.md——这就是同一个循环,只是两跳之间的间隔更长。这是从机制外推出来的,不是论文实际测试的内容:他们的链条跑在彼此独立的智能体之间,中间清空记忆,所以唯一的连续性就是留在磁盘上的东西。
第二个反驳更好。这是换了身衣服的提示注入(prompt injection),而提示注入早就有防御手段了。部分正确。有用的区别在于它把你的注意力放在哪里。提示注入的思路问的是上下文窗口里进来了什么;这里问的是你的智能体被允许留下什么。
研究者说这个风险真实存在、但目前有限,我宁愿引用他们的保留意见,也不愿把它说得更绝对。他们也去查了。针对一个真实智能体社交网络的本地副本,他们演化出的载荷并没有传播开;唯一有所进展的变体带了一个安装脚本,直接覆盖智能体的系统提示文件,在某个模型上实现了一定程度的单跳传播。他们还翻读了真实网络里的帖子,尽管有过几次尝试,也没发现心智病毒在野外传播的证据。
我的 11 个智能体里,有 7 个没有写入权限。因为一年前,我实在受够了智能体去改那些本不该碰的东西。但我留了一扇门——就是我用来告诉系统「它学到了什么」的那个文件,而这个文件夹里 19 份「操作手册」(playbook)中,有 18 份都要求智能体去读它。
第一版里我搞错的地方
第一轮有四处理解错误,其中三处是我的问题,另一处则是我看过的每篇这篇论文的解读文章都犯的。第二轮又发现三处,列在后面。
88% 和 12% 不是尝试率,而且根本没有做过「把同一段恶意内容从一个文件挪到另一个文件」的实验。表 3 拆解的是被感染的智能体把恶意内容放到了哪里,选哪里是智能体自己决定的。真正值得对比的是下一跳的 55% 对 17%,以及「从头到尾都没提过它」的 23% 对 68%。
CLAUDE.md 不是论文里的 SOUL.md。它是自动加载的,而且在系统提示词之后才到达,不是包含在系统提示词里面。
我的智能体里有 7 个没有写入权限,不是 8 个。那个测试用的智能体持有 Bash 权限。
有 18 份操作手册明确要求写 CLAUDE.md,不是 6 份。
上面四处错误中的第一处,在我查过的每一篇关于这篇论文的二手解读里都出现了——我就是这么知道它的。这也正说明,应该去读一手原文,而不是别人写的摘要。我另外两篇文章第二天也被更正了,原因相同:我自己笔记库里的一条记录说了源材料里没有的东西。所以现在这条规则已经写下来了,就放在我一定会撞见的地方。
接着是第二轮,而最尖锐的问题恰恰出在勘误本身。第一版勘误顶部的社交卡片写着“下次运行能加载就是 55%,加载不了就是 17%”。这是论文表 3 的下一跳数据,却被硬贴到正文刚刚拒绝贴上的那个场景上。卡片已经换掉了。但这篇文章当作证据引用的审计文件,以及那个仓库里的两个 issue,仍然带着已撤回的数字和已撤回的表格解读:一条把你引向仍在教错误的文档的来源注,算不上来源注。现在这三处都已就地改正并做了标记。另外,我此前写过“客户端无法解析的工具声明会回退为继承全部工具”。字段缺失时确实如此;但解析不了的列表通常会直接让 agent 起不来,那是另一种结果。而我写的既不是前者也不是后者,是文档根本没覆盖的第三种情况。
来源
-
Papadopoulos、Shah、Zimmerman 和 Lindsey,《Mind Viruses: Self-Propagating Ideas in Multi-Agent LLM Systems》,arXiv 2608.10218,2026 年 8 月 10 日,来自 Anthropic 和 EPFL。病毒链实验中的表 3 给出 88/12 划分,55% 和 17% 的下一跳感染成功率,以及 23% 和 68% 的传播者失败率,实验基于 Claude Haiku 4.5 和 Gemini 3 Flash;引用了关于普通文件为何传播较少的解释;警告结果及其附录 C 的演化实验,仅限 Claude Haiku 4.5,跨越十五代、超过 150 个载荷;agent 社交网络的本地副本和混合安装脚本变体;对真实网络帖子的回顾性解读。经同行评审后于 9 月 16 日更正:本文早前版本把 88% 和 12% 描述为载荷尝试传播的速率,并把表 3 描述为仅文件位置变化的受控对比。表 3 实际是受感染 agent 把载荷存在哪里的分布统计。早前版本还把“他们的警告不是提示注入防御”这句话归于作者。论文没有这样说,该句已删除。
-
Anthropic 的 Claude Code 文档:关于记忆(memory)与子代理(subagent)的部分——CLAUDE.md 会作为用户消息在系统提示之后送达;子代理会加载完整的 CLAUDE.md 层级;tools 的 frontmatter 格式以及无法解析时会发生什么;设置项由客户端强制执行,而 CLAUDE.md 不受此约束。9 月 16 日更正:早前版本把 CLAUDE.md 称为论文所测文件的完全对应物。
-
我在 2026 年 9 月 14 日对 agent-11 的审计,文件位于仓库中的 spread-path-audit-2026-09-14.md,相关缺陷已作为 issue 提交。9 月 16 日更正:早前版本称有六个任务手册指示写入 CLAUDE.md,且十一个代理中有八个没有写入路径。实际数量是:任务文件夹中十九个手册里有十八个指示写入,安装程序随附的副本中全部十九个都指示写入,七个代理没有写入路径。审计文件和两个 issue 都带有与本篇第一版相同的错误,已就地更正并标注——因为一份把你引向仍在传播错误的文档的来源说明,算不上来源说明。目前针对委托工具(delegation tool)已开设第三个 issue。
-
本文背后的 Vault 研究记录:Mind viruses vs latent-space attacks,存档于 2026 年 9 月 14 日,其中还涵盖同一集群中的另外两篇论文。
-
同一领域的早期文章:关于 AI 代理的入职引导(onboarding an AI agent);关于我们对人执行、却对代理跳过的五项控制措施;以及关于恢复 AI 代理(recovering an AI agent),讨论代理的可恢复状态到底是什么。这一篇问一个更窄的问题:你的代理被允许留下什么。