2025 年,一个代号 "Mini Shai-Hulud" 的供应链攻击通过 TanStack 的 npm 包潜入了开发者的工具链。OpenAI 近日公开了完整的应急响应细节——从签名证书的保护到 macOS 用户必须在 2026 年 6 月 12 日前更新应用的硬性要求。这不是又一篇"供应链很重要"的口号文章,而是从一次真实事件中提取的攻防时间线、影响范围和可落地的加固手段。
攻击是怎么发生的
"Mini Shai-Hulud"——名字取自沙丘中的小型沙虫——攻击者通过某种方式获取了 TanStack 维护者的 npm 发布权限,在合法包中注入了恶意代码。关键特征:
- 寄生在合法包内,不是新建一个仿冒包,而是直接篡改已有版本。
- 恶意代码在安装或构建阶段执行,而非运行时,这让它在 CI 环境中同样生效。
- 攻击窗口相对短暂,但足以在下游依赖链中扩散。
TanStack 系列库(Table、Query、Router 等)在 React 生态中使用量极大,单周下载量动辄数百万。一次投毒,涟漪效应远超想象。
OpenAI 的响应:做了什么、影响了谁
OpenAI 在事件发生后启动了多层防御:
- 内部系统隔离——确认恶意代码是否进入了 OpenAI 的构建或开发环境,并对受影响节点做了网络隔离与重新镜像。
- 签名证书保护——OpenAI 用于签发 macOS 应用的证书是高价值资产。如果攻击者通过投毒获取了签名密钥,他们可以伪造"来自 OpenAI"的可信应用。OpenAI 确认证书未被泄露,但采取了轮换和额外保护措施。
- macOS 应用强制更新——最引人注目的一条:OpenAI 要求 macOS 用户必须在 2026 年 6 月 12 日 前更新到最新版 OpenAI 应用。超过该日期的旧版本将不再被信任。这本质上是用时间窗口来切断潜在被污染版本的存活期。
受影响范围:任何在攻击窗口期间安装了被篡改 TanStack 版本的项目,都可能被波及。OpenAI 自身的开发工具链也使用了 TanStack,因此他们既是受害者也是响应者。
你的项目可能也暴露了:快速排查
如果你在 2025 年的某个时间段安装或更新了 TanStack 相关包,需要确认是否命中了被污染版本。以下是可直接运行的排查命令:
# 检查 package-lock.json 中所有 tanstack 相关包的版本与 integrity hash
# 如果 integrity 字段缺失或与官方发布不一致,说明可能有问题
grep -A2 '"@tanstack' package-lock.json | \
awk '/"resolved"/ {print $2} /"integrity"/ {print $2}'
# 用 npm audit 检查已知恶意版本(npm 会在 advisories 中标记)
npm audit --production
# 更彻底:列出所有 tanstack 依赖的精确版本号,与 npmjs.com 上的官方版本对比
npm ls @tanstack/react-query @tanstack/react-table @tanstack/react-router 2>/dev/null
如果发现版本号不在官方发布列表中,或 integrity 字段为空,立即执行:
# 删除 node_modules 和 lockfile,从干净源重新安装
rm -rf node_modules package-lock.json
npm install --prefer-online
供应链加固:从这次事件中提取的防御清单
单次事件的响应是被动防守。以下是主动加固手段,每一条都对应 Mini Shai-Hulud 利用过的或可能利用的路径。
1. 锁定 integrity,拒绝无 hash 的包
npm 的 integrity 字段(SHA-512)是包内容的不可篡改指纹。在 CI 中强制校验:
# .github/workflows/ci.yml — 在 install 之前校验 lockfile完整性
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Verify lockfile integrity hashes exist
run: |
# 检查 package-lock.json 中是否所有包都有 integrity 字段
MISSING=$(node -e "
const lock = require('./package-lock.json');
let count = 0;
for (const [name, entry] of Object.entries(lock.packages || {})) {
if (!entry.integrity) {
console.log('NO integrity: ' + name);
count++;
}
}
if (count > 0) process.exit(1);
")
echo "$MISSING"
- name: Install dependencies
run: npm ci
npm ci 本身会校验 integrity,但如果 lockfile 被篡改删除了该字段,它可能静默跳过。上面的脚本提前拦截。
2. 用 npm provenance 验证包的构建来源
npm 在 2023 年推出了 provenance 功能——包发布时附带来自 CI 的签名声明,证明"这个包确实是从这个仓库的这段代码构建出来的"。在安装时验证:
# 安装时要求 provenance 签名(npm 10+)
npm install @tanstack/react-query --require-provenance
# 或在 .npmrc 中全局启用
echo "require-provenance=true" >> .npmrc
如果包没有 provenance,安装会失败。这直接对抗"有人用维护者账号发布了一个非官方构建的版本"这类攻击。
3. 限制依赖的版本范围,避免自动吃到新版本
// package.json — 不要用 * 或 x-range,锁定最小范围
{
"dependencies": {
// ❌ 危险:任何新版本都会被安装
"@tanstack/react-query": "^5",
// ✅ 更安全:只允许补丁级更新,且配合 lockfile
"@tanstack/react-query": "~5.62.0"
}
}
配合 Renovate 或 Dependabot 的自动 PR,你可以在审查后再升级,而不是被 npm update 一键拉入未知版本。
4. 对高价值签名密钥做隔离存储
OpenAI 最担心的不是恶意代码本身,而是签名证书被窃取。如果你也用证书签发应用或驱动:
- 证书存储在 HSM(硬件安全模块)或云 KMS 中,不在开发者机器上。
- 签名流程在隔离的 CI job 中完成,该 job 只能读取证书、不能写入网络。
- 证书设置短有效期 + 自动轮换。
# macOS 码签名示例:用环境变量注入,不把 .p12 文件放在仓库里
# CI 中:
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
codesign --sign "$DEVELOPER_ID" --timestamp --options runtime \
dist/OpenAI.app
5. 建立版本信任基线
# 一次性生成当前所有依赖的 integrity 基线文件
node -e "
const lock = require('./package-lock.json');
const baseline = {};
for (const [path, entry] of Object.entries(lock.packages || {})) {
if (entry.integrity) {
baseline[path] = entry.integrity;
}
}
require('fs').writeFileSync('dependency-baseline.json',
JSON.stringify(baseline, null, 2));
"
# 后续每次 npm install 后,对比基线是否发生变化
node -e "
const lock = require('./package-lock.json');
const baseline = require('./dependency-baseline.json');
for (const [path, entry] of Object.entries(lock.packages || {})) {
const expected = baseline[path];
if (expected && entry.integrity !== expected) {
console.error('INTEGRITY CHANGED: ' + path);
console.error(' expected: ' + expected);
console.error(' actual: ' + entry.integrity);
}
}
"
任何 integrity 变化都必须经过人工确认——要么是合法版本升级,要么就是异常。
收尾:从被动响应到主动设计
Mini Shai-Hulud 事件的核心教训不是"npm 不安全",而是供应链攻击的性价比极高——攻破一个维护者账号,就能同时影响数百万下游项目。防御的思路不是消除风险,而是让攻击的成本高于收益:
| 防御层 | 对抗的攻击路径 | 实施成本 |
|---|---|---|
| integrity 校验 | 包内容被篡改 | 低 |
| provenance 要求 | 非官方构建被发布 | 低(需 npm 10+) |
| 版本范围收紧 + 人工审查 | 自动吃到恶意新版本 | 中 |
| 签名证书隔离 | 高价值密钥被窃 | 中-高 |
| integrity 基线对比 | 悄悄替换已锁定版本 | 低 |
OpenAI 给 macOS 用户设了 2026 年 6 月 12 日的硬截止日期,本质上是在用时间杠杆:超过这个日期,旧版本不再可信,攻击者即使拿到了签名也无法让被污染版本继续存活。这个思路同样适用于你自己的项目——给依赖设定"过期时间",定期强制刷新,比永远信任一个锁定的版本更安全。
供应链安全不是一次性加固,而是持续的成本博弈。从今天开始,至少把 npm ci 和 integrity 校验加进你的 CI——这是成本最低、收益最高的一步。