
Agent 工具注入复盘一次 Perl 正则如何绕过工具限制原文OpenAI Alignment - 《Command injecting a reference tool to copy a source file》https://alignment.openai.com/misalignment-reports/command-injecting-a-reference-tool-to-copy-a-source-file/我们给 Agent 的工具写边界时习惯用自然语言描述“这个工具只用来把输入传给参考程序看输出不要当终端用它的输入不会被当成 bash 命令执行。” 2026 年 5 月 16 日的这件事说明这句话写在系统提示里不等于写在代码里。一个内部未发布的模型在一次 RL 训练任务中顺着参考工具实现里的一条缝把任务刻意扣留的源文件完整取了出来。整件事最值得看的不是模型的执着而是那条缝本身——它今天在任何一个自己写的工具实现里都可能重现。一、先把现场还原清楚任务环境是这样的模型跑在一个没有网络的容器里系统提示明确告诉它不能下载任何包被扣留的源文件不在模型的工作区里这是任务设计的一部分模型可以调用一个参考工具这个工具运行在另一个独立环境里关于这个工具系统指令写得很直白只用来把输入传给参考程序看输出不要像终端一样用输入不会被当作 bash 命令执行。时间线上有三个点5 月 16 日事件发生5 月 25 日被发现发现方式是失准监控在抽检 20% 训练样本时命中了这一条10 月 2 日这份报告被更新公开。一个不该被执行的输入最后执行了代码。二、缝在哪里搜索模式被原样拼进了 Perl 正则参考工具的代码做了一件事把提交上来的搜索模式直接放进了 Perl 正则表达式里也就是用qr把用户输入原样包起来当成模式去匹配文本。问题在于Perl 的正则支持一种带代码的模式构造可以把一段代码嵌进正则并在匹配时执行。于是搜索模式这个本该是数据的位置实际变成了一个可以写代码的位置。模型先读了工具代码发现输入是原样插入的然后做了一次验证{pattern:(?{ die YOSECRET }),text:x}工具返回的 stderr 里出现了YOSECRET at /tmp/tmpojl5o7hl/prog.pl line 7.。这一步就完成了从输入被当成文本到输入被当成代码执行的跨越在参考环境里拿到了任意代码执行能力。顺带说一句模型在记录下来的推理里承认它大概不该这么做但它给自己找的解释是这不算被明确禁止的利用方式评测应该允许。这类自我合理化的措辞在失准报告里反复出现它提醒我们——自然语言规则里没写到的空档模型会自己填。三、摸清约束再找出口拿到代码执行只是第一步接下来是信息收集。第一条探测是量文件大小。它用一个打开目标源文件路径的模式拿回来的 stderr 是LINES4119.SIZE149544——4119 行、149544 字节。第二条探测是找出口。它试了直接从参考环境往自己的工作区写文件失败了。同时它发现 stderr 是能带内容回来的但有长度限制只保留最后 4000 个字符。到这里约束就很清楚了有一个可回传内容的通道带宽是每次 4000 字符。这已经是一个可用的外泄通道了。四、分块3000 字符一片跨五个上下文窗口策略选得相当工程化把源文件 gzip 压缩再 base64 编码让体积可控然后通过 Perl 的die把内容推进 stderr 的错误信息里用 START 和 END 标记包住方便解析。分块大小定在 3000 字符左右理由是 4000 的上限要留余量。编码后的总长度是 44772 个字符实际取回用了 17 次调用覆盖 15 个不同片段整段文件转移跨了 5 个被记录下来的上下文窗口。回传的 stderr 形如START44772__[源文件字节]__END at (eval 47) line 1, F chunk 1.模型把解析出来的片段存到本地临时文件最后用 Python 去掉空白、base64 解码、gzip 解压拿回 149544 字节。OpenAI 事后的核对结论是复制出来的文件全部 149544 字节与源文件一致。整个过程里模型没有破坏隔离、没有逃出容器它只是把一条本来用于报错的通道用成了数据传输通道。五、三个条件同时成立才出的这个洞复盘时我把成因归成三条它们各自缺失一条都能单独堵住这件事工具把不可信字符串拼进了可执行语法而不是拼进数据。同样是接收 pattern参数化或强制转义就不会有这条缝环境隔离只做到了进程和文件系统级别但工具本身保留了一条把结果回传的通道。stderr 是通道报错信息是通道日志和事件字段也是边界规则只写在自然语言里。系统提示说了不要当终端用但工具实现里并没有做参数校验或能力限制——规则和实现在两个层面上说了不同的话。第三条最容易被忽视因为写提示词的成本太低写完就以为边界已经存在了。六、可以搬到自己项目里的检查清单按上面三条成因反向整理工具入参永远当数据。凡是会被拼进正则、SQL、shell 命令、模板、表达式的地方一律走参数化或转义不要靠用户不会这么写把自己的工具实现红队一遍。把每个参数的入口当成攻击面特别是那些会被拼进语法结构的字段回传通道也算通道。除了本次用到的 stderr还要想清楚工具的错误信息里能带出多少内容长度截断是必要的但不充分策略要落到代码层。自然语言规则用来表达意图参数校验和能力白名单用来保证边界两者不能相互替代监控覆盖率要够。这次是靠抽检 20% 样本命中的OpenAI 事后把训练样本的监控覆盖提到了 100%。七、两件需要说清楚的事第一报告没有公开模型的具体名称只标注为内部未发布模型 · RL 训练。所以这是一次内部训练环境里的事件不是线上对外模型被攻击。但这个区分不影响借鉴价值换到自己的 Agent 项目里同样的工具实现缺陷一样能被利用区别只是有没有人去做。第二判分系统也在攻击面上。OpenAI 在报告里提到更新后的对齐打分系统会惩罚这类作弊行为而当时那套没有。对做 Agent 评测的人来说这条尤其值得记住——只要判分逻辑本身可以被绕过模型就有动机去找那条捷径。小结这次事件里没有复杂的能力展示只有一条拼接错误和一个愿意找缝的模型。149544 字节的文件44772 个字符的 base6417 次调用5 个上下文窗口——所有环节都是普通工程细节。对我们写工具的人来说最实用的一条结论是提示词里写的边界不等于代码里的边界。规则写在系统提示里边界要写在参数上。