10 月 21 日,LiteLLM 官方确认了一次供应链事件:发布流水线被入侵,恶意版本 1.107.0 被推到 PyPI 和容器镜像仓库。PyPI 上的恶意包存活了大约两个小时,Docker Hub 上的 latest、main-latest、main-stable 等标签也一度指向被污染镜像。

LiteLLM 是很多团队用来统一接入 OpenAI、Anthropic、Gemini 等模型的网关。它通常握着大量 provider API key、数据库密码和云访问凭据。也正因如此,这次事件的重点不是“一个 Python 包被投毒”,而是“攻击者拿到网关之后能横着走多远”。

恶意版本做了什么

官方报告写得很直接:1.107.0 里包含编译过的恶意代码,功能是窃取凭据并加载远程代码执行工具。PyPI 上的七个 wheel 从 11:39:51 到 13:23 UTC 可下载;Docker Hub 的 v1.107.0、latest、main-latest、main-stable 从 11:46:56 到 14:20 UTC 指向被污染的镜像索引。GHCR 上的相关标签也在短时间内受到影响。

官方特别说明:GitHub 上 1.107.0 的源码 tag 本身没有恶意文件,恶意产物是从攻击者控制的仓库构建出来的;之前的版本是安全的,企业私有发布通道也没有受影响。

攻击链:没有正面硬刚,而是拿到了钥匙

这次最值得读的部分是“怎么发生的”。LiteLLM 在 3 月经历过一次类似事件后,已经加强了发布流程:私有仓库、受控 workflow、Trusted GitHub Publishers、不可变 Docker tag、镜像签名。按正常设计,发布应该需要多个环节配合。

但攻击者拿到了三组开发者 GitHub CLI 凭据。其中一组能运行 GitHub workflow,另一组属于 GitHub 组织 owner。攻击者先用 owner 权限关掉分支保护,强推一个改动,把发布 workflow 指向一个看起来很像的仓库:Berri-AI/litellm,而不是 BerriAI/litellm。然后,受污染的 workflow 完成了签名和发布。

注意这里的细节:攻击者没有破解 GitHub,也没有伪造签名算法;他们只是让“合法的发布流程”去构建了一个不该构建的仓库。供应链攻击最难防的地方就在这里——每一步看起来都合规,合起来却不是你想要的结果。

为什么发布链这么难守

发布链建立在一串信任假设上:组织 owner 的账号可信,workflow 文件没有被改,tag 不会被重写,签名身份对应正确的仓库,包索引里的产物就是 CI 构建出来的那一份。

这些假设单看都合理,连在一起却有一个共同弱点:它们都依赖“凭据没有落到错的人手里”。只要攻击者拿到一个足够高权限的 token,他不需要漏洞利用,也不需要破解密码,就能让系统自己完成剩下的步骤。签名和不可变 tag 仍然有效,只是签的是一个被替换过的构建源。

所以这类事件里,“有没有签名”不是关键问题,“签名时构建的是什么”才是。SLSA、Sigstore、不可变 tag 都是有用的,但它们证明的是构建过程和来源,不证明构建发起者的意图。

凭据是怎么漏出去的

官方调查提到一条很具体的路径:在某些情况下,Claude Code 会把用户的 GitHub CLI OAuth token 放进响应体;如果响应体被记录,token 就可能出现在 LiteLLM 的日志里。攻击者先访问了 LiteLLM 一个暴露在公网、名字很不起眼的内部部署,从日志里拿到了这些 token,再用它们进入发布流程。

官方还没有完全确认攻击者最初是怎么进入那个内部部署的,目前怀疑是利用了一个刚修补过的漏洞。这个链条里没有“超级黑客”式的单点突破,更多的是几个普通问题叠在一起:内部服务暴露、日志记录敏感数据、AI 工具输出被当成普通文本、开发者凭据权限过大、组织 owner 的 token 没有单独隔离。

如果你用过 LiteLLM

先不要急着重装。官方给了一个很重要的提醒:不要在受影响环境里先跑 pip。因为恶意代码会通过 cook.pth 和 sitecustomize.py 在 Python 启动时再次执行,连 pip 启动也会触发。

比较稳妥的检查和处理顺序是:

  • 确认是否装过 1.107.0,或者运行过受影响的 Docker 镜像。可以从镜像层、包清单和安装记录核对;不要在已经被污染的环境里直接启动 Python。
  • 在文件系统里找 litellm-1.107.0.dist-info、cook.pth、sitecustomize.py,以及对应的 pycache。找到其中一个就说明暴露过;找不到也不代表干净。
  • 结束那个脱离终端的 python 守护进程,删掉相关文件,重新建虚拟环境或镜像,固定到 1.106.2 或更早的明确版本。
  • 轮换这台机器能触达的一切:实例 IAM role、LiteLLM 数据库里的 provider key、master key 和 virtual key、DATABASE_URL 密码,以及任何出现在进程环境里的 secret。
  • 查 CloudTrail 里安装时间附近的 GetCallerIdentity,以及角色凭据是否从你的基础设施之外被使用过。

如果这台机器能出网,官方建议按“已经外传”处理。容器和临时目录会清理痕迹,没找到 marker 文件并不能证明没泄露。

给 AI 基础设施提个醒

LiteLLM 这类网关是 AI 应用里的“钥匙串”:它知道所有模型的 key,知道数据库密码,通常还和云账号连着。它的发布链一旦被攻破,影响不会停在一个包上,而是会顺着 key 扩散到模型账单、数据、甚至生产环境。

对做发布系统的团队来说,这次事件至少留下四个落点:

  • 把长期 token 换成短期 OIDC 凭据,尤其是能触发 workflow、能改组织设置的权限。
  • 给发布环境加人工审批和环境级 secret 隔离,不要让一个 owner 账号能单独完成“改分支保护 + 改 workflow + 发布”的全过程。
  • 把日志当成敏感数据,默认脱敏 token、Cookie 和 Authorization 头;构建机默认禁止随意出网。
  • 依赖升级不要追求“自动跟最新”,生产网关应该锁版本、校验哈希,并保留可回滚的上一版。

这次事件还有一个很现实的教训:AI 编程工具已经进入了发布流程,但它输出的内容仍然可能包含凭据。日志系统、聊天记录、CI 输出,只要有一个地方把响应体原样保存下来,token 就会多出一条泄露路径。工具本身没有恶意,问题在于我们还没把“AI 输出”当成需要脱敏的数据。

参考:LiteLLM 官方事件报告《Contained: Oct 21, 2026 LiteLLM release v1.107.0 Compromised》,2026 年 10 月 21 日。