标志说了不
2026 年 8 月 3 日,The Hacker News 报道了 FaceHugger:Hugging Face 的 Diffusers 库——每月七百万次下载——里的三个缺陷,让一个精心构造的模型仓库得以在加载它的机器上执行任意代码,即使已经设置 trust_remote_code=False。根本原因:信任检查和代码加载发生在两个不同的时间点。编码代理拉取模型就像拉取软件包一样,所以那面说谎的标志守护的是开发者的笔记本电脑。Bromure Agentic Coding 把边界放在进程之下而不是进程之内:加载在一台可丢弃的 VM 里运行,触手可及的凭证是诱饵,载荷打回家的电话死在主机代理服务器上——恶意仓库让攻击者赔上载荷,而你什么都不赔。
仓库里有一个名叫 None.py 的文件,整个把戏就是这样。安全标志设了,
检查跑了,代码照样执行了,因为检查和加载发生在两个不同的时刻——
而攻击者只需要在第二个时刻在场。
2026 年 8 月 3 日,The Hacker News
转载了 Zafran Labs 在七月底发表的研究:
Hugging Face 的 Diffusers 库里的三个漏洞,合称 FaceHugger,让一个
精心构造的模型仓库得以在任何加载它的机器上执行任意 Python 代码。
Diffusers 是运行扩散模型的标准途径——每月约七百万次下载,安坐在生产
流水线、CI 任务、笔记本和容器镜像里。这个库恰好为这种场景准备了一道
防护:trust_remote_code,一面本该阻止模型仓库里未经审阅的代码在你
机器上运行的标志。三个缺陷全都在它设为 False 的情况下从旁走过。
检查和加载是两个不同的事件
从 Hub 加载模型不是单一一个操作。库会依次发出两个 HTTP 请求:第一个
拉取仓库的配置,第二个拉取该配置所指向的代码和权重。trust_remote_code
这道闸门在第一个请求期间做它的检查。它要防范的东西,在第二个请求里
才到达。
Zafran 的研究人员找到了穿过这道缝隙的三条路,如今已配上
CVE
并在 Diffusers 0.38.0 中修复。在 CVE-2026-44827(CVSS 8.8)里,一个
字符串格式化的怪癖会在没有请求自定义流水线时拼出文件名 None.py——
而对这个文件的存在性检查跑在一条不会标记它的代码路径上,于是一个带着
名为 None.py 文件的仓库就通过闸门、在加载时执行。CVE-2026-45804 是
一场竞速:两个请求之间隔着大约三分之一秒,一个在这期间发生变化的仓库,
被验证的是一个东西,被执行的是另一个东西。CVE-2026-44513 则在本地快照
加载时整个跳过闸门。Zafran 的研究人员把三个压缩成一句话:任何能让
加载器看到闸门没看到的自定义代码的方法,都能绕过。
这个模式是有前科的。Transformers——Hugging Face 生态的另一根支柱——
有过它自己的 trust_remote_code=False 绕过(CVE-2026-4372),通过一个
标准的模型加载调用即可利用。这类漏洞是随设计而来的:一个安全承诺住在
它所监管的进程里面。标志是一个函数参数,而强制执行却散落在代码路径、
文件名格式化和请求时序之间。当其中任何一个和标志意见不合,输的就是
标志,而且没有任何东西告诉你它输了。模型照样加载,演示照样能跑。
你的代理在某个星期二加载模型
从编码代理的椅子上重读这个故事。把 inpainting 流水线从 Hub 上拉下来、
和我们的跑个基准测试,是再普通不过的星期二任务。项目钉住
diffusers==0.37,因为项目就是会钉住东西。代理写下一位谨慎工程师会写
的东西——from_pretrained(..., trust_remote_code=False)——而谨慎的
版本正是有漏洞的版本。软件包的源码你可以在安装前读一遍;模型仓库则是
配置、权重,以及拜加载器所赐、第二个 HTTP 请求说它是什么它就是什么的
东西。
代理让这条加载路径变得火热。它们拉取模型就像拉取软件包,任务进行到 一半、凭名字就拉,而攻击者早已想明白推荐本身就可以被下毒。 一个在加载时引爆的模型仓库,是同一招换到隔壁的注册表,而且伪装更好: 载荷安坐在一个你归档为数据、而非代码的产物里。
Zafran 的建议是把模型仓库当成不可信代码对待。正确——但它有一个报告 没有展开的前提:一个让不可信代码可以运行、而你毫无损失的地方。怀疑 的策略是一种纪律,你维持它,直到截止日期开始大声。一间为引爆而造的 房间,不需要你的纪律。
加载发生在盒子里
Bromure Agentic Coding 就是那间房间,而它从 FaceHugger 的标志失守的 地方开始:边界坐在进程底下,在一个加载器的内部无从争辩的环境里, 无论进程配不配合都会强制执行。
把这场攻击沿着一个配置文件走一遍。你的代理接下基准测试任务,连钉住
的版本一起,对恶意仓库调用加载器。None.py 执行了——在一台可丢弃的
Linux VM 里,隔着一层虚拟机管理程序才到 macOS。载荷此刻拿下的机器,
是一台为这个任务而存在、只装着这个任务的工作区、工作合并后就被丢弃
的机器。
载荷的第一步真正行动是对外的。它非如此不可:拉取第二阶段,或者把
找到的东西送回它的操作者。这些请求每一个都要穿过主机代理服务器,
目的地在那里比对你为这个配置文件批准的列表。huggingface.co 在列表
上——模型就是这样进来的。攻击者的投递箱不在。线路拒绝了,就像闸门
本该做的那样——只是这次的
拒绝不取决于库的代码路径彼此意见一致。代理服务器不知道、也不在乎是
哪个 Python 文件在发请求。它比对目的地,找不到任何批准项,就把连接
掐断。
假设载荷跳过网络,改去翻箱倒柜,翻环境变量和密钥文件。它找到的是 中介的诱饵。真正的值住在主机上, 只为批准的目的地在线路上注入——密码从来不在 VM 里,无从偷起。而如果它想 让自己住下来——改写某个配置、把什么东西关掉—— 破坏性的形状会停下来,等主机上的护栏对话框, 何况在一台寿命只有一个任务的机器里搞持久化,本来就是亏本买卖。
与此同时,任务成功了。基准测试跑完,数字回来了,你读着 diff。攻击者 花掉一个供应链上的位置,换来一个在没东西可偷、没办法打电话回家的 房间里执行的载荷。你什么都没花——连注意力都没花,除非你把读代理服务 器日志当运动。
给库打补丁,把房间留下
Zafran 报告里的修复建议要组织升级,然后出门打猎:那些钉住旧版 Diffusers 的应用、笔记本、推理服务和容器镜像。这场狩猎没有终点线—— 在你的依赖树某处,总有某个项目钉住那个带缝隙的版本,而下一个 FaceHugger 会是另一个库的标志配上另一种绕过。守卫和 shell 意见 不合是这种失败在命令允许 列表里的版本;沙箱只是一句话是 这种失败在提示词里的版本。每一次,都是一个检查住在它所检查的东西 里面,而那个东西赢了。
配置文件的边界不会共享这种命运,因为它不参与它所围住的进程。VM 不
解析流水线配置。代理服务器没有 None.py 的代码路径。它们唯一的
工作——这台机器可丢弃、这条线路拒绝列表外的目的地——在绕过被公开的
那天,和被修补的那天,是同一份工作。一位 Sumo Logic 的 CISO 在评论
这项研究时心里并没有 Bromure,却点名了那些守得住的防御:不光鲜的
那些:出口控制、分段,以及凭证卫生。
我们把不光鲜的那些,做进了一个你点一下就有的配置文件。
继续拉取模型吧——杠杆就在那里,你的代理本来就该替你试用它们。只是 别再让一个函数参数,当 Hub 和你笔记本电脑之间唯一的屏障。 安装 Bromure Agentic Coding,给代理一个规则守在 线路上的配置文件,下一次某个加载器的承诺破掉时——总会有一个破掉—— 新闻故事仍然是别人家的。