Tech
编译那一步就是运行那一步:Rust 最受信任的账号被借去投毒,窗口只开了 86 分钟
2026年8月22日 · 凯文·凯利《失控》~1 min read
8 月 20 日 07 点 15 分(UTC),crates.io 上多出三个新版本:arrayref 0.3.10、append-only-vec 0.1.9、internment 0.8.7。发布账号是 BurntSushi,也就是写出 ripgrep 的 Andrew Gallant,Rust 圈里最让人放心的名字之一。而这三个版本里各埋着一根引线:谁的构建机在这个早上照常跑 cargo build 拉到它们,编译还没结束,引线就着了。
30 秒读懂
攻击者用被盗的 BurntSushi 账号发布三个恶意版本;Rust 安全团队明确表示不认为他本人作恶,几乎可以肯定是机器或凭据被盗。
三个版本各新增一个依赖 proc-macro1,名字仿冒 David Tolnay 的 proc-macro2;毒全在它的构建脚本里,编译期执行,不需要你调用任何函数。
arrayref 累计下载 2.45 亿次,近 90 天 5,370 万次,403 个 crate 把它列为直接依赖。
曝光窗口 86 分钟:07:15 发布,08:41 从索引移除;crates.io 删除 6 个攻击者创建的 crate、移除 3 个恶意版本、锁定账号。
版本钉死在 lockfile 里、构建时不自动升级的项目,这 86 分钟里没有自动中招的路径;真正兜底的是 lockfile 里那行钉死的版本号。
1 出事
86 分钟,投毒的包顶着一个最可信的名字挂在架上
从上架到下架,一顿早饭的工夫。
时间线短得反常:
时间(UTC) 发生了什么
07:15 三个恶意版本经被盗账号上架
08:41 从索引移除,曝光窗口共 86 分钟
随后 锁定被盗账号,清理攻击者名下的 crate
短,是因为抓得快。可你别看只有 86 分钟,全球的构建机没有上下班:CI 在跑、发布在跑、新项目在第一次拉依赖,这段时间里每一次解析到这三个新版本号的构建,都站在窗口里。老版本干净,毒只挂在新版本号上。
⚡ 为什么值得看: 这条链上每一环,你的项目里八成也有:一个人人都信的维护者、一块地基级的包、一段默认放行的构建脚本。这回是 Rust,换成 npm 或 PyPI,剧本一个字不用改。
2 引线
你一个函数都没调,它在编译那一刻就跑了
毒不在库代码里,在构建脚本里。
三个恶意版本的改动小得可怜:各加了一个依赖,叫 proc-macro1。名字仿的是 David Tolnay 的 proc-macro2,Rust 生态最常用的库之一。一字之差,typosquat(蹭名)的老套路:正主写得堂堂正正,仿的那个才带毒。
核心 proc-macro1 的 build.rs 构建脚本:从 base64 还原出攻击者地址,走 TLS 拉取二级载荷,证书都不校验,在编译期执行。项目只要构建时解析到它,载荷就落地,三个 crate 的函数一个都不用调。
cargo 的规矩是,依赖树里任何一个包带构建脚本,编译时先跑它。这条路也不是 Rust 独一份:npm 的 install 脚本、Python 的 setup.py,走的都是同一类「装上即执行」的门。你审计代码,审的是「我调了什么」;这毒躲在「我编译了什么」里,差着一层,审不到。
旧认知
恶意包总得等我调用它的函数才作得了恶,引进来不用,顶多占点体积。
现实
毒在构建脚本里,cargo build 解析到它那一刻就执行;这三个版本里没有一行需要你调用的恶意代码。
有人要说了:我又不写宏、不搞元编程,proc-macro 系的包跟我有什么关系?这话恰好踩在坑沿上。被投毒的三个包,管的是数组引用、只增向量、字符串驻留,全是最普通的工具件;proc-macro1 是作为它们的新依赖被拖进来的,你从头到尾没有机会选它,甚至不会知道它来过。
打个比方
像装修进场的一批板材。你还没打算用它打柜子,可木工班的规矩是料一进场,先照厂家说明书做预处理。高仿板材的说明书头一条写的是:把这家的大门钥匙配一把,寄回厂家。坏事的不是板材,是「照说明书办」这条谁都没怀疑过的规矩。
3 分量
2.45 亿次下载的地基砖,押在一份登录凭据上
攻击者挑中它,看上的正是名字背后的覆盖面。
arrayref 有多要紧?累计下载 2.45 亿次,近 90 天 5,370 万次,403 个 crate 把它列为直接依赖,再往下传出去的树没法数。你别看它拢共没几行代码,踩它的人太多,它才成了地基。
被盗的这个人,分量同样不一般。BurntSushi 在 Rust 社区的信誉攒了十几年,ripgrep 装在无数开发者的机器上。Rust 安全团队把话说得很明白:不认为 Gallant 本人作恶,几乎可以肯定是机器或凭据被盗。至于怎么盗的,是钓鱼、是哪台机器中了木马,还是别的路子,公开信息里没有定论;能确认的只有结果。
核心 2.45 亿次下载背后的信任,实际押在一台电脑、一份凭据的安全上。人没变,锁丢了。
下载量与 86 分钟曝光窗口取自 StepSecurity 的分析;处置动作以 crates.io 通报为准。
COMPILING IT WAS ENOUGH TO RUN IT ON crates.io · ATTACKER SIDE YOUR MACHINE · BUILD TIME Maintainer account stolen crates.io Malicious releases arrayref 0.3.10 append-only-vec 0.1.9 internment 0.8.7 New dependency proc-macro1 fakes proc-macro2 build.rs the build script base64 → decode address TLS → 2nd-stage payload no cert validation runs at compile time 86 min exposure · 07:15–08:41 UTC 245M arrayref all-time downloads 53.7M downloads · last 90 days 403 crates depend on it directly No function call needed — dependency resolution is the trigger. Source: The Hacker News / StepSecurity (2026-08-20). The malicious code hid in the build script of a dependency — the moment a build resolves it, it runs; no function call is ever needed.
4 信任
包管理器把「我信这个人」放大成「我信这个账号发的一切」
信任的自动传递是效率,也是攻击面。
凯文·凯利在《失控》里写过:去中心化生态的力量来自互相依赖,信任像营养一样在网络里自动流动,不用谁居中审批,生态才长得快。crates.io 就是这样一张网。你信 BurntSushi,cargo 替你把这份信任自动续到他账号发的每个版本;403 个 crate 信 arrayref,几十万个项目没人过问就继承了这份信任。而且这份信任会滚利:越多人信 arrayref,新项目越敢闭眼引它,信它的人就更多。滚到 2.45 亿次下载,中间没有任何一环需要谁点头。效率是真效率。可管道不辨好坏,账号一丢,毒顺着同一套管道、以同样的速度,86 分钟灌向全球的构建机。
这事之后,社区又把一个老争论吵起来了:build.rs 凭什么能随意联网、任意执行?一派说这是生态跑起来的前提,多少包靠它找系统库、生成绑定;另一派说这就是每回投毒都能得手的原因。吵了不止一年,这回的案子给后一派又添了份证据。
我自己在这条管道上交过学费。2021 年我一个小工具的构建突然挂了:上游一个 npm 小包,作者删库跑路,打包机上恰好有缓存才没停摆。从那以后我把整个 node_modules 定期拷进一块移动硬盘,贴纸上写着俩字:别笑。这回的事让我把那块硬盘重新掂量了一遍。它防的是「包没了」,防不了「包变了」;真拉到投毒版本,它只会忠实地替我把毒多备一份。对付「变了」,得靠把版本钉死。
核心 crates.io 事后删除 6 个攻击者创建的 crate(proc-macro1、proc-macro-en、aovine、arone、aronenao、tinymember),移除 3 个恶意版本,锁定被盗账号。这一轮账算清了;让构建脚本随意联网的规则,还是原样。
5 你能做的
这对你意味着什么
三件今天就能动手的事。
第一,lockfile 提交进仓库,CI 构建加 --locked(npm、pnpm 对应 frozen-lockfile)。版本钉死之后,上游发什么新版本都进不来,升级只发生在你主动动手那一次。这次 86 分钟的窗口,对钉死版本的项目就构不成自动中招的路径。钉死也有边界:总有要升级的一天,那一天你得睁着眼过。
第二,把升级依赖当一次正经变更来审。diff 里盯两样:版本号,还有新冒出来的传递依赖。proc-macro1 与 proc-macro2 一字之差,眼睛扫过去很容易放行;名字长得像大牌的新面孔,多看一眼再放。
第三,别默认信构建脚本。CI 上能锁网络出口就锁,构建阶段只放行 registry 的域名。这拦不住所有花样,但至少让「编译期偷偷外联」这条路变吵、变难,出事时日志里有迹可查。
这三条针对的都是「构建时自动拉到新恶意版本」这一条路径;86 分钟窗口内若已拉取并编译过,属于事件响应的范畴,本文不覆盖。
至于 BurntSushi,人没问题。可工具链认的从来只有账号这一档,账号是会丢的。在生态学会区分「人」和「账号」之前,你能攥在手里的,就是自己仓库里那份 lockfile。
本文框架取材自凯文·凯利《失控》;事件与数字来自 The Hacker News 与 StepSecurity(2026-08-20)的报道与分析,细节以原始报道为准。
技术
编译那一步就是运行那一步:Rust 最受信任的账号被借去投毒,窗口只开了 86 分钟
2026年8月22日 · 凯文·凯利《失控》约 6 分钟
8 月 20 日 07 点 15 分(UTC),crates.io 上多出三个新版本:arrayref 0.3.10、append-only-vec 0.1.9、internment 0.8.7。发布账号是 BurntSushi,也就是写出 ripgrep 的 Andrew Gallant,Rust 圈里最让人放心的名字之一。而这三个版本里各埋着一根引线:谁的构建机在这个早上照常跑 cargo build 拉到它们,编译还没结束,引线就着了。
30 秒读懂
攻击者用被盗的 BurntSushi 账号发布三个恶意版本;Rust 安全团队明确表示不认为他本人作恶,几乎可以肯定是机器或凭据被盗。
三个版本各新增一个依赖 proc-macro1,名字仿冒 David Tolnay 的 proc-macro2;毒全在它的构建脚本里,编译期执行,不需要你调用任何函数。
arrayref 累计下载 2.45 亿次,近 90 天 5,370 万次,403 个 crate 把它列为直接依赖。
曝光窗口 86 分钟:07:15 发布,08:41 从索引移除;crates.io 删除 6 个攻击者创建的 crate、移除 3 个恶意版本、锁定账号。
版本钉死在 lockfile 里、构建时不自动升级的项目,这 86 分钟里没有自动中招的路径;真正兜底的是 lockfile 里那行钉死的版本号。
1 出事
86 分钟,投毒的包顶着一个最可信的名字挂在架上
从上架到下架,一顿早饭的工夫。
时间线短得反常:
时间(UTC) 发生了什么
07:15 三个恶意版本经被盗账号上架
08:41 从索引移除,曝光窗口共 86 分钟
随后 锁定被盗账号,清理攻击者名下的 crate
短,是因为抓得快。可你别看只有 86 分钟,全球的构建机没有上下班:CI 在跑、发布在跑、新项目在第一次拉依赖,这段时间里每一次解析到这三个新版本号的构建,都站在窗口里。老版本干净,毒只挂在新版本号上。
⚡ 为什么值得看: 这条链上每一环,你的项目里八成也有:一个人人都信的维护者、一块地基级的包、一段默认放行的构建脚本。这回是 Rust,换成 npm 或 PyPI,剧本一个字不用改。
2 引线
你一个函数都没调,它在编译那一刻就跑了
毒不在库代码里,在构建脚本里。
三个恶意版本的改动小得可怜:各加了一个依赖,叫 proc-macro1。名字仿的是 David Tolnay 的 proc-macro2,Rust 生态最常用的库之一。一字之差,typosquat(蹭名)的老套路:正主写得堂堂正正,仿的那个才带毒。
核心 proc-macro1 的 build.rs 构建脚本:从 base64 还原出攻击者地址,走 TLS 拉取二级载荷,证书都不校验,在编译期执行。项目只要构建时解析到它,载荷就落地,三个 crate 的函数一个都不用调。
cargo 的规矩是,依赖树里任何一个包带构建脚本,编译时先跑它。这条路也不是 Rust 独一份:npm 的 install 脚本、Python 的 setup.py,走的都是同一类「装上即执行」的门。你审计代码,审的是「我调了什么」;这毒躲在「我编译了什么」里,差着一层,审不到。
旧认知
恶意包总得等我调用它的函数才作得了恶,引进来不用,顶多占点体积。
现实
毒在构建脚本里,cargo build 解析到它那一刻就执行;这三个版本里没有一行需要你调用的恶意代码。
有人要说了:我又不写宏、不搞元编程,proc-macro 系的包跟我有什么关系?这话恰好踩在坑沿上。被投毒的三个包,管的是数组引用、只增向量、字符串驻留,全是最普通的工具件;proc-macro1 是作为它们的新依赖被拖进来的,你从头到尾没有机会选它,甚至不会知道它来过。
打个比方
像装修进场的一批板材。你还没打算用它打柜子,可木工班的规矩是料一进场,先照厂家说明书做预处理。高仿板材的说明书头一条写的是:把这家的大门钥匙配一把,寄回厂家。坏事的不是板材,是「照说明书办」这条谁都没怀疑过的规矩。
3 分量
2.45 亿次下载的地基砖,押在一份登录凭据上
攻击者挑中它,看上的正是名字背后的覆盖面。
arrayref 有多要紧?累计下载 2.45 亿次,近 90 天 5,370 万次,403 个 crate 把它列为直接依赖,再往下传出去的树没法数。你别看它拢共没几行代码,踩它的人太多,它才成了地基。
被盗的这个人,分量同样不一般。BurntSushi 在 Rust 社区的信誉攒了十几年,ripgrep 装在无数开发者的机器上。Rust 安全团队把话说得很明白:不认为 Gallant 本人作恶,几乎可以肯定是机器或凭据被盗。至于怎么盗的,是钓鱼、是哪台机器中了木马,还是别的路子,公开信息里没有定论;能确认的只有结果。
核心 2.45 亿次下载背后的信任,实际押在一台电脑、一份凭据的安全上。人没变,锁丢了。
下载量与 86 分钟曝光窗口取自 StepSecurity 的分析;处置动作以 crates.io 通报为准。
编译那一步,就是运行那一步 crates.io · 攻击者一侧 你的机器 · 构建时 维护者账号 被盗 crates.io 发布恶意版本 arrayref 0.3.10 append-only-vec 0.1.9 internment 0.8.7 新增依赖 proc-macro1 仿冒 proc-macro2 build.rs 构建脚本 base64 还原地址 TLS 拉取二级载荷 不验证证书 编译期执行 86 分钟 曝光窗口 07:15–08:41 UTC 2.45 亿 arrayref 累计下载 5,370 万 近 90 天下载 403 直接依赖它的 crate 不需要调用任何函数——构建解析到依赖,就是触发 来源:The Hacker News / StepSecurity(2026-08-20)。恶意代码藏在依赖的构建脚本里——构建一旦解析到它,就会执行,不需要调用任何函数。
4 信任
包管理器把「我信这个人」放大成「我信这个账号发的一切」
信任的自动传递是效率,也是攻击面。
凯文·凯利在《失控》里写过:去中心化生态的力量来自互相依赖,信任像营养一样在网络里自动流动,不用谁居中审批,生态才长得快。crates.io 就是这样一张网。你信 BurntSushi,cargo 替你把这份信任自动续到他账号发的每个版本;403 个 crate 信 arrayref,几十万个项目没人过问就继承了这份信任。而且这份信任会滚利:越多人信 arrayref,新项目越敢闭眼引它,信它的人就更多。滚到 2.45 亿次下载,中间没有任何一环需要谁点头。效率是真效率。可管道不辨好坏,账号一丢,毒顺着同一套管道、以同样的速度,86 分钟灌向全球的构建机。
这事之后,社区又把一个老争论吵起来了:build.rs 凭什么能随意联网、任意执行?一派说这是生态跑起来的前提,多少包靠它找系统库、生成绑定;另一派说这就是每回投毒都能得手的原因。吵了不止一年,这回的案子给后一派又添了份证据。
我自己在这条管道上交过学费。2021 年我一个小工具的构建突然挂了:上游一个 npm 小包,作者删库跑路,打包机上恰好有缓存才没停摆。从那以后我把整个 node_modules 定期拷进一块移动硬盘,贴纸上写着俩字:别笑。这回的事让我把那块硬盘重新掂量了一遍。它防的是「包没了」,防不了「包变了」;真拉到投毒版本,它只会忠实地替我把毒多备一份。对付「变了」,得靠把版本钉死。
核心 crates.io 事后删除 6 个攻击者创建的 crate(proc-macro1、proc-macro-en、aovine、arone、aronenao、tinymember),移除 3 个恶意版本,锁定被盗账号。这一轮账算清了;让构建脚本随意联网的规则,还是原样。
5 你能做的
这对你意味着什么
三件今天就能动手的事。
第一,lockfile 提交进仓库,CI 构建加 --locked(npm、pnpm 对应 frozen-lockfile)。版本钉死之后,上游发什么新版本都进不来,升级只发生在你主动动手那一次。这次 86 分钟的窗口,对钉死版本的项目就构不成自动中招的路径。钉死也有边界:总有要升级的一天,那一天你得睁着眼过。
第二,把升级依赖当一次正经变更来审。diff 里盯两样:版本号,还有新冒出来的传递依赖。proc-macro1 与 proc-macro2 一字之差,眼睛扫过去很容易放行;名字长得像大牌的新面孔,多看一眼再放。
第三,别默认信构建脚本。CI 上能锁网络出口就锁,构建阶段只放行 registry 的域名。这拦不住所有花样,但至少让「编译期偷偷外联」这条路变吵、变难,出事时日志里有迹可查。
这三条针对的都是「构建时自动拉到新恶意版本」这一条路径;86 分钟窗口内若已拉取并编译过,属于事件响应的范畴,本文不覆盖。
至于 BurntSushi,人没问题。可工具链认的从来只有账号这一档,账号是会丢的。在生态学会区分「人」和「账号」之前,你能攥在手里的,就是自己仓库里那份 lockfile。
本文框架取材自凯文·凯利《失控》;事件与数字来自 The Hacker News 与 StepSecurity(2026-08-20)的报道与分析,细节以原始报道为准。
テクノロジー
编译那一步就是运行那一步:Rust 最受信任的账号被借去投毒,窗口只开了 86 分钟
2026年8月22日 · 凯文·凯利《失控》約 6 分
8 月 20 日 07 点 15 分(UTC),crates.io 上多出三个新版本:arrayref 0.3.10、append-only-vec 0.1.9、internment 0.8.7。发布账号是 BurntSushi,也就是写出 ripgrep 的 Andrew Gallant,Rust 圈里最让人放心的名字之一。而这三个版本里各埋着一根引线:谁的构建机在这个早上照常跑 cargo build 拉到它们,编译还没结束,引线就着了。
30 秒读懂
攻击者用被盗的 BurntSushi 账号发布三个恶意版本;Rust 安全团队明确表示不认为他本人作恶,几乎可以肯定是机器或凭据被盗。
三个版本各新增一个依赖 proc-macro1,名字仿冒 David Tolnay 的 proc-macro2;毒全在它的构建脚本里,编译期执行,不需要你调用任何函数。
arrayref 累计下载 2.45 亿次,近 90 天 5,370 万次,403 个 crate 把它列为直接依赖。
曝光窗口 86 分钟:07:15 发布,08:41 从索引移除;crates.io 删除 6 个攻击者创建的 crate、移除 3 个恶意版本、锁定账号。
版本钉死在 lockfile 里、构建时不自动升级的项目,这 86 分钟里没有自动中招的路径;真正兜底的是 lockfile 里那行钉死的版本号。
1 出事
86 分钟,投毒的包顶着一个最可信的名字挂在架上
从上架到下架,一顿早饭的工夫。
时间线短得反常:
时间(UTC) 发生了什么
07:15 三个恶意版本经被盗账号上架
08:41 从索引移除,曝光窗口共 86 分钟
随后 锁定被盗账号,清理攻击者名下的 crate
短,是因为抓得快。可你别看只有 86 分钟,全球的构建机没有上下班:CI 在跑、发布在跑、新项目在第一次拉依赖,这段时间里每一次解析到这三个新版本号的构建,都站在窗口里。老版本干净,毒只挂在新版本号上。
⚡ 为什么值得看: 这条链上每一环,你的项目里八成也有:一个人人都信的维护者、一块地基级的包、一段默认放行的构建脚本。这回是 Rust,换成 npm 或 PyPI,剧本一个字不用改。
2 引线
你一个函数都没调,它在编译那一刻就跑了
毒不在库代码里,在构建脚本里。
三个恶意版本的改动小得可怜:各加了一个依赖,叫 proc-macro1。名字仿的是 David Tolnay 的 proc-macro2,Rust 生态最常用的库之一。一字之差,typosquat(蹭名)的老套路:正主写得堂堂正正,仿的那个才带毒。
核心 proc-macro1 的 build.rs 构建脚本:从 base64 还原出攻击者地址,走 TLS 拉取二级载荷,证书都不校验,在编译期执行。项目只要构建时解析到它,载荷就落地,三个 crate 的函数一个都不用调。
cargo 的规矩是,依赖树里任何一个包带构建脚本,编译时先跑它。这条路也不是 Rust 独一份:npm 的 install 脚本、Python 的 setup.py,走的都是同一类「装上即执行」的门。你审计代码,审的是「我调了什么」;这毒躲在「我编译了什么」里,差着一层,审不到。
旧认知
恶意包总得等我调用它的函数才作得了恶,引进来不用,顶多占点体积。
现实
毒在构建脚本里,cargo build 解析到它那一刻就执行;这三个版本里没有一行需要你调用的恶意代码。
有人要说了:我又不写宏、不搞元编程,proc-macro 系的包跟我有什么关系?这话恰好踩在坑沿上。被投毒的三个包,管的是数组引用、只增向量、字符串驻留,全是最普通的工具件;proc-macro1 是作为它们的新依赖被拖进来的,你从头到尾没有机会选它,甚至不会知道它来过。
打个比方
像装修进场的一批板材。你还没打算用它打柜子,可木工班的规矩是料一进场,先照厂家说明书做预处理。高仿板材的说明书头一条写的是:把这家的大门钥匙配一把,寄回厂家。坏事的不是板材,是「照说明书办」这条谁都没怀疑过的规矩。
3 分量
2.45 亿次下载的地基砖,押在一份登录凭据上
攻击者挑中它,看上的正是名字背后的覆盖面。
arrayref 有多要紧?累计下载 2.45 亿次,近 90 天 5,370 万次,403 个 crate 把它列为直接依赖,再往下传出去的树没法数。你别看它拢共没几行代码,踩它的人太多,它才成了地基。
被盗的这个人,分量同样不一般。BurntSushi 在 Rust 社区的信誉攒了十几年,ripgrep 装在无数开发者的机器上。Rust 安全团队把话说得很明白:不认为 Gallant 本人作恶,几乎可以肯定是机器或凭据被盗。至于怎么盗的,是钓鱼、是哪台机器中了木马,还是别的路子,公开信息里没有定论;能确认的只有结果。
核心 2.45 亿次下载背后的信任,实际押在一台电脑、一份凭据的安全上。人没变,锁丢了。
下载量与 86 分钟曝光窗口取自 StepSecurity 的分析;处置动作以 crates.io 通报为准。
ビルドした時点で、実行されていた crates.io · 攻撃者側 あなたのマシン · ビルド時 保守者アカウント 乗っ取り crates.io 悪意ある版を公開 arrayref 0.3.10 append-only-vec 0.1.9 internment 0.8.7 依存を追加 proc-macro1 proc-macro2 に偽装 build.rs ビルドスクリプト base64 でアドレス復元 TLS で第二段を取得 証明書は未検証 コンパイル時に実行 86 分 露出時間 07:15–08:41 UTC 2.45 億 arrayref 累計DL 5,370 万 直近 90 日のDL 403 直接依存する crate 関数を呼ぶ必要はない——依存の解決そのものが引き金 出典:The Hacker News / StepSecurity(2026-08-20)。悪意あるコードは依存パッケージのビルドスクリプトに潜む。ビルドが依存を解決した時点で実行され、関数を呼ぶ必要はない。
4 信任
包管理器把「我信这个人」放大成「我信这个账号发的一切」
信任的自动传递是效率,也是攻击面。
凯文·凯利在《失控》里写过:去中心化生态的力量来自互相依赖,信任像营养一样在网络里自动流动,不用谁居中审批,生态才长得快。crates.io 就是这样一张网。你信 BurntSushi,cargo 替你把这份信任自动续到他账号发的每个版本;403 个 crate 信 arrayref,几十万个项目没人过问就继承了这份信任。而且这份信任会滚利:越多人信 arrayref,新项目越敢闭眼引它,信它的人就更多。滚到 2.45 亿次下载,中间没有任何一环需要谁点头。效率是真效率。可管道不辨好坏,账号一丢,毒顺着同一套管道、以同样的速度,86 分钟灌向全球的构建机。
这事之后,社区又把一个老争论吵起来了:build.rs 凭什么能随意联网、任意执行?一派说这是生态跑起来的前提,多少包靠它找系统库、生成绑定;另一派说这就是每回投毒都能得手的原因。吵了不止一年,这回的案子给后一派又添了份证据。
我自己在这条管道上交过学费。2021 年我一个小工具的构建突然挂了:上游一个 npm 小包,作者删库跑路,打包机上恰好有缓存才没停摆。从那以后我把整个 node_modules 定期拷进一块移动硬盘,贴纸上写着俩字:别笑。这回的事让我把那块硬盘重新掂量了一遍。它防的是「包没了」,防不了「包变了」;真拉到投毒版本,它只会忠实地替我把毒多备一份。对付「变了」,得靠把版本钉死。
核心 crates.io 事后删除 6 个攻击者创建的 crate(proc-macro1、proc-macro-en、aovine、arone、aronenao、tinymember),移除 3 个恶意版本,锁定被盗账号。这一轮账算清了;让构建脚本随意联网的规则,还是原样。
5 你能做的
这对你意味着什么
三件今天就能动手的事。
第一,lockfile 提交进仓库,CI 构建加 --locked(npm、pnpm 对应 frozen-lockfile)。版本钉死之后,上游发什么新版本都进不来,升级只发生在你主动动手那一次。这次 86 分钟的窗口,对钉死版本的项目就构不成自动中招的路径。钉死也有边界:总有要升级的一天,那一天你得睁着眼过。
第二,把升级依赖当一次正经变更来审。diff 里盯两样:版本号,还有新冒出来的传递依赖。proc-macro1 与 proc-macro2 一字之差,眼睛扫过去很容易放行;名字长得像大牌的新面孔,多看一眼再放。
第三,别默认信构建脚本。CI 上能锁网络出口就锁,构建阶段只放行 registry 的域名。这拦不住所有花样,但至少让「编译期偷偷外联」这条路变吵、变难,出事时日志里有迹可查。
这三条针对的都是「构建时自动拉到新恶意版本」这一条路径;86 分钟窗口内若已拉取并编译过,属于事件响应的范畴,本文不覆盖。
至于 BurntSushi,人没问题。可工具链认的从来只有账号这一档,账号是会丢的。在生态学会区分「人」和「账号」之前,你能攥在手里的,就是自己仓库里那份 lockfile。
本文框架取材自凯文·凯利《失控》;事件与数字来自 The Hacker News 与 StepSecurity(2026-08-20)的报道与分析,细节以原始报道为准。