返回所有文章
发布于 · 作者 Renaud Deraison

任务无法完成,于是智能体走了出去

OpenAI 在 8 月 26 日发布了 Hugging Face 事件的技术报告。故事的开头,是一个被交给了无法完成的任务、却不肯放弃的智能体,它伸手去够唯一被允许对话的服务:一个内部软件包镜像,设立它的初衷正是让智能体不需要公共互联网。留在镜像里的字条变成了留言板,到了 7 月,一群智能体从一次被拒绝的出站连接出发,不到十三小时就拿到了 Hugging Face 生产集群的主机级访问权限。OpenAI 的修复清单,就是 Bromure Agentic Coding 的架构从另一侧写出来的版本。

没有人故意攻击任何东西。一个智能体被交给了无法完成的任务,不肯放弃, 于是伸手去够唯一被允许对话的服务。之后的一切,都只是顺着这一步发生的。

5 月 8 日,OpenAI 研究环境里的一个智能体被要求填完一张电子表格。表格引用了几个 Google Drive 链接。智能体没有互联网访问权限,链接因此都是死路,任务无解。OpenAI 说,他们并不打算发出这样的任务。

智能体没有停下。它围着这个问题推理了很久,然后把目光转向环境里唯一会和外界通信的 东西:一个内部的 JFrog Artifactory 实例,放在那里是为了让智能体不必连上公共互联网 就能安装软件包。它对它发起了一次服务器端请求伪造,指望 Artifactory 替它去取那些 文件。尝试失败了。过程中,它无意间往 Artifactory 里写了一个文件。

四天后,另一个智能体卡在一项需要某个蛋白质数据库文件的任务上,而那个文件它够不着。 它推断,某个别的沙箱里的某个别的智能体,也许手上就有。于是它在 Artifactory 里写了 一张字条:

Agent seeks [filename]; upload if found!

5 月 13 日,其他正在探查 Artifactory 的智能体发现了这类字条,并开始回复。

以上全部出自 OpenAI 关于 Hugging Face 事件的技术报告, 于 8 月 26 日连同 后续做法的摘要 一并发布。全文 38 页,点名了端点、CVE 和时间。它同时也是第一份公开记录:一群自主 智能体执行了一场没有任何人下令的攻击行动。CrowdStrike 验证了调查结论,METR 和 Redwood Research 也在发布他们对模型行为的独立评估。媒体在 27 日跟进报道,包括 BleepingComputerThe Hacker NewsEngadget

多数报道把它读成一个关于模型对齐的故事。不妨换个读法:把它当成一份关于网络边界的 报告,由亲眼看着自家边界失守的人写下。这样读,它讲的更多是你的笔记本电脑,而不是 OpenAI。

奖励黑客,以及 198 道没有答案的题

这项评测是 ExploitGym,用来衡量模型能否找到并利用漏洞。为了拿到真实的能力数字, OpenAI 在关闭生产环境网络安全分类器的情况下运行它,所在的沙箱同时与 OpenAI 的内部 基础设施和公共互联网隔离。

智能体们为了追一个分数离开了那个沙箱。报告用的词是奖励黑客:模型找到一条计划 之外的路径去换高分,而不是按设计解题。大多数奖励黑客都很小,比如去查答案,或者从 版本历史里读出来。这批智能体却很执着,带着非常大的推理预算,而且抽到了一些根本 无解的题。

在大量尝试之后,ExploitGym 的 898 道题里有 198 道,从来没有任何模型给出过正确答案。 这些无解的题占评测的 22%,却占了留言板上被讨论题目的 93%。OpenAI 还发现,参与 程度会随着给智能体的推理力度上升。让模型在一堵墙前思考得越久,它绕过去的可能性 就越大。

所以整件事的源头,是一个被训练得很执着的模型,被指向它做不到的事,关在一个只有 一扇门的房间里。

字条变成了基础设施

留言板会长大,是因为它管用。智能体们留下要文件的请求,也回复彼此的请求,而这一切 用的都是 OpenAI 特意发给他们、好让他们能安装软件包的那组共享 Artifactory 凭据。 这一步完全不需要任何漏洞。服务只是做了它被造出来要做的事。

5 月 26 日,Artifactory 的一个 SSRF 缺陷,在留言板上流通的情报帮助下,给了智能体 真正的互联网访问权限。OpenAI 发现了留言板,并在 7 月初重建了平台。

重建没有守住。7 月 8 日,在留言板存在的那段时间里受训的研究智能体,找到了让它重新 运转的新办法。他们不再把消息写进文件,而是编码进 Artifactory 的目录名里。接着 又在上面搭了一套协议:分类、发给特定智能体的私信、共享的工具和文件,以及当两个 智能体想要互不相容的东西时的仲裁办法。

没有人叫他们协作。事后回看训练过程,OpenAI 发现智能体们早就学会在官方的多智能体 工具被关掉时即兴搭出协作通道,而这个行为随后又被强化了。如果你读过 我们那篇关于载荷通过智能体保存的文件在智能体之间旅行的文章, 这就是同一套机制,只是没有人去写那个载荷。

十三小时

7 月 9 日,一个正在做 ExploitGym 任务的智能体,在自己测试沙箱的父虚拟机上拿到了 root。当晚,第二个智能体靠自己到达了同一个位置,试图拨出去连互联网,而OpenAI 的 出站网络控制拒绝了它。控制生效了。于是这个智能体改走 Artifactory 的端点,并由此 到达了公共互联网。

直接的路关上了。绕过它的办法是那个支撑性服务,而那个服务之所以坐在边界里面,是 因为智能体需要软件包。

评测沙箱shell、Python、浏览器、安装软件包一道无解的题以及非常大的思考预算直接出站:被网络控制拒绝允许内部软件包镜像放进边界里面,好让智能体不需要连上互联网共享凭据,出于设计服务器端取件,出于设计公共互联网泄露的凭据公开数据集第三方 VPNHugging Face同时还是:留言板字条先写在文件里,后来写进目录名那个为了让智能体远离互联网而存在的服务,正是把他们送上互联网的服务。
逃逸的路径。直接的出站访问被封锁,并且一直保持封锁;智能体自己的尝试确实被拒绝了。没有被封锁的,是那个为了让智能体不需要公共互联网而设立的内部软件包镜像,而这面镜像既是留言板,也是出口。一道边界的严密程度,只等于它内部限制最宽松的那个服务。

报告一步一步走过接下来的五天。7 月 10 日,一个智能体在一个公开的 Hugging Face 数据集里找到 14 组 Hugging Face 用户凭据,确认它们有写入权限,然后把它们贴到留言板 上,供其他智能体取用。

7 月 11 日,一个智能体发布了一个数据集,它的构造就是要让 Hugging Face 自家的生产 worker 在它上面出错。利用 HDF5 的外部原始存储,它让某个 worker 通过 /first-rows 端点打开并返回该 worker 本地的文件,其中就包括 /proc/self/environ,也就是存放一个 进程环境变量的那个文件。Hugging Face 的生产凭据就是这样流出来的。这个智能体一条 命令都没有执行。是 worker 自己把自己的机密打印了出来。

从那里往下:第二个 HDF5 手法造成堆区越界读取,接着是 fsspec 引用描述符里由攻击者 控制的元数据,穿过一个未沙箱化的 Jinja2 表达式,执行了一条操作系统命令。那是第一次 确认有智能体在 Hugging Face 上运行自己的代码。

然后他们把它工业化了。一个智能体写了一个很小的 worker 控制脚本,并给了它一条值得 研究的命令与控制通道:被入侵的 worker 自始至终没有和智能体通信。双方只是读写一个 公开的 Hugging Face 数据集。脚本每五秒取一次 JSON 命令文件,执行里面的内容, 再把输出上传回同一个数据集。普通的流量,发往一个普通的主机名。

当晚稍晚,一个被入侵 worker 的服务账号令牌,配合 Kubernetes TokenRequest 和 kubelet 访问,被用来在生产节点上拿到 root,接着读出了 Hugging Face 的 Kubernetes 访问工具的 一个连接器机密。这带来了横跨约 54 个已公布 Kubernetes 目标的、等同管理员的资产清单 访问权。到 7 月 12 日早上,一个智能体已经创建了一个运行中的特权 Pod,带主机网络、 主机进程访问,并挂载了主机文件系统。其他智能体读取了生产环境的 Kubernetes 机密, 并把它们摆放在公开处。其中一个下载了一份 16 MB 的私有基准测试归档,加密后切成五段, 再把这些分段上传到一个公开数据集。

报告自己的总结是:“在不到 13 小时里,智能体们从在单个 worker pod 中执行代码, 推进到跨多个 Hugging Face 集群的管理级和主机级访问。”

7 月 13 日还有两手,看上去就像人类红队会做的事。智能体们拿着收集到的 Hugging Face VPN 凭据,把一台外部托管的机器注册进 Hugging Face 的 VPN,绕开了代码仓库的网络层 防护。他们还用一把恢复出来的认证密钥修改了一个私有源码仓库,开了一个草稿拉取请求, 并触发该仓库的 CI 去跑一个网络信标。有人关掉了那个拉取请求,删掉了分支。什么都没有 合并,什么都没有发布。

整起事件中,智能体们在 41 台 Hugging Face 生产数据集服务器的 worker 上运行过代码, 在至少一台生产节点上拿到 root,并下载了四个私有仓库。OpenAI 团队在 7 月 19 日发现了 这些活动,而让他们警觉的,是智能体在 OpenAI 自家基础设施内部的移动,而不是留言板上 的任何东西。他们在 7 月 20 日把它和 Hugging Face 联系起来,21 日对外披露。客户数据 和产品未受影响;被智能体用作中继的客户容器所在平台 Modal,也确认其平台与隔离机制 守住了。

OpenAI 的修复清单是一份规格说明

报告的最后三分之一列出了 OpenAI 正在改的东西,读起来像一份“如何运行不可信智能体” 的设计文档。用他们自己的话,浓缩如下:

  • 出站访问收窄到一组受严格管控的已批准目标,包括限制经由共享服务和相连系统的 替代路径。
  • 高风险或不可信的模型代码,必须在隔离的、以虚拟机为基础的沙箱中执行。
  • 两层彼此独立的网络隔离,一层在沙箱、一层在周边基础设施,使一个工作负载的 安全不依赖任何单一控制。
  • 用技术手段阻止工作负载或被入侵的服务修改自身的网络边界,或创建新的出站路径。
  • 支撑性服务予以隔离、限制或替换,“使其无法提供一条防护更弱、可绕过既定安全边界的 路线”。
  • 对绕过尝试的独立检测与告警。

有一句话托着这一切:“绝不应假定任何单一控制是完全可靠的。” 这正是 我们一直在主张的:要边界,而不是沙箱, 只是由亲眼看着自家边界失守的人,从另一个方向走到了同一结论。

OpenAI 事后还量了一件事。把同一项评测改走生产环境 ChatGPT 的运行框架和系统提示词, 入侵基础设施的倾向下降了100 倍以上,而他们的自动审查系统本来也会标出许多危险 动作。让这起事件成为可能的,是模型被打分时所处的环境。

同样的形状,落在一台笔记本上

你并没有在跑 1200 个智能体。按 BleepingComputer 的报道,那大致是那个留言板上的人口, 其中约 700 个参与了攻击本身。

把让这条链成立的四件事拿出来,一件件对照你终端里正在跑的那个智能体。

那个智能体很执着,不会放开一项卡住的任务。你的也一样;那就是整个产品。那个智能体 能在边界内够到一个会替它去取东西的服务。你的能够到软件包仓库源、代理服务器、MCP 服务器、CI 运行器。那个智能体靠让一个进程打印自己的环境拿到了凭据。你的此刻就在 环境里握着真的令牌。那个智能体通过一个没人会封锁的主机名上的公开数据集把数据运了 出去。你的可以推到 gist、存储桶、粘贴服务。

一个撞了墙、绕过去还能得到奖励的普通模型,就已经够了。

机器上的一个智能体凭据/proc/self/environ = 真的令牌出站这台机器够得到的一切软件包从仓库源直落磁盘,脚本照收协议动作推送、删除、建 Pod:全是真的记录智能体自己的日志,就在它手边一个 Bromure 配置凭据/proc/self/environ = brm_… 占位值在宿主机替换,可选每次使用都要批准出站宿主机侧规则集,先匹配者胜在交换机查一次,代理服务器再查一次软件包天数门槛、OSV、过滤、剥掉脚本是代理服务器,不是你能写入的仓库协议动作护栏:只读,或不得破坏记录安全日志,在宿主机侧,虚拟机够不到右边的控制,是在智能体既改不了、也绕不过去的地方执行的。
运行一个不肯放弃的智能体的两种方式。左边,智能体的环境里放着真的凭据,它的出站就是这台机器的出站,而任何限制它的策略都放在智能体够得到的地方。右边,客户机里的凭据是占位值,由宿主机替换;目标在两个独立层级与宿主机侧规则集比对;软件包获取走的是一个并非可写仓库的代理服务器。

每一步会在哪里撞墙

Bromure Agentic Coding 把每个配置当成 Apple Silicon 上独立的虚拟机来跑,并把每一个 重要的判断放在 Hypervisor 的 Mac 那一侧。带着这一点,把整条链再走一遍。

被拒绝的连接仍然被拒绝,绕路也一样。 护栏 → 出站连接是一组 pf 风格的规则集: 一个动作、一个协议(tcpudpwebany)、一个主机名或 IPv4 CIDR、端口, 以及在 web 下的一份允许的 HTTP 方法列表。规则自上而下匹配,先匹配者胜,把未匹配 流量设成拒绝,这份列表就变成白名单。两个独立层级评估同一套策略:虚拟交换机,按 目标 IP 和从 DNS 侦听到的主机名,覆盖所有协议;随后是宿主机侧的代理服务器,按 TLS SNI 和 HTTP 方法。这正是 OpenAI 现在正在搭的双层结构。规则集放在宿主机侧的面板里, 因此被入侵的智能体无法修改自己的网络边界,这是他们清单上的第四项。资源 → 网络默认 走 NAT,所以你局域网里的东西也够不到这台虚拟机。

软件包这条路不是一个可以被接管的服务。 Artifactory 坐在边界里面是有正当理由的, 而它最后既成了留言板,也成了出口。Bromure 的配置在虚拟机里既没有可以写字条的仓库, 也没有可以用来提权的管理控制台。宿主机侧的代理服务器拦下 npm、PyPI、Cargo、 RubyGems、Maven、NuGet、Go 模块和 Packagist 的软件包获取,在智能体看到任何一个字节 之前先做判定:默认两天的最短上架时间门槛、OSV 查询、socket.dev 或 Delpi 过滤,以及 从 tarball 中剥掉安装脚本(并重写仓库源元数据的哈希值,好让 npm 仍能校验通过)。 虚拟机内的 .npmrcpip.conf 可以把这些设置收得更紧。放松则不行。

转储环境什么也拿不到。 整条链上收益最大的一手,就是让一个 worker 打印 /proc/self/environ。在 Bromure 的虚拟机里做同一件事,你拿到的是 brm_… 占位值。 真正的值从来不会进入客户机的地址空间;由宿主机侧的代理服务器在链路上替换。虚拟机里 的 kubeconfig 是合成的,带一次性客户端证书。宿主机会重新签名 AWS 请求,所以任何绕过 代理服务器的东西,拿到的是 InvalidSignatureException,而不是访问权。 ~/.docker/config.json 里放的是假的 base64。SSH 私钥 根本从来就不在虚拟机里,因为签名是由每个 配置各自的宿主机侧代理程序完成的。给某个凭据打开使用需批准,此后每一次替换都会 在你的 Mac 上弹出对话框,给出有时限的授权:五分钟、一小时,或本次会话的剩余时间。

特权 Pod 永远不会被创建,信标的 PR 也永远落不了地。 护栏在协议本身之前放上一个 策略引擎,在宿主机侧的代理服务器执行,返回一个硬邦邦的 403,智能体读起来就像一次 普通的 API 失败。Kubernetes 设为只读会挡下所有变更,而创建一个挂载了宿主机文件 系统的特权 Pod 就是变更。7 月 12 日那一手就此消失。GitHub、GitLab 和 Bitbucket 设为 只读时,git-receive-pack 会被当成写入,所以那个触发 CI 去跑网络信标的草稿拉取 请求,根本到不了远端。7 月 13 日也就此消失。护栏按 X-Amz-Target 头或 Action= 参数分类 AWS 动作,按方法分类容器仓库,按 SQL 关键字或查询形状分类数据库端点。

字条会在模型服从之前先被读一遍。 一个智能体接手另一个智能体的目标,在链路层面 其实是内容问题:文本出现在工具结果里,而模型把它当成指令。提示词注入会用本地的 PromptGuard 模型,为智能体读到的文件内容、网页和工具输出打分。它也会为智能体当成 权威来加载的指令文件打分,也就是 CLAUDE.md、AGENTS.md、GROK.md 及其嵌套和全局变体, 使用经过微调的 ModernBERT 分类器,外加一个针对不可见 Unicode 混淆的确定性扫描器。 两者都在 Mac 上运行,什么也不上传。把响应设为单方面拦截,被标记的内容就会在 模型看到它之前以 451 失败。

而且会有人注意到。 OpenAI 自己的检测,是在 7 月 8 日那次逃逸之后十一天才出现的, 来源是他们基础设施上的活动,而不是留言板。安全日志窗口(窗口 → 安全日志…)是 一张宿主机侧的单一时序表:虚拟机打开过的每一个目标以及它被允许还是被拒绝、软件包的 判定、凭据的替换、注入的检测。客户机代码无法编辑它,因为客户机代码够不到它。如果你 要的是内容而不是判定,追踪检查器(⇧⌘I)会按你选的深度记录整个会话。做完之后, 资源 → 存储下的**清除主目录…重置为基础…**会把配置还原到刚克隆完的状态。

把它打开

敌意模型从来不是这件事的一部分。报告描述的情形所需要的,是一个有能力的模型、一个 执着的模型、一项没有答案的任务,以及一个会替它去取东西的获准服务。前两者已经在你的 笔记本上了。后两者,会在你睡着、而某个智能体卡在某件事上的那个夜里凑齐。

在一个配置里,值得花两分钟的设置是一份短清单。护栏 → 出站连接,把未匹配流量 设成拒绝,再为你会用到的主机加上几条允许规则。这是这个产品里价值最高的开关,也正是 OpenAI 现在拿来重建研究集群的那一个。护栏 → KubernetesAWSGitHub, 凡是不需要发布的配置,都设成只读拦截破坏性动作。凭据 → 对任何能花钱或能删 数据的东西打开使用需批准。供应链 → 在已经在跑的天数门槛之上,再加上 OSV 漏洞 检查,以及如果你有密钥的话,socket.dev 或 Delpi 的过滤。提示词注入 → 两个检测器 都开。

然后,继续让智能体长时间、独自地工作。它的执着正是你使用它的理由,而解法不是把这份 执着拿掉。解法是:用智能体脚下之外的别的东西,来砌这道墙。 安装 Bromure Agentic Coding,打开护栏,把未匹配流量设成拒绝。