Mini Shai-Hulud 供应链攻击事件解析与检测指南

Mini Shai-Hulud 供应链攻击事件解析与检测指南
Mr. O2026 年 5 月 11 日,出现了一起针对 npm 与 PyPI 生态的协同供应链攻击。此次攻击同时波及多个知名的 npm 与 PyPI 软件包。
这不是一次普通的恶意包投放事件。攻击者并未简单依赖窃取 npm 凭据,而是利用 GitHub Actions、缓存污染、OIDC Token 滥用、恶意依赖、持久化守护进程以及多通道 C2 基础设施,构建了一条完整的供应链入侵链路。
TanStack 相关恶意版本发布具体时间:北京时间 5 月 12 日凌晨 3:20 至 3:26(UTC+8)
一、事件概览
本次攻击影响了 npm 与 PyPI 两大生态中的多个高价值包。
主要受影响的软件包:
@tanstack下的软件包,例如@tanstack/react-router,这是 React 生态中最广泛使用的路由库之一,每周下载量约 1200 万次,@tanstack/vue-router也受到影响。@uipath下的软件包,包括 UiPath 企业自动化平台相关工具、CLI 与 Agent SDK@mistralai/mistralai,即 Mistral AI 官方 TypeScript 客户端@opensearch-project/opensearch- PyPI 上的
mistralai: 2.4.6 - PyPI 上的
guardrails-ai: 0.10.1
npm 团队已经注意到该攻击活动,并快速移除了恶意版本。
可以在文章结尾查看已知的具体受影响的软件包。
二、TanStack 攻击链:GitHub Actions 成为突破口
TanStack 的失陷源于一条针对 GitHub Actions 的复杂漏洞利用链(包含三个漏洞):
攻击链大致如下:
- 攻击者 fork 了
TanStack/router仓库。 - 攻击者将 fork 后的仓库重命名为
zblgg/configuration规避了 Fork 列表的审查。 - 攻击者提交 Pull Request,触发目标仓库中的
pull_request_target工作流。 - 该工作流错误地 checkout 并执行了攻击者 fork 中的代码。
- 攻击者借此污染 GitHub Actions 缓存,将恶意 pnpm store 写入缓存。
- 后续合法维护者的 PR 被合并到 main 分支时,release workflow 恢复了被污染的缓存。
- 攻击者控制的二进制文件从 GitHub Actions Runner 的进程内存中读取 OIDC Token。
- 攻击者使用这些 Token 发布恶意 npm 包版本。
这一链路最值得关注的地方在于:攻击者无需窃取 npm 凭据,也能完成恶意包发布。
这说明在现代软件供应链中,包管理账号本身已不再是唯一的关键资产。CI/CD 工作流、缓存、OIDC Token、发布权限和工作流边界同样是高价值攻击面。
UiPath 的感染变种
在 @tanstack 受到攻击后不久,@uipath 也宣告失陷。这些包使用一个预安装脚本(node setup.mjs)下载 Bun 运行时环境并执行恶意 payload。这与之前 SAP 失陷事件中的交付机制相同。UiPath 变种使用了重新混淆过的 payload 和不同的 Campaign Key,但连接的是相同的 C2(命令与控制)基础设施。
三、恶意 Payload:凭证窃取与蠕虫式传播
本次恶意 payload 具备自我传播能力,主要窃取以下类型的凭据和身份材料:
它主要锁定以下目标:
- CI/CD 令牌(GitHub Actions OIDC、GitLab、CircleCI)
- 云服务凭证(AWS IMDSv2、GCP、Azure)
- Kubernetes 服务账号
- HashiCorp Vault
- Package registry tokens
蠕虫行为:恶意软件会使用窃取来的 npm 令牌和 GitHub Actions OIDC 令牌,进一步向受害者拥有写入权限的其他包发布投毒版本,从而在 npm 生态系统中像蠕虫一样迅速蔓延。
破坏性的本地守护进程
如果恶意软件发现了有效的目标 ghp_ 开头的 GitHub Personal Access Token 或 gho_ 开头的 OAuth Token,它在本地会安装一个持久化守护进程:
macOS:
1 | ~/Library/LaunchAgents/com.user.gh-token-monitor.plist |
Linux:
1 | ~/.config/systemd/user/gh-token-monitor.service |
如果它因 GitHub 令牌被撤销而收到 40X 错误,该进程会尝试执行 rm -rf ~/,直接清空用户主目录! 不过该守护进程会在运行 24 小时后自动退出。
注:与之前的 Mini Shai-Hulud 变种一样,该恶意软件会检查系统是否配置为俄语,如果是,则直接终止运行,不窃取任何数据。
四、检查指南
- 排查软件包:搜索 lockfiles 和 CI 日志,是否出现受影响的软件包版本。检查包根目录下是否存在
router_init.js或setup.mjs。 - 检查并清除持久化威胁:在开发者机器上搜索
gh-token-monitor守护进程并将其删除(路径见下方指标)。 - 轮换所有凭证:如果怀疑受到影响,请立即轮换 GitHub 令牌、npm 令牌、AWS 凭证、Vault 令牌、Kubernetes 服务账号和 CI/CD 机密信息。
警告:在吊销 GitHub 令牌之前,请务必先检查并移除持久化守护进程,以免触发恶意软件的(
rm -rf ~/)命令。 - 检查 IDE 目录:检查
.claude/和.vscode/目录中是否残留router_runtime.js或setup.mjs。这些文件在执行npm uninstall后仍可能驻留。 - 屏蔽 C2 目标域名:在 DNS/代理层面拦截
git-tanstack.com以及*.getsession.org。
文件
| 文件名称 | 详情 | SHA-256 / SHA-1 |
|---|---|---|
router_init.js |
2,341,681 bytes | SHA256: ``ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c` |
router_init.js |
2,339,346 bytes | SHA256:2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96SHA1: e7d582b98ca80690883175470e96f703ef6dc497 |
setup.mjs |
5,047 bytes | SHA256:2258284d65f63829bd67eaba01ef6f1ada2f593f9bbe41678b2df360bd90d3dfSHA1: 12f35b1081b17d21815b35feb57ab03d02482116 |
opensearch_init.js |
- | SHA1:820fa07a7328b6cf2b417078e103721d4d8f2e79 |
网络 IOC
| 类型 | 目标 |
|---|---|
| C2 域名 | git-tanstack.com |
| Session 种子节点 | seed1.getsession.org, seed2.getsession.org, seed3.getsession.org |
| Session 文件服务器 | filev2.getsession.org |
| C2 IP 地址 | 83.142.209.194 |
| PyPI Payload URL | git-tanstack.com/tmp/transformers.pyz |
其他特征
| 类别 | 特征描述 |
|---|---|
| 服务名称 | gh-token-monitor |
| macOS 路径 | ~/Library/LaunchAgents/com.user.gh-token-monitor.plist |
| Linux 路径 | ~/.config/systemd/user/gh-token-monitor.service |
| 运行时痕迹 | router_runtime.js , tanstack_runner.js |
| Hook 执行命令 | preinstall: node setup.mjs |
| 恶意 Git 依赖 | github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c |
| 特定运行环境 | Bun Version 1.3.13 |
五、受影响的软件包
PyPI
NPM
| Package | Affected Versions |
|---|---|
| @beproduct/nestjs-auth | 0.1.2, 0.1.3, 0.1.4, 0.1.5, 0.1.6, 0.1.7, 0.1.8, 0.1.9, 0.1.10, 0.1.11, 0.1.12, 0.1.13, 0.1.14, 0.1.15, 0.1.16, 0.1.17, 0.1.18, 0.1.19 |
| @cap-js/db-service | 2.10.1 |
| @cap-js/postgres | 2.2.2 |
| @cap-js/sqlite | 2.2.2 |
| @dirigible-ai/sdk | 0.6.2, 0.6.3 |
| @draftauth/client | 0.2.1, 0.2.2 |
| @draftauth/core | 0.13.1, 0.13.2 |
| @draftlab/auth | 0.24.1, 0.24.2 |
| @draftlab/auth-router | 0.5.1, 0.5.2 |
| @draftlab/db | 0.16.1, 0.16.2 |
| @mesadev/rest | 0.28.3 |
| @mesadev/saguaro | 0.4.22 |
| @mesadev/sdk | 0.28.3 |
| @mistralai/mistralai | 2.2.2, 2.2.3, 2.2.4 |
| @mistralai/mistralai-azure | 1.7.1, 1.7.2, 1.7.3 |
| @mistralai/mistralai-gcp | 1.7.1, 1.7.2, 1.7.3 |
| @ml-toolkit-ts/preprocessing | 1.0.2, 1.0.3 |
| @ml-toolkit-ts/xgboost | 1.0.3, 1.0.4 |
| @opensearch-project/opensearch | 3.5.3, 3.6.2, 3.7.0, 3.8.0 |
| @squawk/airport-data | 0.7.4, 0.7.5, 0.7.6, 0.7.7, 0.7.8 |
| @squawk/airports | 0.6.2, 0.6.3, 0.6.4, 0.6.5, 0.6.6 |
| @squawk/airspace | 0.8.1, 0.8.2, 0.8.3, 0.8.4, 0.8.5 |
| @squawk/airspace-data | 0.5.3, 0.5.4, 0.5.5, 0.5.6, 0.5.7 |
| @squawk/airway-data | 0.5.4, 0.5.5, 0.5.6, 0.5.7, 0.5.8 |
| @squawk/airways | 0.4.2, 0.4.3, 0.4.4, 0.4.5, 0.4.6 |
| @squawk/fix-data | 0.6.4, 0.6.5, 0.6.6, 0.6.7, 0.6.8 |
| @squawk/fixes | 0.3.2, 0.3.3, 0.3.4, 0.3.5, 0.3.6 |
| @squawk/flight-math | 0.5.4, 0.5.5, 0.5.6, 0.5.7, 0.5.8 |
| @squawk/flightplan | 0.5.2, 0.5.3, 0.5.4, 0.5.5, 0.5.6 |
| @squawk/geo | 0.4.4, 0.4.5, 0.4.6, 0.4.7, 0.4.8 |
| @squawk/icao-registry | 0.5.2, 0.5.3, 0.5.4, 0.5.5, 0.5.6 |
| @squawk/icao-registry-data | 0.8.4, 0.8.5, 0.8.6, 0.8.7, 0.8.8 |
| @squawk/mcp | 0.9.1, 0.9.2, 0.9.3, 0.9.4, 0.9.5 |
| @squawk/navaid-data | 0.6.4, 0.6.5, 0.6.6, 0.6.7, 0.6.8 |
| @squawk/navaids | 0.4.2, 0.4.3, 0.4.4, 0.4.5, 0.4.6 |
| @squawk/notams | 0.3.6, 0.3.7, 0.3.8, 0.3.9, 0.3.10 |
| @squawk/procedure-data | 0.7.3, 0.7.4, 0.7.5, 0.7.6, 0.7.7 |
| @squawk/procedures | 0.5.2, 0.5.3, 0.5.4, 0.5.5, 0.5.6 |
| @squawk/types | 0.8.1, 0.8.2, 0.8.3, 0.8.4, 0.8.5 |
| @squawk/units | 0.4.3, 0.4.4, 0.4.5, 0.4.6, 0.4.7 |
| @squawk/weather | 0.5.6, 0.5.7, 0.5.8, 0.5.9, 0.5.10 |
| @supersurkhet/cli | 0.0.2, 0.0.3, 0.0.4, 0.0.5, 0.0.6, 0.0.7 |
| @supersurkhet/sdk | 0.0.2, 0.0.3, 0.0.4, 0.0.5, 0.0.6, 0.0.7 |
| @tallyui/components | 1.0.1, 1.0.2, 1.0.3 |
| @tallyui/connector-medusa | 1.0.1, 1.0.2, 1.0.3 |
| @tallyui/connector-shopify | 1.0.1, 1.0.2, 1.0.3 |
| @tallyui/connector-vendure | 1.0.1, 1.0.2, 1.0.3 |
| @tallyui/connector-woocommerce | 1.0.1, 1.0.2, 1.0.3 |
| @tallyui/core | 0.2.1, 0.2.2, 0.2.3 |
| @tallyui/database | 1.0.1, 1.0.2, 1.0.3 |
| @tallyui/pos | 0.1.1, 0.1.2, 0.1.3 |
| @tallyui/storage-sqlite | 0.2.1, 0.2.2, 0.2.3 |
| @tallyui/theme | 0.2.1, 0.2.2, 0.2.3 |
| @tanstack/arktype-adapter | 1.166.12, 1.166.15 |
| @tanstack/eslint-plugin-router | 1.161.9, 1.161.12 |
| @tanstack/eslint-plugin-start | 0.0.4, 0.0.7 |
| @tanstack/history | 1.161.9, 1.161.12 |
| @tanstack/nitro-v2-vite-plugin | 1.154.12, 1.154.15 |
| @tanstack/react-router | 1.169.5, 1.169.8 |
| @tanstack/react-router-devtools | 1.166.16, 1.166.19 |
| @tanstack/react-router-ssr-query | 1.166.15, 1.166.18 |
| @tanstack/react-start | 1.167.68, 1.167.71 |
| @tanstack/react-start-client | 1.166.51, 1.166.54 |
| @tanstack/react-start-rsc | 0.0.47, 0.0.50 |
| @tanstack/react-start-server | 1.166.55, 1.166.58 |
| @tanstack/router-cli | 1.166.46, 1.166.49 |
| @tanstack/router-core | 1.169.5, 1.169.8 |
| @tanstack/router-devtools | 1.166.16, 1.166.19 |
| @tanstack/router-devtools-core | 1.167.6, 1.167.9 |
| @tanstack/router-generator | 1.166.45, 1.166.48 |
| @tanstack/router-plugin | 1.167.38, 1.167.41 |
| @tanstack/router-ssr-query-core | 1.168.3, 1.168.6 |
| @tanstack/router-utils | 1.161.11, 1.161.14 |
| @tanstack/router-vite-plugin | 1.166.53, 1.166.56 |
| @tanstack/solid-router | 1.169.5, 1.169.8 |
| @tanstack/solid-router-devtools | 1.166.16, 1.166.19 |
| @tanstack/solid-router-ssr-query | 1.166.15, 1.166.18 |
| @tanstack/solid-start | 1.167.65, 1.167.68 |
| @tanstack/solid-start-client | 1.166.50, 1.166.53 |
| @tanstack/solid-start-server | 1.166.54, 1.166.57 |
| @tanstack/start-client-core | 1.168.5, 1.168.8 |
| @tanstack/start-fn-stubs | 1.161.9, 1.161.12 |
| @tanstack/start-plugin-core | 1.169.23, 1.169.26 |
| @tanstack/start-server-core | 1.167.33, 1.167.36 |
| @tanstack/start-static-server-functions | 1.166.44, 1.166.47 |
| @tanstack/start-storage-context | 1.166.38, 1.166.41 |
| @tanstack/valibot-adapter | 1.166.12, 1.166.15 |
| @tanstack/virtual-file-routes | 1.161.10, 1.161.13 |
| @tanstack/vue-router | 1.169.5, 1.169.8 |
| @tanstack/vue-router-devtools | 1.166.16, 1.166.19 |
| @tanstack/vue-router-ssr-query | 1.166.15, 1.166.18 |
| @tanstack/vue-start | 1.167.61, 1.167.64 |
| @tanstack/vue-start-client | 1.166.46, 1.166.49 |
| @tanstack/vue-start-server | 1.166.50, 1.166.53 |
| @tanstack/zod-adapter | 1.166.12, 1.166.15 |
| @taskflow-corp/cli | 0.1.24, 0.1.25, 0.1.26, 0.1.27, 0.1.28, 0.1.29 |
| @tolka/cli | 1.0.2, 1.0.3, 1.0.4, 1.0.5, 1.0.6 |
| @uipath/access-policy-sdk | 0.3.1 |
| @uipath/access-policy-tool | 0.3.1 |
| @uipath/admin-tool | 0.1.1 |
| @uipath/agent-sdk | 1.0.2 |
| @uipath/agent-tool | 1.0.1 |
| @uipath/agent.sdk | 0.0.18 |
| @uipath/aops-policy-tool | 0.3.1 |
| @uipath/ap-chat | 1.5.7 |
| @uipath/api-workflow-tool | 1.0.1 |
| @uipath/apollo-core | 5.9.2 |
| @uipath/apollo-react | 4.24.5 |
| @uipath/apollo-wind | 2.16.2 |
| @uipath/auth | 1.0.1 |
| @uipath/case-tool | 1.0.1 |
| @uipath/cli | 1.0.1 |
| @uipath/codedagent-tool | 1.0.1 |
| @uipath/codedagents-tool | 0.1.12 |
| @uipath/codedapp-tool | 1.0.1 |
| @uipath/common | 1.0.1 |
| @uipath/context-grounding-tool | 0.1.1 |
| @uipath/data-fabric-tool | 1.0.2 |
| @uipath/docsai-tool | 1.0.1 |
| @uipath/filesystem | 1.0.1 |
| @uipath/flow-tool | 1.0.2 |
| @uipath/functions-tool | 1.0.1 |
| @uipath/gov-tool | 0.3.1 |
| @uipath/identity-tool | 0.1.1 |
| @uipath/insights-sdk | 1.0.1 |
| @uipath/insights-tool | 1.0.1 |
| @uipath/integrationservice-sdk | 1.0.2 |
| @uipath/integrationservice-tool | 1.0.2 |
| @uipath/llmgw-tool | 1.0.1 |
| @uipath/maestro-sdk | 1.0.1 |
| @uipath/maestro-tool | 1.0.1 |
| @uipath/orchestrator-tool | 1.0.1 |
| @uipath/packager-tool-apiworkflow | 0.0.19 |
| @uipath/packager-tool-bpmn | 0.0.9 |
| @uipath/packager-tool-case | 0.0.9 |
| @uipath/packager-tool-connector | 0.0.19 |
| @uipath/packager-tool-flow | 0.0.19 |
| @uipath/packager-tool-functions | 0.1.1 |
| @uipath/packager-tool-webapp | 1.0.6 |
| @uipath/packager-tool-workflowcompiler | 0.0.16 |
| @uipath/packager-tool-workflowcompiler-browser | 0.0.34 |
| @uipath/platform-tool | 1.0.1 |
| @uipath/project-packager | 1.1.16 |
| @uipath/resource-tool | 1.0.1 |
| @uipath/resourcecatalog-tool | 0.1.1 |
| @uipath/resources-tool | 0.1.11 |
| @uipath/robot | 1.3.4 |
| @uipath/rpa-legacy-tool | 1.0.1 |
| @uipath/rpa-tool | 0.9.5 |
| @uipath/solution-packager | 0.0.35 |
| @uipath/solution-tool | 1.0.1 |
| @uipath/solutionpackager-sdk | 1.0.11 |
| @uipath/solutionpackager-tool-core | 0.0.34 |
| @uipath/tasks-tool | 1.0.1 |
| @uipath/telemetry | 0.0.7 |
| @uipath/test-manager-tool | 1.0.2 |
| @uipath/tool-workflowcompiler | 0.0.12 |
| @uipath/traces-tool | 1.0.1 |
| @uipath/ui-widgets-multi-file-upload | 1.0.1 |
| @uipath/uipath-python-bridge | 1.0.1 |
| @uipath/vertical-solutions-tool | 1.0.1 |
| @uipath/vss | 0.1.6 |
| @uipath/widget.sdk | 1.2.3 |
| agentwork-cli | 0.1.4, 0.1.5 |
| cmux-agent-mcp | 0.1.3, 0.1.4, 0.1.5, 0.1.6, 0.1.7, 0.1.8 |
| cross-stitch | 1.1.3, 1.1.4, 1.1.5, 1.1.6, 1.1.7 |
| git-branch-selector | 1.3.3, 1.3.4, 1.3.5, 1.3.6, 1.3.7 |
| git-git-git | 1.0.8, 1.0.9, 1.0.10, 1.0.11, 1.0.12 |
| guardrails-ai | 0.10.1 |
| intercom-client | 7.0.4 |
| lightning | 2.6.2, 2.6.3 |
| mbt | 1.2.48 |
| mistralai | 2.4.6 |
| ml-toolkit-ts | 1.0.4, 1.0.5 |
| nextmove-mcp | 0.1.3, 0.1.4, 0.1.5, 0.1.6, 0.1.7 |
| safe-action | 0.8.3, 0.8.4 |
| ts-dna | 3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.0.5 |
| wot-api | 0.8.1, 0.8.2, 0.8.3, 0.8.4 |
结语
这次攻击展示了供应链攻击的新趋势:攻击者不再只盯着包管理器账号,而是将 GitHub Actions、缓存、OIDC、开发者终端、CI/CD Secret 和开源包发布流程串联成完整攻击链。
对于安全团队而言,单纯“升级依赖”已经不够。真正有效的防护需要覆盖从代码提交、CI 执行、凭据签发、包发布到开发者本地环境的全链路。
这次事件也再次提醒我们:CI/CD 是生产系统的一部分,依赖包是执行代码的一部分,开发者机器也是供应链边界的一部分。



