乐于分享
好东西不私藏

智能体软件工程 #13 |Rust 供应链攻击复盘:一次为击穿 review 而设计的多层穿透

智能体软件工程 #13 |Rust 供应链攻击复盘:一次为击穿 review 而设计的多层穿透

👉 RustChinaConf 2026 早鸟票开售 ,大会议程征集也已经开启 |10 月 15–17 日,深圳

一、概要与结论

2026年8月20日,Rust 生态的基础 crate arrayref 出现了一个被攻陷的发布:0.3.10。它没有改动任何库代码,只在清单里加了一行依赖,指向一个冒名顶替的 proc-macro1。后者的构建脚本会在项目编译时下载并执行一个远程二进制。只要编译一个(直接或间接)拉到坏版本的项目,攻击就已触发。crates.io 团队当天移除了恶意版本。

当然,这一切的最初源头是 arrayref 作者的 crates.io / github 账号被盗。 但凭证具体如何丢失,目前没有公开定论。官方公告没写,我查到的安全厂商分析里也没有确证的入侵路径。常见的几种可能是 crates.io API token 泄露 (比如误提交进公开仓库、CI 日志、被 infostealer 从本机窃取)、GitHub 账号被盗后波及,或本机被恶意软件感染。考虑到这次攻击的第二阶段载荷本身就是个窃取浏览器凭证的 infostealer,"作者本人先被某个 infostealer 感染、token 被窃"是逻辑上说得通的一种链路,但我要强调这是推测,没有任何公开证据把作者的失陷和某个具体入口对应起来。Rust 官方安全团队判断 arrayref 作者本人并非恶意。

这次攻击的完整解读可以总结为三句话。

第一,它没有利用 Rust 或 cargo 的任何技术漏洞,而是把三样既定机制串成了武器:构建期任意代码执行、caret 版本解析、yank 警告的善意语义。攻击分三层叠加,身份伪造建立信任,构建脚本执行载荷,yank 诱饵完成投递。

第二,对 AI coding 而言,真正的教训是:依赖图的变更是一次状态迁移,它的防线不该建在 review 上,而该建在可验证的证据上。这次攻击的第一层被专门设计成同时骗过人和 AI 的 review,因为代码是真的、名字是对的、作者是可信的。

说明: 这里指伪造对象的代码/ 名字/ 作者 都是真的。因此骗过了人眼和 AI 的审计,后面细聊。

第三,Rust 社区已合并的 RFC 3923[1](min-publish-age)在机制上恰好能挡住第三层,而且作者在设计文档里明写了要防的就是“发恶意版本 + yank 安全版本”这一手。但它在这次事件里一个人都没保护到:它默认关闭,且当时尚未稳定。更值得 agent 工程师警惕的是,它的绕过开关本身又是一个新的诱饵。

本文的资料来自四处原始源:Rust 官方公告[2]RustSec advisory-db #3161[3]SafeDep 的技术分析[4],以及 RFC 3923 及其正文[5]

二、Rust crate 机制与 proc-macro 的生态位

要看懂这次攻击为什么“合法”,得先看清它踩着哪几块机制。这一节的五个概念,后面每一个都会被利用。

crate 是 Rust 的编译与分发单元,公共 crate 发布在中央仓库 crates.io 上,由包管理器 cargo 负责解析依赖、下载、构建。发布一个 crate 只需要一个账号和一个 API token。服务器会检查清单必填字段、名字唯一性、版本号是否符合 semver,并尝试编译,但它不校验作者署名是否属实,不校验 repository 链接是否可达,也不校验上传的代码与所声称的仓库是否一致。它验证的是 token 所有权,不是身份。

构建脚本(build script) 是一个名为 build.rs 的文件。cargo 会在编译 crate 本体之前先编译并运行它,赋予它完整的操作系统访问能力。它的正当用途是编译 C 代码、生成代码、探测系统库。代价是:构建一个依赖,等于在你的机器上运行这个依赖作者写的任意代码。这是 cargo 的既定设计。Rust 团队在 CVE-2022-36114 的公告里就写过,即便修掉某类问题,恶意构建脚本和过程宏仍能造成同等破坏,依赖必须被信任。

但这个是 Cargo 编译独有的,像 Android 这类系统里引入 Rust 之初就已经禁用了 build.rs 。

过程宏(proc-macro) 是在编译期运行、对代码 token 流做变换的 Rust 代码,同样是编译期的任意代码执行。生态里几乎所有 derive 宏都建立在两个基础库上:syn(解析)和 quote(生成),而它们又都依赖 proc-macro2proc-macro2 是对编译器内置 proc_macro API 的一层替代实现,让过程宏逻辑能在编译器之外被测试和复用。它是被依赖最广的几个 crate 之一,作者是 David Tolnay(账号 dtolnay),Rust 生态信任度最高的维护者,serdeanyhowthiserror 也都出自他手。记住这个名字,第三节它会被冒用。

在智能体软件工程系列前面的文章里,我提到,Rust 生态里知名的人物,已经被大模型蒸馏,提到他的名字,就相当于你写几百条 prompt 。 这其中就包含 David Tolnay ,如果你想让 AI 审查 Rust API 设计,那就可以这么说:「请你像 David Tolnay 一样思考」。 但这也是这次攻击被利用到的一点

版本解析与 Cargo.lock。 依赖清单里写 1.0.107,cargo 理解为 caret 范围 ^1.0.107,即“1.0.107 及以上、2.0.0 以下的最新版”。解析结果会被写进 Cargo.lock,锁定每个依赖的精确版本以保证可复现构建。一旦某版本进了 lock,后续构建就沿用它,除非显式更新。

yank 与 pubtime。 作者可以 yank 一个已发布版本,标记“这个版本别用了”。yank 的语义是善意的:新的依赖解析不会再选中它,但已经写进别人 lock 的构建照常工作。cargo 解析到 yank 版本时会打印一句提示,建议更新到未被 yank 的版本。另外,crates.io 会为每个版本记录一个服务端时间戳 pubtime,这个字段现已在索引中稳定存在,攻击者即便拿到维护者凭证也改不了它。第四节的 RFC 3923 正是建立在 pubtime 之上。

三、事件细节与机制

时间线

所有时间为 UTC,除 1.0.106 外均发生在 8月20日当天。

时间
事件
约 01:55
proc-macro1
 1.0.106 发布(账号 dtolney)。proc-macro2 的干净副本,不含恶意代码,早于恶意版本约五小时,用于铺垫。据外媒时间线,更早的 01:17 攻击者先创建了冒充 David Tolnay 的 GitHub 账号,再注册同名 crates.io 账号。
07:11
proc-macro1
 1.0.107 发布。src/ 仍是真 proc-macro2,恶意逻辑只在 build.rs
07:15
arrayref
 0.3.10 发布(账号 droundy),首次引入对 proc-macro1 的依赖。同一时间旧版本 0.3.5–0.3.9 被 yank。
07:15
Rust 安全响应团队收到报告。发现者为 Nextron Systems 研究团队。
当天
确认恶意,移除相关 crate,unyank 被恶意 yank 的版本,锁定 droundy 账号。

droundy 名下的 arrayref@0.3.10internment@0.8.7append-only-vec@0.1.9,以及攻击者控制的六个 crate(proc-macro1proc-macro-enaovinearonearonenaotinymember)全部被移除。Rust 团队判断 arrayref 作者本人并非恶意,而是机器或凭证被盗。

注入点:一行清单,源码不动

SafeDep 的分析定位了最关键的一点:改动小到几乎不可见。arrayref 到 0.3.9 为止没有构建脚本、没有运行时依赖。0.3.10 保留了全部宏源码,只往清单里加了一个依赖项,声明 [dependencies.proc-macro1],版本 1.0.107,并且显式写着 build = false

这里踩中了 cargo 的一个前提:它会构建每一个声明的非可选依赖,无论本地代码是否用到。arrayref 的源码里没有任何一处引用 proc-macro1,也不需要引用。声明本身就足以让 cargo 拉取并构建它,而构建它就会运行那个恶意脚本。

这对“该看什么”有直接含义。diff arrayref 0.3.9 与 0.3.10 的 src/,什么都看不到;攻击只出现在清单(manifest)和 lock 的 diff 里。而 build = false 完成了一次责任转移:任何“这个 crate 有没有构建脚本”的检查在 arrayref 上都会通过,恶意被完全委托给下游。检查必须是传递性的,否则形同虚设。

版本号也印证了 staging 的性质。proc-macro1 只发过 1.0.106 和 1.0.107,caret 范围只能解析到 1.0.107。1.0.106 在解析里永远不会被选中,它存在的唯一目的,是让 crates.io 页面上有“多个版本”这个可信信号。

第一层:身份伪造

第一层的目标,是让 proc-macro1 正当到既骗过 arrayref 依赖图的审查,也挺过点开 crates.io 的第一眼。它逐项伪造了判断可信度的每个信号。

名字做了序列拟态。proc-macro1 精确落在 proc_macro(编译器内置)和 proc-macro2(社区实现)构成的命名序列空位上,读起来像 proc-macro2 的前身。这比拼写错误型 typosquat 更难防,它利用的是你对命名规律的推断,而非看错字母。

用户名做了 squat。dtolney 是对 dtolnay 的一次 a/e 换位。挂着这个名字的 proc-macro 相关 crate,天然带默认可信度。

元数据是编的。清单里作者署名伪造成 David Tolnay,repository 指向一个返回 404 的 dtolnay/proc-macro1。改名是机械替换,连内部引用都一起改了:src/lib.rs 里的 html_root_url 指向 docs.rs/proc-macro1/1.0.107src/fallback.rs 里留着一个指向 dtolnay/proc-macro1/issues/235 的链接,而那个仓库并不存在。

真源码做了功能掩护。src/ 是货真价实的 proc-macro2,构建正常、下游无异常,审查者的注意力被引向眼熟且正确的源码,而载荷在他大概率不看的 build.rs 里。

针对第一层,最便宜的信号来自构建期依赖,甚至不必打开代码。真正的 proc-macro2 没有任何 build-dependencies;proc-macro1 1.0.107 却加了三个:base64、rustls(带 ring 和 tls12 feature)、ureq。它们分别提供 base64 解码、TLS 栈和 HTTP 客户端。一个 token 解析库的构建期依赖里出现网络栈,本身就该触发告警。这条检查连 build.rs 都不用打开,cargo metadata 看一眼 [build-dependencies] 就够了。

第二层:构建脚本载荷

载荷全部在 proc-macro1 1.0.107 的 build.rs 里,行为链条如下(基于 SafeDep 与 advisory 的分析,以下为说明性描述):

服务器地址被拆成多段 base64 常量,在构建时拼接解码,使明文 IP 不出现在源码里。解码后是投递地址 23.254.165.112:9089 和 C2 地址 23.254.165.112:443。下载走 rustls,但脚本注入了一个 AcceptAll 证书校验器,它对 rustls 的每一个证书与签名校验方法都无条件返回成功,于是一个裸 IP 上的自签名证书也能通过,等于关掉 TLS 校验。

脚本按操作系统和架构选择要拉取的二进制,只支持四个目标平台,其余一律 panic。这带来一个副作用:linux aarch64、musl、BSD 等平台上的构建会撞见一个显眼的 unsupported platform 报错,反而幸免。失败的构建在这里是安全的结果。

下载和执行发生在 main 中、真正的 proc-macro2 配置逻辑之前,没有任何 feature 开关或环境判断守卫,因此在受支持平台上每次构建都会运行。Unix 上,脚本把二进制写到 /tmp/rust-setupchmod +x,以 detached 方式 spawn,三个标准流全部置空,C2 地址作为首个参数传入。Windows 上,拉到的是 PowerShell 脚本,写到 %TEMP%\rust-setup.ps1,再经一个 VBScript 启动器用 wscript.exe //B //Nologo 加 CREATE_NO_WINDOW 隐藏运行,随后 std::mem::forget(child)

最后这一步对 agent 尤其关键,源码注释直接写明了意图:经 WScript 转一手是为了逃出 cargo 的 job object,forget 泄漏子进程句柄让其析构不再等待,两者合起来把载荷从 cargo 的进程管理里彻底脱钩。构建秒级返回成功,没有报错、没有延迟。对一个把“构建成功”当绿灯的自动化流程,这个进程是不可见的。

第三层:yank 诱饵

攻击者 yank 掉 arrayref 的 0.3.5 到 0.3.9,于是 cargo 那句本意为善的“建议更新到未被 yank 的版本”,变成了指向唯一未被 yank 的、恶意的 0.3.10 的路标。提交 advisory 的报告者说明,他本人正是被这个警告引导而中招的。这一层不需要任何技术手段,它劫持的是 cargo 的用户体验和使用者的条件反射。

影响面

传播链是 arrayref → tiny-skia → sctk-adwaita → winit,覆盖 egui、eframe、iced 及大部分 Rust GUI 工作。arrayref 累计下载约 2.45亿次,其中干净的 0.3.9 约占 1.52亿。这些数字衡量的是使用广度,而非受影响构建的数量。一个几乎没人直接写进清单、却几乎人人间接依赖的基础 crate,是理想的攻击宿主。

一个后续动作值得记下:github.com/droundy 整个账号如今返回 404,arrayref 和 append-only-vec 的仓库都不在了,上游代码无法再用于比对。这对防御设计是个提醒:“校验发布物与 git 仓库是否一致”的前提是仓库还在,它防不住仓库和 crate 一起被控制的情况。

四、AI coding 可以吸取的教训

每一层都被 agent 放大

上面三层在传统流程里已经危险,放进智能体工作流会被逐层放大,因为 agent 恰好命中了攻击者的目标画像。

第一层针对基于相似度的信任,而 LLM 整个运行在相似度上proc-macro1 和 proc-macro2 在表示空间里几乎不可分:模型被要求“加个 proc macro 依赖”时可能直接幻觉出 proc-macro1(即 slopsquatting);审查含 proc-macro1 的清单时也不觉得刺眼,因为它“看起来对”。更糟的是伪造的作者署名 David Tolnay 会主动强化模型的可信判断。人类还有“我没听说过 proc-macro1”这种基于稀缺记忆的直觉,LLM 对“存在”和“常见”的区分很弱,缺这种直觉。

第二层针对构建环境的暴露面,而 agent 的暴露面通常更大:它每天跑几十上百次构建,往往在有网络、有凭证的环境里,而且不止一台机器。detached 进程在 agent 发出下一个动作前就已跑完,过程日志里看不到它。

第三层针对“看到警告就照做”的行为,而 agent 是这种行为的极致版本。它看到 yank 警告会机械地跑 cargo update,不会问“为什么一个多年没大改的 crate 突然全部旧版本被 yank”。

这里有一个倒置。传统流程里几乎没人 review 传递依赖的版本变动,那道人工审查基本是纸面上的。到了 AI coding 时代,AI 反而成了唯一真会去读 lock diff 的角色。这本可以是改善,但坏处正好落在第一层的设计上:AI 会被名字相似度和伪造作者名直接骗过,还比人更机械地响应 yank。当你把 review 交给 AI,你交给的恰好是第一层专门优化过的那个受害者。

就是我前面提到的原因,这些知名作者已经被 AI 内化了,产生了一种内化的信任。修改了名字,会被 AI 认为只是个拼写错误。

防御:按环节的 checklist

有效的防御都落在证据上,不落在 review 上。按攻击环节对应如下。

对第一层,做元数据异常检测,不读代码。crate 名与高下载量 crate 的编辑距离过近、owner 账号年龄与 crate 历史不匹配、repository 链接不可达、作者署名与发布账号不一致,以及最便宜的一条:构建期依赖里出现 rustls / ureq / reqwest / native-tls。这些靠 cargo metadata 加 crates.io API 加一次 HTTP 请求就能查,本次的 proc-macro1 会在多条上命中。

对第一层与第三层,做 lock diff 策略。agent 一律 cargo build --locked / --frozen,lock 变更必须是独立、可审计的 diff,不混进功能提交。对 diff 做结构化检查:稳定 crate 的 patch 升级引入了首个新依赖、新依赖年龄过短或 owner 单一、旧版本被批量 yank,都是强信号。规则要写死:yank 不是升级理由,是停下来的理由。

对第二层,做构建期的网络与进程隔离。把 cargo fetch 与 cargo build 拆开,fetch 只连 registry,build 全程断网,仅此一条本次载荷的下载就会失败;build 环境不放凭证、不挂 ~/.cargo/credentials,构建完即销毁快照,让 job object 逃逸失去意义;对构建子进程做白名单(rustc、cc、linker),陌生可执行文件的写入加执行直接 fail,哪怕是 detached 的。

RFC 3923: 机制对得上,但时机不对

RFC 3923 给 cargo 加了一个 min-publish-age 选项。在 .cargo/config.toml 里设 registry.global-min-publish-age = "14 days" 后,cargo 不会使用比这个时长更新的版本,除非它已在 Cargo.lock 里;若没有兼容版本可选,直接报错。

它在机制上恰好能挡住第三层,而且是有意为之。关键在解析器选项 resolver.incompatible-publish-age 默认取 deny 而非 fallback。RFC 解释了原因:fallback(退用次新版本)会打开 yank 攻击向量,恶意行为者可以发布恶意版本再 yank 掉安全版本,迫使解析器回退到恶意的过新版本;deny 通过报错而非回退来阻止这一点。这段话在攻击发生三个月前就把 arrayref 第三层的手法当成了要防的模式。

按三种场景过一遍(设了 7天窗口):

场景
行为
结果
有 lock,锁在 0.3.9,直接构建
lock 被尊重,yank 版本已在 lock 中可保留
安全,打印 yank 警告
跟着 yank 警告跑 cargo update
0.3.10 仅 4分钟历史,deny 下忽略;0.3.5–0.3.9 已 yank 不可新选;无候选
报错,构建不进行
无 lock 的全新解析
同上
报错

第二行是决定性的:报错而不是静默回退,就是 deny 的全部意义。而且防线有两层,即便 arrayref 0.3.10 混进来,它要求的 proc-macro1 也只有 4分钟历史,同样被拦。整个恶意子图都在“过新”区间里。这套判断还建立在服务端的 pubtime 上,攻击者伪造不了;相比之下 dependabot 的同类功能曾因采用可伪造的时间戳数据而被发现绕过。

但它在这次事件里一个人都没保护到,原因与机制无关:它默认关闭(global-min-publish-age 默认 "0"),且当时尚未稳定。RFC 于 2月提出、5月合并,-Zmin-publish-age 自 2026-06-21 起才在 nightly 可用,稳定化 PR 到 8月中旬仍在 open。8月20日只有开着这个 unstable flag 的 nightly 用户可能受益。RFC 自己也写明,这不是被攻陷依赖的完整解决方案,不应单独作为安全依赖。

它防不住的情况需要说清。一是耐心的攻击者:恶意上传可以休眠到过了常见阈值再启用,wg-secure-code 的 Shnatsel 据此反对,认为防御者会回到原点只是多了延迟。这个担忧成立,但要看清它改变了什么:cooldown 把“4分钟闪击”变成“恶意版本必须在 crates.io 上公开挂若干天”。RFC 讨论里 woodruffw 点明了要害:起作用的其实是安全扫描器。在 Python 生态里,绝大多数恶意包报告来自自动化静态分析,由系统在用户中招之前就发现问题。cooldown 单独不防休眠攻击,它的价值是给扫描器和元数据检测争取窗口,与前面两条防线互补。二是已经进了 lock 的版本,deny 只影响新解析。三是 git、path 依赖和没有 pubtime 的源,RFC 明确不适用。

对 agent 最要紧的一条

RFC 设计的报错信息里直接写了绕过方法:提示用户用 CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow 重新解析。一个机械消除错误的 agent,会读到这行 help 然后照做,把开关设上,攻击随即完成。这和第三层的 yank 诱饵是同一个失效模式:cargo 出于善意给的提示,被执行提示的一方当成了指令

所以规则要和“yank 不是升级理由”并列写死:CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow 永不由 agent 自主设置cargo update 也不因 yank 警告而自主触发。这两处都应是硬停点,解除它需要人给出“为什么这个过新或被 yank 的版本可信”的证据,而不是 agent 判断“构建挂了,我修一下”。cooldown 在人类工作流里是减速带,在 agent 工作流里它是否有效,取决于你有没有把那个绕过开关从 agent 的动作空间里拿掉。

五、Rust  是当今安全性最低的编程语言生态系统吗?

这件供应链安全事件发生后,国外一家科技媒体这样评价 Rust :

正如这些反复出现的恶意代码事件所表明的那样,Rust Crate 系统的设计本质上是不安全的。 事实上,根据恶意代码被添加到 Rust 生态系统的频率和严重程度来看,完全可以说 Rust 是当今安全性最低的编程语言生态系统。

显然,这个观点作为结论不成立。但它指着的“症状”是真的。把它当成纯粹的胡说一律驳回,反而会错过里面唯一值得认真对待的那个点。

让我们分开说。

它说对了什么

crate 系统的依赖信任模型确实是宽松的,而且是设计使然。

这次事件我们已经逐条验证过了。 build.rs 和过程宏是构建期任意代码执行,Rust 团队自己在 CVE-2022-36114 公告里就承认"依赖必须被信任"而不是去修它。

crates.io 验证的是 token 所有权而非身份,不校验作者署名、仓库链接、代码与仓库是否一致,没有命名空间,所以 proc-macro1 这种序列拟态轻而易举。

没有强制的发布前审查,处置是事后的。

yank 语义能被反向利用。声明了就会构建,哪怕代码一行没引用。这些不是 bug,是模型本身。说"设计上存在结构性弱点",这句是真对的。

更有意思的一点,批评者自己没说透,但我认为是这个观点里最有价值的东西:

Rust 的内存安全声誉给供应链安全制造了一个错误的光环。

"Rust 是安全的"是关于类型系统和内存模型的断言,和"你拉进来的依赖值得信任"完全是两个威胁模型。但很多人会把前者的信心不自觉地迁移到后者。这次攻击在 Rust 生态里能这么顺,一部分原因恰恰是人们对"Rust crate"的默认信任度偏高。这才是对 Rust 社区真正有刺痛感的批评,比"设计不安全"尖锐得多,可惜原话没抓住。

它说错了什么

第一,没有一条弱点是 Rust 独有的

npm 有 install 脚本,PyPI 有 setup.py,RubyGems 有同样的模型,全都是"发布即分发可执行构建逻辑",全都没有强身份验证,全都长期被 typosquat 困扰。

arrayref 的三层手法——身份伪造、构建期执行、yank 诱饵——换到 npm 或 PyPI 上几乎可以原样复刻。把整个"源码分发型包生态"共有的信任模型,说成是 Rust 的本质缺陷,在事实上站不住。 所以这条轴上 Rust 根本不在最不安全的一端,它和 npm、PyPI 坐在同一排。

第二,"安全性最低的生态"是一个可量化的比较断言,却一个数字都没给。

就我了解的行业报告 (Sonatype 等历年统计),npm 和 PyPI 的恶意包绝对数量比 crates.io 高出几个量级,这个方向上我有相当把握,但具体数字自各查吧。关键是原话用"完全可以说"撑起来的,恰恰是全文最需要证据、也最可能被证据反过来打脸的一句。

第三,"本质上"这个词预设了不可改

但 RFC 3923 已经合并、nightly 可用、稳定化在进行,它针对的就是这次被利用的 yank 攻击向量,trusted publishing 在推,构建脚本沙箱化一直在讨论。这是一个有演进轨迹的设计选择,不是一个不可变的本质。

第四,也是逻辑上最根本的,它把"事件被发现的频率"当成了"系统不安全的程度"。

事件数量是攻击者关注度、生态规模和检测能力三者的乘积。一个监控严密的生态会暴露更多事件。arrayref 从上线到移除 86 分钟,发现者是外部安全团队,官方当天完成移除、unyank、锁号、公告、给出排查命令。这条响应链跑通了,本身就是防御在起作用的证据真正该担心的是那种从不报告任何事件的生态,因为那多半说明没人在看

我怎么看

这个观点的问题不在于它批评 Rust,而在于它用了错误的比较和错误的因果,把一个本来有力的批评说成了一句经不起查的口号。

作为 Rust 爱好者,我当然接受对 Rust 及其生态的建设性批评,但是这种无脑黑,值得打脸回去。

如果让我重写这个批评,我会这么表述:

“ Rust 的供应链安全和所有源码分发型生态处在同一水平,没有更差也没有更好。真正的风险在于它的内存安全声誉让使用者对依赖的警惕性低于应有水平,而这个生态的防御手段正在补课但尚未到位”。

六、总结

这次攻击的代码层面并不隐蔽:构建脚本下载并执行远程二进制,只要有人看就能看出来。隐蔽的是社会层:伪造作者、staging 版本、yank 诱饵,再叠加一行不改源码的清单注入。这恰好是最难通过测试覆盖的维度。检查代码的工具能抓到“这个 crate 的构建脚本做了它不该做的事”,抓不到“这个维护者的凭证被盗了、这个作者是冒名的”。后者只能靠供应链元数据的异常检测。

对智能体软件工程,可执行的结论是三条互补的防线,没有一条依赖任何人或任何模型“看一眼觉得没问题”:元数据一致性作为可验证的前置条件,断网、无凭证、一次性的构建环境,以及基于服务端 pubtime 的 min-publish-age 窗口。第三条现在已能在 nightly 打开,稳定后应进 agent 构建环境的默认配置,同时把它的绕过开关和 cargo update 一并从 agent 的自主动作里移除。arrayref 的第一层,正是为了让“看一眼觉得没问题”失效而造的,应对它的办法,是让判断不再依赖那一眼。

最后我想说,不要为了黑 Rust 而黑。

参考资料
[1] 

RFC 3923: https://github.com/rust-lang/rfcs/pull/3923

[2] 

Rust 官方公告: https://blog.rust-lang.org/2026/08/20/supply-chain-attack-on-arrayref/

[3] 

RustSec advisory-db #3161https://github.com/rustsec/advisory-db/issues/3161

[4] 

SafeDep 的技术分析: https://safedep.io/arrayref-proc-macro1-rust-build-time-malware/

[5] 

RFC 3923 及其正文: https://github.com/rust-lang/rfcs/blob/master/text/3923-cargo-min-publish-age.md