
最近供应链安全又出事了。2026年5月,一个叫Shai-Hulud的蠕虫病毒搞定了42个TanStack包,包括@tanstack/react-router——一个装在数百万个JavaScript项目里的库。从上线到被发现大概3个小时,够长了。如果你那天正好装了依赖,可能已经中招了。
这篇不是写给库维护者看的。是写给咱们这些普通开发者的——谁还没npm install过呢。
"冷知识"1
只存活了约3小时。@tanstack/react-router每周下载量是1270万次。也就是说在那3小时窗口内,仅这一个包就有约22.5万次下载。这只是被攻击的42个包里的一个。
以前供应链攻击感觉是别人的事——大库被黑,维护者修复,一切如常。Shai-Hulud改写了剧本:这玩意儿会自动扩散到受害者维护的所有包,把普通开发者变成无意识的病毒分发者。下面说说到底发生了什么,以及你现在能做点什么。
CI工作流用了pull_request_target,这玩意儿用仓库的受信任权限运行——包括访问密钥和跟真实发布流水线共享的构建缓存。
但它同时检出了fork的代码并执行,用于性能测试。这是个危险组合:陌生人的代码跑在你自己仓库的信任环境里。
攻击者没有立刻偷东西。只是污染了共享缓存然后等着。几个小时后,合法发布流水线运行了,在不知情的情况下捡起了被篡改的缓存,用TanStack自己的有效凭证发布了恶意包。
关键洞察:这个配置错误并不明显。性能测试的意图是合理的;问题在于没意识到pull_request_target + "运行PR的代码"无论你想做什么,都是危险组合。
"冷知识"2
这个蠕虫有个死亡开关。它植入了一个后台服务,每60秒用窃取的GitHub令牌轮询api.github.com/user。如果令牌被吊销——也就是GitHub返回4xx响应——服务就会触发rm -rf ~/,擦掉用户的整个主目录。所以你得先禁用并移除监控服务再吊销任何凭证。
pull_request_target Pwn Request专门针对公开仓库——也就是陌生人可以提PR的那种。
话虽如此,攻击的其他部分(workflow之间的缓存污染、OIDC令牌过度授权)如果你的GitHub Actions有类似配置错误,私有仓库也逃不掉。
到这儿听起来像是库维护者的问题?还真不是。你不需要维护一个下载量几百万的库才能中招。你只需要在错误的时间运行npm install。一旦被黑的包进了你的node_modules,你也成了链条的一部分。
.npmrcmin-release-age=7
ignore-scripts=true
min-release-age=7 阻止安装发布不足7天的包ignore-scripts=true 阻止生命周期脚本如preinstall/prepare在安装时运行——这正是恶意optionalDependency利用的入口pnpm-workspace.yamlminimumReleaseAge: 10080 # 分钟 — 7天
minimumReleaseAge默认开启,默认值1天preinstall/postinstall脚本min-release-age — 不支持。v1没有等效配置。yarn install --ignore-scripts存在,但只是CLI参数.yarnrc.ymlnpmMinimalAgeGate: 10080 # 分钟 — 7天
enableScripts: false # ignore-scripts等效配置
ENV npm_config_min_release_age=3
npm ci会读取.npmrc设置和环境变量——npm_config_*约定对install和ci都生效。ENV npm_config_min_release_age=3会被静默忽略方案A — 仅在Docker中升级到Node 24
FROM node:24-alpine
# npm v11已包含,min-release-age可用
ENV npm_config_min_release_age=3
方案B — 保持当前Node版本,手动安装npm v11
FROM node:22-alpine
RUN npm install -g npm@11
ENV npm_config_min_release_age=3
min-release-age官方文档minimumReleaseAge配置npmMinimalAgeGate配置enableScripts配置原文链接:https://dev.to/...