判断先给:litellm 1.107.0 的签名没过期、没有被伪造——被偷走的是「有资格签名的人」。 这意味着过去十年依赖管理的主线「锁版本」,从今天起要往前挪一步:锁住版本还不够,得验发布管道。

10 月 21 日,安全公司 SafeDep 披露:挂在 Python 官方包仓库上的 litellm 1.107.0,七个平台的 wheel 全部带毒。同日 LiteLLM 官方事故报告确认了全过程。整个暴露窗口约两个小时,但这两个小时足够把一件旧事翻过去:在签名有效的包面前,版本锁是最后一道防线,而这道防线今天被证明挡的是错的东西。

攻击链复盘:密码学一步没碰,碰的是人

先把时间线摊开(均为 UTC 10-21)。这条链上没有一行伪造签名的代码,全部走的是合法通道:

时间动作
10:08攻击者用组织 owner 权限绕过分支保护
10:46staging 构建,发布 workflow 被指向仿名仓库 Berri-AI/litellm
11:39–40七个恶意 wheel 上 PyPI
11:46恶意 Docker 镜像发布(v1.107.0/latest/main-latest/main-stable)
13:23PyPI 下架
13:40攻击者 GitHub 账号 404
14:20Docker 标签移除、重指

入口凭据的来源值得每个团队对照自查:三名开发者的 GitHub CLI 登录凭据,来自一台暴露在公网的内部 LiteLLM 部署的日志。也就是说,不需要钓鱼、不需要 0day,一台忘了关的内部服务的访问日志,就把「有资格签名的人」的交椅送出去了。

绕过分支保护之后,攻击者把私有发布 workflow 指向仿名仓库 Berri-AI/litellm,再用第二名开发者的凭据走过审批门。剩下的事不用人参与了:官方流水线自己完成了构建、签名、发布。 签名是真的,发布记录是真的,PyPI 上的版本号是真的——只有内容不是你以为的那个内容。

这种「每一步都合法、合起来是犯罪」的结构,比伪造签名更难防。伪造签名可以靠校验签名抓,合法签名配恶意内容,只能靠比对「发布出来的东西」和「源码仓库里的东西」是不是一回事。

三个文件与一张清单:载荷怎么落地,偷的是什么

毒包的狡猾在于触发方式。它以三个文件落入 site-packages:

  • cook.pth,内容仅一行 import cook;
  • cook.py,22KB,主体是 exec(zlib.decompress(b64decode(...)));
  • sitecustomize.py,25KB,loader、后门加内嵌窃取器。

Python 进程启动时会自动加载 .pth 与 sitecustomize,这意味着任何 Python 进程都会触发——包括从来不 import litellm 的进程、pip、IDE 的语言服务器、CI 作业。装过这个包的机器,跑什么都是敞开的。

外传通道也经过了设计:数据经 AES-256-CBC 加密、RSA-OAEP 包密钥,POST 到仿冒域 www.litellm[.]dev/healthcheck(注意,不是官方 litellm.ai);sitecustomize.py 另起守护进程,每小时从一个公开 GitHub 仓库的文件里读新的服务器地址、下载载荷并执行——想换载荷,攻击者只需要改一个文件。域名白名单在这里是失效的,载荷托管在一个几乎所有环境都放行的代码托管域名上。

偷的清单分成十五类,基本就是一张现代开发机的全景图:SSH 私钥、云凭据(AWS/Azure/gcloud/kube/docker/gh/rclone)、CI/CD secrets(GitLab runner / Jenkins / Vault / Terraform)、浏览器密码与历史、shell 历史、$HOME 下的 .pem/.key/.pfx、K8s service account token、GPG/TLS 私钥,以及 LiteLLM proxy 数据库——读 DATABASE_URL,把 LiteLLM_CredentialsTable 里各家 LLM provider 的 key 一锅端。

其中最该被划重点的是单独一类:「AI 开发工具」,点名了十七种编码 agent 的凭据文件——Claude Code、Codex、Cursor、Gemini CLI、Windsurf、Copilot、VS Code 的 mcp.json、Continue、Aider、Goose、opencode、Qwen、Crush、Kiro、Zed、HF token。编码 agent 的钥匙和 SSH 私钥躺在同一张清单上,而过去我们只按「本地配置文件」的规格在管它们。这些钥匙的权限比传统凭据宽得多:能发合并请求、能读私有仓库、能调付费接口。权限早就升级了,管理口径还停在十年前。

顺便一个选型视角:LiteLLM 是大量团队自建 LLM 网关的默认选择,其 proxy 数据库本身就是「各家模型供应商 key 的集中地」——攻破一个网关安装包,等于摸到下游所有供应商的钥匙。更巧的是,10-10 的 Pwn2Own 已经攻破过 LiteLLM 网关(SSRF + 代码注入),10-21 是其 PyPI 供应链被投毒——同一个组件两周内以两种完全不同的面被攻破,选型时不能只看 star 数与更新频率。

从锁版本到验管道:真正该加的那道门

毒包下架后,SafeDep 公布了完整 IOC(网络/主机/镜像 digest/文件哈希),处置动作是明确的:查版本(干净的是 1.106.2 与两个 rc),在 site-packages 搜 cook.pth/cook.py/sitecustomize.py 三个文件名,Docker 镜像对一遍摘要值。这些半小时能查完,是止血。

但这次事件真正的增量在另一个地方。看上游 git 源码:源码包里没有这些文件,sdist 里也没有 cook/ 目录——唯一可见的差异,是 sdist 的 pyproject.toml 的 maturin 段多了一行 data = "cook"。一次制品与源码的结构比对就能发现的事,过去没有任何工具替你做。

这不是孤例。同一周前后脚还有两个样本:Zod 在 npm 发布时靠发布扫描拦下了唯一一个恶意版本(10-13);GhostAction 用伪装成 “Security Audit” 的 workflow 提交混进仓库(10-13)。三个样本指向同一处:发布管道的信任——签名、审批门、开发者凭据——正在成为供应链投毒的主攻方向。

由此产生一个待验证的判断:半年内会出现「制品与源码结构 diff」的独立验证品类,PyPI / npm 会把这类 diff 变成发布前自动门禁,对标 SBOM 但做到文件级与构建参数级。检验点有三个:包管理器平台是否上线结构 diff 门禁;是否出现第三方「制品溯源」服务;企业是否把 AI 工具凭据从开发机本地文件迁进短时效、按用途分发的凭据服务。三条中两条成立,依赖管理的工作重心就该正式从「锁版本」前移到「验管道」。

落到工程上,是三件事,按成本从低到高:

  1. 盘点:版本、三个特征文件名、镜像摘要值,对完 IOC 即止血。
  2. 隔离:把编码 agent 的凭据当生产凭据管——挪出明文散落,短时效,按用途分开授权,进专门的凭据管理;顺手把命令行工具的登录凭据权限收窄,这次被偷的正是它。
  3. 加一道比对:锁版本管「装什么」,这道新门管「发出来的是不是源码里那个」——发布后自动拿已发布的包与源码仓库做文件清单比对。一次结构比对就能发现的事,不值得赌第二次。

同一周的其他信号

  • 10-22 讯飞星火 X2.6 发布:293B 总参数、1M 上下文、全国产算力训练,限时输入价每百万 0.9 元;4B 端侧模型开源且推理免费。「自主可控」第一次连同定价一起摆上桌。
  • 10-21 Claude Duet 进研究预览:常驻型 agent 第一次成为产品形态,官方自己写明代价——后台检查计入配额、几小时才检查一轮、会误判紧急程度。
  • 10-20 OpenAI 把提示缓存窗口从 30 分钟拉到 12 小时:写入价变 2.5 倍、读取价不变。「计费资格的最短生命周期」这个措辞,做容量规划的人都该抄走。
  • 10-21 PHP2Shell:AI 在攻击者之前找到一个藏了 14 年的 PHP 漏洞,但归功争议提醒我们:听到「AI 发现」,先问清是参与、主导,还是独立完成。

数据说明

攻击链、时间线、文件清单、窃取模块分类、外传机制与 IOC 来自 SafeDep 10-21 披露与 LiteLLM 官方事故报告(同日发布);干净版本号(1.106.2、1.107.0rc1/rc2)与未受影响的配套包(litellm-proxy-extras 0.4.110、litellm-enterprise 0.1.78)来自官方报告;「sdist 与 git 源码的唯一差异为 pyproject.toml 多一行 data = "cook"」为 SafeDep 比对结论。Pwn2Own 10-10 攻破 LiteLLM 网关为该赛事官方记录。Zod(10-13)与 GhostAction(10-13)两条来自各自项目的公开安全公告。「发布管道信任成为主攻方向」及三个检验点为本文提出的待验证假设,非既成事实。星火 X2.6、Claude Duet、OpenAI 缓存、PHP2Shell 四条详见本日报另两节。

相关阅读:上一篇《DeepSeek 开源 DeepInfer,成本写在「不含」里》讨论的是「开源交付物缺了哪一块」;这一篇讨论的是「交付物被换掉了怎么办」——两件事的共同点是:账单上都不写,只有你自己去对。