1为什么依赖投毒是 AI 生态的头号安全威胁?
软件依赖投毒是指攻击者通过污染开源包仓库(npm、PyPI 等),将恶意代码注入到开发者日常使用的第三方库中。对于 AI 生态来说,这个威胁被进一步放大——AI 项目的依赖树比传统项目更加庞大复杂,一个典型的大语言模型应用可能涉及数百个依赖包。
2025 到 2026 年,开源生态经历了史上最严重的供应链攻击浪潮:
Shai-Hulud 蠕虫(2025 年 9 月):这是 npm 生态系统的分水岭事件。攻击者发布了一个自我复制的恶意蠕虫,在 12 天内感染了超过 500 个 npm 包。这个蠕虫不再只是传统的"恶作剧"级别威胁,它会自动窃取 npm Token 和 GitHub Personal Access Token(PAT),然后利用这些凭证自动感染和重新发布合法包。这标志着 npm 攻击进入了高后果威胁时代。
TeamPCP 级联攻击(2026 年 3 月):这是史上最复杂的供应链攻击之一。攻击链为:攻陷 Trivy 安全扫描器 → 窃取 CI/CD 凭证 → 发布自复制 npm 蠕虫 → 后门 LiteLLM(AI 模型网关)和 Telnyx SDK。整个过程涉及两个包生态系统(npm 和 PyPI)、三种投递机制(GitHub Actions 标签劫持、Python .pth 文件注入、npm postinstall 钩子),以及至少两个不同的威胁组织。
TanStack 生态沦陷(2026 年 5 月):攻击者利用 pull_request_target Pwn Request、GitHub Actions 缓存投毒和 OIDC Token 从 Runner 进程内存提取的三步链式攻击,在 42 个 @tanstack/* 包中发布了 84 个恶意版本。CVE 评分高达 9.6。受影响的包括 TanStack Query、Table、Router 等 170+ npm 包。
Axios 二次攻击(2026 年 3 月):最受欢迎的 npm HTTP 客户端库(周下载 5000 万+)被植入恶意依赖 plain-crypto-js,通过 postinstall 脚本窃取环境变量和 API 密钥。
这些事件揭示了一个核心事实:你的代码写得再安全,只要依赖链中有一个环节被攻破,整个系统就暴露了。
理解依赖投毒的关键在于认识到:安全边界不再只是你自己的代码,而是你整个依赖树的每一个节点。一个典型的 npm 项目有 86 个直接依赖和超过 1000 个间接依赖,人工审查每一个包是不现实的。
不要以为只有小项目会被攻击。Axios 周下载 5000 万次、Trivy 被数百万开发者使用——攻击者专门挑选使用量最大的包,因为投毒一个流行包的收益远超投毒一百个小包。
2依赖投毒的六大攻击向量深度解析
要构建有效的防御体系,必须理解攻击者的完整入侵路径。以下是 2025-2026 年真实攻击中使用的六大攻击向量。
向量一:npm 生命周期钩子注入
npm 提供了多个脚本执行钩子:preinstall、install、postinstall、prepublish 等。攻击者在恶意包的 package.json 中嵌入 postinstall 脚本,当开发者执行 npm install 时,恶意代码以安装者的权限自动执行。这是最常见的投毒方式。Axios 事件中,恶意依赖 plain-crypto-js 正是通过 postinstall 脚本运行 setup.js 来窃取凭据。
向量二:Python .pth 文件持久化
TeamPCP 在 LiteLLM 的投毒中使用了一个创新技巧:在 Python site-packages 目录中放置一个 .pth 文件。这个文件会在每次 Python 解释器启动时自动执行,即使恶意包被标准包管理器删除,.pth 文件仍然存在,实现了持久化驻留。
向量三:GitHub Actions 标签劫持
攻击者入侵 Trivy 的 GitHub 仓库后,通过伪造的 commit 和标签劫持,让 CI/CD 流水线在构建过程中注入恶意代码。这种攻击的隐蔽性极高——最终发布的包签名、哈希、元数据全部正常,但构建过程被篡改了。
向量四:pull_request_target Pwn Request
TanStack 事件中,攻击者利用 GitHub 的 pull_request_target 事件触发器,在一个看似无害的 PR 中注入了恶意 GitHub Actions 工作流。当仓库维护者审核 PR 时,工作流以仓库的权限运行,从而窃取 OIDC Token 并获取发布权限。
向量五:自复制蠕虫传播
Shai-Hulud 蠕虫引入了蠕虫式自动传播机制:一旦感染一个开发者的机器,它会扫描本地存储的 npm Token,然后用这些 Token 自动登录 npm 并发布恶意版本的该开发者维护的所有包。这意味着一次感染可以级联传播到数百个包。
向量六:OIDC Token 从 Runner 内存提取
在 TanStack 攻击中,攻击者不仅窃取了 GitHub Actions 的环境变量,还进一步从 Runner 进程内存中提取了 OIDC Token。这使得攻击者能够模拟 CI/CD 流水线的身份,以合法的凭证发布恶意包。
| 攻击向量 | 技术原理 | 隐蔽性 | 影响范围 | 典型案例 |
|---|---|---|---|---|
npm 生命周期钩子 | postinstall 自动执行 | 中 | 所有安装者 | Axios + plain-crypto-js |
Python .pth 持久化 | 解释器启动时加载 | 高 | 所有 Python 项目 | TeamPCP → LiteLLM |
GitHub Actions 劫持 | CI/CD 构建过程篡改 | 极高 | CI 构建产物 | Trivy 安全扫描器 |
Pwn Request | pull_request_target 触发 | 高 | 维护者审核时 | TanStack 生态沦陷 |
自复制蠕虫 | 窃取 Token 自动传播 | 极高 | 维护者的全部包 | Shai-Hulud 500+ 包 |
OIDC 内存提取 | Runner 进程内存读取 | 极高 | CI/CD 身份冒用 | TanStack 84 个恶意版本 |
理解攻击向量的最佳方式是查看真实的攻击链。上述六种向量在 2025-2026 年的真实攻击中全部出现过,说明攻击者已经掌握了从「单点投毒」到「级联传播」的完整工具箱。
不要只看 postinstall 这一个入口。TeamPCP 展示了攻击者会使用 .pth 文件、CI/CD 劫持、内存提取等多种手段组合攻击。防御必须覆盖所有向量,不能只防一种。
3第一道防线:锁文件完整性验证
锁文件(Lockfile)是开发者最熟悉但也最容易被忽视的安全防线。package-lock.json(npm)和 requirements.txt / Pipfile.lock(Python)记录了依赖树中每个包的精确版本号和内容哈希。
锁文件的核心安全价值在于:它锁定了你实际安装的每一个依赖的版本和哈希值。即使攻击者在仓库中发布了新的恶意版本,只要你的锁文件没有更新,你安装的仍然是安全的旧版本。
但锁文件也有局限性:
第一,锁文件只保护你,不保护你的间接依赖的新版本。如果你的锁文件中某个间接依赖被标记为可更新,下次 npm update 时可能拉取到恶意版本。
第二,锁文件可以被篡改。如果攻击者已经获得了对你的代码仓库的写入权限,他们可以同时修改锁文件,使其指向恶意版本。
第三,锁文件不验证包的真实性。它只验证哈希值匹配,但如果攻击者在投毒时同时更新了锁文件中的哈希值,校验就会通过。
最佳实践:
启用 lockfileVersion 3:npm v7+ 默认使用 lockfileVersion 3,包含更完整的依赖树信息和 integrity 哈希。确保你的项目使用最新版本。
CI/CD 中强制锁文件校验:在 CI 流水线中运行 npm ci(而不是 npm install),它会严格根据锁文件安装依赖,如果锁文件与 package.json 不一致则直接失败。
定期审查锁文件变更:在 Code Review 中,将锁文件的变更视为与安全相关的变更。任何新增或修改的依赖条目都应该被审查。
使用 npm audit:npm 内置了安全审计功能,可以检测已知漏洞。虽然它不能检测零日投毒,但能帮助你发现已知的安全问题。
在你的 CI 流水线中,用 npm ci 替代 npm install。npm ci 会删除现有的 node_modules 并严格按照锁文件重新安装,如果锁文件与 package.json 不一致会直接报错——这是防止恶意依赖混入的最简单有效的方法。
锁文件不是银弹。Shai-Hulud 蠕虫证明,即使有锁文件,如果攻击者窃取了你维护者的 Token 并修改了锁文件,校验就会通过。锁文件必须与其他防御手段配合使用。
4第二道防线:代码签名与包验证(Sigstore/Cosign)
锁文件解决的是「版本锁定」问题,但不能解决「包是否来自可信发布者」的问题。这正是代码签名技术要解决的。
Sigstore 是一个由 Linux 基金会托管的开源代码签名基础设施,它提供了一套完整的软件供应链安全工具链。对于 npm 和 PyPI 生态,Sigstore 的核心组件包括:
Cosign:用于容器镜像和软件制品的签名和验证工具。开发者可以用 Cosign 对发布的包进行签名,使用者在安装前验证签名是否匹配。
Fulcio:基于 OpenID Connect 的短期证书颁发机构。它为开发者颁发短期有效的签名证书,避免了传统 PKI 的复杂性。
Rekor:一个不可篡改的透明日志,记录所有的签名事件。任何人都可以查询某个包在某个时间点是否被签名过。
npm 从 v9 开始内置了对 Sigstore 的支持。当你在 npm 上发布包时,npm 会自动使用 Sigstore 进行签名。使用者可以通过以下命令验证包的签名:
npm install --verify-signatures
这会检查包的签名是否与 Sigstore 透明日志中的记录匹配。如果签名不匹配或缺失,安装将被拒绝。
对于 PyPI,可以使用 sigstore-python 工具对发布的 wheel 包进行签名。虽然 Python 生态的代码签名支持不如 npm 成熟,但 Sigstore 正在快速发展。
更进阶的方案是在 CI/CD 中集成 SLSA(Supply chain Levels for Software Artifacts) 框架。SLSA 是由 Google 提出的供应链安全等级标准,从 Level 0(无安全保证)到 Level 4(最高级别保证),每一级都对构建过程的来源性、完整性和防篡改性提出了更严格的要求。
如果你的项目发布公共 npm 包,立即启用 Sigstore 签名。在你的 package.json 中设置 publishConfig.provenance: true,npm 发布时会自动生成 Sigstore 证明。这让你的用户能够验证包的来源。
代码签名不是万能的。如果攻击者窃取了开发者的签名密钥(如通过窃取 npm Token),他们可以以合法身份签名恶意包。因此签名必须与密钥轮换、多因素认证等措施配合使用。
5第三道防线:CI/CD 流水线安全加固
2026 年的供应链攻击(特别是 TeamPCP 和 TanStack 事件)揭示了一个关键事实: 攻击者的目标不再只是包仓库本身,而是 CI/CD 流水线。一旦攻陷 CI/CD,攻击者就能以「合法身份」发布恶意包。GitHub Actions 安全最佳实践:
第一,禁用 pull_request_target 或严格限制。pull_request_target 以目标仓库的权限运行工作流,攻击者可以通过构造恶意 PR 来触发工作流并窃取权限。如果必须使用,确保工作流中不包含敏感操作(如发布包)。
第二,使用环境(Environments)保护发布步骤。将 npm publish 等敏感操作放在受保护的环境中,需要人工审批才能执行。这样即使工作流被注入恶意代码,也需要额外的审批步骤。
第三,限制 GITHUB_TOKEN 权限范围。在工作流文件中使用 permissions 字段将 Token 权限限制为最小必要权限。例如,如果工作流只需要读取代码,就不要授予写入或发布权限。
第四,固定 Actions 的版本哈希。不要在工作流中使用 actions/checkout@v3 这样的标签引用,而是使用 actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 这样的完整 SHA 哈希。这防止攻击者劫持标签指向恶意版本。
第五,启用 OIDC 身份验证的细粒度控制。如果使用 OIDC Token 进行云资源认证(如 AWS、GCP),确保 OIDC 的条件限制只允许特定的仓库、分支和工作流触发。
第六,定期轮换 secrets 和 Token 740。npm Token、GitHub PAT、云服务商的 API Key 都应该定期轮换。TeamPCP 攻击之所以影响广泛,部分原因是受害组织的 CI/CD 凭证长期未轮换。 CI/CD 安全架构建议:
在 CI/CD 流水线中引入 软件物料清单(SBOM)生成步骤。SBOM 是软件所有组件的完整清单,包括名称、版本、许可证、来源等信息。当供应链攻击发生时,SBOM 可以帮助你快速判断哪些组件受影响。
使用 CycloneDX或SPDX格式生成 SBOM,并将其作为构建产物存档。工具如 syft、trivy sbom 可以自动从你的项目中生成 SBOM。
立即行动:检查你所有 GitHub 仓库的工作流文件,将 actions/checkout@v3 等标签引用替换为具体的 SHA 哈希。这是一个 5 分钟就能完成的操作,但能防御标签劫持攻击。
CI/CD 安全不仅仅是技术问题。TeamPCP 攻击的第一步是通过钓鱼邮件获取了 Trivy 维护者的 GitHub 账号。即使 CI/CD 配置再安全,维护者账号被盗也会让整个防御体系崩溃。
6第四道防线:运行时检测与应急响应
即使前面所有防线都失效了,你仍然可以在运行时检测异常行为。这是最后一道防线。
npm 包运行时检测:
当你在 CI 或生产环境中安装 npm 依赖时,可以使用以下工具进行实时检测:
Socket.dev:这是一个专门针对 npm 包的实时监控平台。它会对每个包进行静态分析,检测可疑行为,如:网络请求、文件系统访问、子进程执行、环境变量读取等。当发现可疑行为时,Socket 会发出告警。
npm 审计自动化:将 npm audit 集成到 CI 流水线中,每次安装依赖时自动运行审计。可以配置为当发现中危或高危漏洞时阻断构建。
自定义 postinstall 脚本扫描:编写脚本检查 node_modules 中所有包的 package.json,列出所有包含 scripts.postinstall 的包。这能帮助你快速识别哪些依赖会在安装时执行自定义脚本。
应急响应流程:
当发现供应链攻击时,以下是标准化的应急响应流程:
第一步:识别影响范围。使用 SBOM 或 npm ls 命令列出所有受影响的依赖。确定哪些包被投毒、哪些版本受影响、你的项目是否使用了这些版本。
第二步:立即轮换凭据。TeamPCP 攻击的核心目标是窃取 API Key 和 CI/CD 凭据。一旦确认被攻击,立即轮换所有可能泄露的凭据,包括 npm Token、GitHub PAT、云服务商 API Key、数据库密码等。不要等确认「是否真的被窃取」——假设已经被窃取。
第三步:清除恶意包。删除 node_modules 目录,清理 npm 缓存(npm cache clean --force),然后使用已知安全的版本重新安装。如果使用 Python,检查 site-packages 目录中是否有可疑的 .pth 文件。
第四步:修复漏洞根源。分析攻击是如何发生的——是锁文件被篡改?是 Token 泄露?还是 CI/CD 配置不当?修复根源后,重新评估整个防御体系。
第五步:上报和共享信息。将事件上报到 npm 安全团队、GitHub 安全团队或相关的 CVE 数据库。供应链安全是社区问题,共享信息能帮助其他开发者避免同样的攻击。
在你的项目中添加一个安全检查脚本,在每次 npm install 后自动运行,检查 node_modules 中所有包含 postinstall/preinstall 脚本的包。这能帮助你快速识别哪些依赖可能在安装时执行自定义代码。
应急响应中最常见的错误是「只修复表面症状」。如果你只删除了恶意包但没有轮换凭据,攻击者仍然可以用窃取的 Token 继续攻击。凭据轮换是应急响应的第一步,不是最后一步。
7第五道防线:零信任供应链架构
将前面所有防线整合起来,就构成了一个零信任软件供应链(Zero Trust Software Supply Chain)架构。零信任的核心理念是:不信任任何外部代码,无论它来自哪里。
零信任供应链的五个核心原则:
原则一:最小信任边界。每个外部依赖都应该被视为不受信任的,直到它被验证。验证包括:签名验证、哈希校验、行为分析、社区声誉检查。
原则二:纵深防御。不依赖单一防线。锁文件、代码签名、CI/CD 加固、运行时检测、SBOM——每一层都能独立提供一定的安全保障,多层叠加形成纵深防御。
原则三:持续监控。供应链安全不是一次性的。你需要持续监控依赖的更新、新的漏洞公告、社区的投毒报告、以及你自己的 SBOM 变更。
原则四:快速响应。当发现供应链攻击时,响应速度决定了损失大小。自动化凭据轮换、自动回滚到安全版本、自动通知相关人员——这些都应该在应急预案中提前规划。
原则五:社区协作。供应链安全是集体行动问题。共享威胁情报、参与安全社区、报告漏洞、贡献安全工具——每个参与者都从社区安全中受益。
AI 项目的特殊考虑:
AI 项目除了传统的代码依赖,还有模型依赖和数据依赖:
模型依赖:从 Hugging Face 等仓库下载的预训练模型。模型的序列化文件(如 pickle)可能包含恶意代码。建议使用 safetensors 格式(不包含可执行代码)而非 pickle。
数据依赖:训练数据集可能被投毒。攻击者向数据集中注入带有特定触发器的样本,使得模型在推理时产生异常行为。使用数据完整性校验和数据集签名来防范。
Prompt 模板依赖:开源的 Agent 框架和 Prompt 模板库可能被植入恶意指令。在使用开源模板前,审查其内容是否包含可疑的系统提示。
零信任不是一步到位的。从最容易实施的开始:启用 lockfile、固定 Actions 哈希、添加 npm audit 到 CI。然后逐步升级到代码签名、SBOM 生成、行为监控。每升级一次,你的供应链安全性就提升一个等级。
零信任架构的最大挑战不是技术,而是文化和流程。它要求开发者改变「npm install 就可以」的习惯,增加验证步骤、审批流程、监控告警。这些会增加开发摩擦,但相对于供应链攻击的代价,这是必要的投资。
8实战:构建你的 AI 项目供应链安全体系
下面是一个具体的实施清单,帮助你在 AI 项目中逐步建立供应链安全防御。
立即执行(今天就能做):
确保 package-lock.json 存在并提交到 Git。在 CI 中使用 npm ci 而非 npm install。运行 npm audit 修复所有已知漏洞。检查 GitHub Actions 工作流中是否有使用标签引用的 Actions,替换为 SHA 哈希。
一周内完成:
在你的项目中添加锁文件变更审查的 Code Review 规则。启用 GitHub 的 Dependabot 安全更新。安装 Socket.dev CLI 工具对你的依赖进行行为分析。编写一个脚本定期检查 node_modules 中的 postinstall 脚本。
一个月内完成:
为你的公共 npm 包启用 Sigstore 签名(publishConfig.provenance: true)。在 CI 中生成 SBOM 并存档。建立凭据轮换计划(至少每季度一次)。制定供应链攻击应急响应预案。
持续改进:
参与开源安全社区,关注 npm 安全公告和 PyPI 安全公告。定期审查你的依赖列表,移除不再需要的包(减少攻击面)。将 SLSA 框架作为长期目标,逐步提升供应链安全等级。考虑使用内部私有仓库代理(如 Verdaccio、Nexus),在代理层统一进行包的验证和过滤。
AI 项目的额外检查清单:
检查所有 Hugging Face 模型是否使用 safetensors 格式。审查 Agent 框架的 Prompt 模板是否包含可疑指令。对训练数据集进行完整性校验。监控 AI 依赖(torch、tensorflow、langchain 等)的安全公告。
将供应链安全检查纳入你的 Code Review 流程。每次有依赖更新时,不仅检查功能变更,还要检查:这个新版本是否有安全公告?锁文件变更是否合理?是否有新增的 postinstall 脚本?
不要陷入「安全完美主义」陷阱。你不可能防御所有攻击。目标是建立一个成本效益最优的防御体系——用合理的工作量降低最大的风险。从立即执行的 5 项开始,逐步推进。
9扩展阅读与参考资源
以下是进一步学习软件供应链安全的推荐资源。
官方文档:
Sigstore 官方文档:sigstore.dev — 学习代码签名的完整工具链。SLSA 框架文档:slsa.dev — 了解供应链安全等级标准。npm 安全指南:docs.npmjs.com — npm 内置的安全功能和最佳实践。GitHub Actions 安全指南:docs.github.com — CI/CD 安全配置。
安全工具:
Socket.dev — npm 包行为实时监控平台。Trivy — 容器和依赖漏洞扫描器(Aqua Security 出品)。syft — SBOM 生成工具。cosign — Sigstore 代码签名工具。npm audit — npm 内置安全审计。
经典案例分析:
Shai-Hulud 蠕虫分析报告(Palo Alto Networks Unit 42)— 理解自复制 npm 蠕虫的工作原理。TeamPCP 攻击技术分析(Cloud Security Alliance)— 学习级联供应链攻击的完整链路。TanStack 事件事后分析(TanStack 官方)— 了解 Pwn Request + 缓存投毒 + OIDC 提取的三步攻击。
社区与资讯:
OpenSSF(Open Source Security Foundation)— 开源安全基金会。npm Security Advisories — npm 安全公告。PyPI Security — Python 包安全资讯。StepSecurity Blog — 开发者安全工具和分析报告。
建议从 Shai-Hulud 蠕虫的技术报告开始阅读——它是最能帮助你理解供应链攻击「为什么危险」和「如何传播」的案例分析。理解攻击,才能构建有效的防御。
供应链安全领域发展极快。今天的安全最佳实践可能明天就被新的攻击手段打破。保持关注安全社区的最新动态,定期更新你的防御策略。