昨天,LiteLLM——统一各家 LLM API 调用的 Python 库——被投毒了。GitHub 4 万星。月下载量 9500 万次。2000+ 个依赖包,包括 DSPy、MLflow 和 Open Interpreter。
版本 1.82.7 和 1.82.8 包含凭证收割器。一行 pip install 就足够了。
这不是一个包被黑的故事。这是关于整个 Python 包生态的信任模型为何在 AI Agent 基础设施面前从根本上失效——以及真正的防御长什么样。
发生了什么
攻击是一个四步级联供应链攻击:
第 1 步(3 月 19 日): Trivy v0.69.4 被投毒。Trivy 是 Aqua Security 的开源漏洞扫描工具——一个保护你的工具。攻击组织 TeamPCP 在其中植入了凭证窃取器。
第 2 步(3 月 23 日): LiteLLM 的 CI 流水线运行被投毒的 Trivy 来扫描自己的代码。在这次”安全扫描”中,Trivy 静默窃取了维护者的 PYPI_PUBLISH_PASSWORD。
第 3 步(3 月 24 日上午): TeamPCP 用窃取的凭证将 litellm 1.82.7 发布到 PyPI。恶意代码隐藏在 litellm/proxy/proxy_server.py 中,当开发者导入该模块时执行。
第 4 步(3 月 24 日数小时后): TeamPCP 发布 litellm 1.82.8——升级版。这个版本添加了 litellm_init.pth 文件,在每次 Python 启动时自动执行。无需 import。无需函数调用。只要 Python 运行,恶意代码就运行。
安全工具变成了攻击入口。
.pth 攻击向量
这是技术上最有趣的部分。Python 的 .pth 文件是路径配置文件,由 site 模块在解释器启动时处理。如果某行以 import 开头,会被直接 exec() 执行——这是 Python 的文档行为,不是漏洞。
攻击者利用了这一机制:
import os, subprocess, sys; subprocess.Popen([sys.executable, "-c","import base64; exec(base64.b64decode('...'))"],stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)这意味着:
pip install anything→ Python 启动 →.pth运行 → 凭证被收割python -c "print(1)"→ 同上- IDE 启动语言服务器 → 同上
pytest跑测试 → 同上
没有任何用户可感知的动作。完全静默。载荷使用三层 base64 嵌套规避静态分析。
三层载荷架构
第 1 层——凭证收割器: 系统性搜刮 /home、/opt、/srv、/var/www、/app、/data、/tmp 下的 SSH 密钥、AWS/GCP/Azure 凭证、K8s Secrets、加密货币钱包、.env 文件、数据库密码、Shell 历史。用随机 32 字节 AES 会话密钥加密,再用 RSA-OAEP 包装 AES 密钥(4096 位公钥内嵌载荷中)。外传至 models.litellm.cloud——一个高度逼真的仿冒域名。
第 2 层——K8s 横向移动: 检测到 K8s 服务账号令牌后,在每个集群节点部署特权 Pod,安装持久化 dropper。
第 3 层——持久化后门: 在 ~/.config/sysmon/sysmon.py 注册为 systemd 用户服务。每 50 分钟轮询 checkmarx.zone 获取新载荷。下载到 /tmp/pglog 执行。启动有 5 分钟延迟规避沙箱分析。卸载 litellm 后仍然存活。
现有防御为何全部失效
pip install --require-hashes? 无效。恶意文件正常列入 wheel 的 RECORD,哈希完全匹配。因为包是用窃取的合法 PyPI 凭证发布的,一切在技术上都是”真实的”。
包签名? 同样的问题。凭证是真的。签名是有效的。
安全扫描? 这次攻击起始于攻陷安全扫描工具。Trivy 本应保护 LiteLLM,结果它变成了入口。
社区报告? Issue 提交后,攻击者用 73 个被盗账号在 102 秒内发送 88 条垃圾评论淹没讨论,然后用被盗的维护者账号关闭 issue。
攻击被发现的唯一原因:攻击者自己的代码有 bug。.pth 文件生成的子进程在初始化时再次触发同一个 .pth,导致指数级递归——fork bomb 撑爆了一位 Cursor IDE 用户的内存。Karpathy 评论:如果攻击者代码写得再好一点,这件事可能几周都不会被发现。
真正的问题:隐式执行
根本问题不是 LiteLLM。而是 Python 包生态有多条路径让代码在没有显式调用的情况下执行:
| 执行钩子 | 何时运行 | 用户感知度 |
|---|---|---|
setup.py | pip install 时 | 低 |
.pth 文件 | 每次 Python 启动 | 几乎为零 |
__init__.py | 首次 import 时 | 低 |
| Entry point 脚本 | CLI 调用时 | 中 |
AI Agent 基础设施通常组合数十个包,每个都有自己的依赖树。每个依赖都是一个信任决策,而大多数开发者是无意识做出的。LiteLLM 攻击表明,即使你从未直接安装的包(传递性依赖)也能静默收割你的凭证。
沙箱化实际上阻止了什么
在 Rotifer Protocol 中,我们将 Agent 能力(称为 Gene)编译为 WebAssembly,在 wasmtime 沙箱中执行。这不是理论上的防御——它是一个根本不同的执行模型,从结构上消除了 LiteLLM 被攻击所利用的攻击面。
无文件系统访问。 沙箱中的 Gene 无法读取 ~/.ssh/、~/.aws/credentials 或任何 .env 文件。WASM 沙箱没有文件系统 API,除非被显式授予。
无子进程生成。 subprocess.Popen、child_process.exec、os.system——在 WASM 执行环境中都不存在。.pth 攻击链(Popen → base64 → exec)在结构上不可能发生。
无隐式执行钩子。 WASM 中没有 .pth 的等价物。代码只在运行时显式调用时运行,而不是在解释器启动时。
声明式网络边界。 需要网络访问的 Gene 必须在其 Phenotype 中声明 allowedDomains——一份机器可读的能力清单。未声明的 POST 到 models.litellm.cloud 会在请求离开沙箱之前被拒绝。
二进制级执行。 这些限制不是可被绕过的策略规则——它们由 wasmtime 运行时在系统调用层面执行。编译为 WASM 的 Gene 在物理上无法发出读取文件或生成进程所需的系统调用,无论其源代码试图做什么。
在 v0.8 中,我们运行了 22 个专门设计来突破沙箱边界的对抗测试:内存越界攻击、无限循环、递归栈溢出、文件系统访问尝试、未授权网络调用。在修补测试中发现的两个关键漏洞后,零次逃逸成功。
V(g):精准检测这些模式
我们在 v0.7.9 发布的 V(g) 安全扫描器 检测的正是 LiteLLM 攻击中使用的模式:
| V(g) 检测规则 | LiteLLM 攻击模式 |
|---|---|
动态代码执行(eval、exec) | exec(base64.b64decode(...)) |
子进程生成(child_process、subprocess) | subprocess.Popen(...) |
| 混淆载荷 | 三层 base64 编码 |
| 未授权网络调用 | POST 到 models.litellm.cloud |
V(g) 对源码进行静态扫描——无 ML,无启发式,只对重要的东西做模式匹配。评分从 A 到 D,并生成 shields.io 兼容徽章,任何开发者都可以嵌入 README。
当我们用 V(g) 扫描 ClawHub 最热门 50 个 Skill 时,100% 触发了至少一项发现。零个 A 级结果。14% 包含动态代码执行——与 LiteLLM 载荷完全相同的技术。
令人不安的结论
LiteLLM 事件不是个例。它是一个生态系统的逻辑后果,在这个生态中:
- 信任是传递性的且不可见的。 你信任 litellm,litellm 信任 Trivy,而 Trivy 被攻陷了。你从未对 Trivy 做过任何决策。
- 执行是隐式的。 代码运行不是因为你调用了它,而是因为解释器启动了。
- 认证 ≠ 授权。 有效凭证不等于有效意图。哈希验证和包签名是认证措施——它们告诉你谁发布了包,而不是这个包做了什么。
防御不在于更好地扫描 Python 包(虽然这有帮助)。防御在于一个执行模型,其中不受信任的代码在物理上无法访问它想窃取的资源。
编译为 WASM。在沙箱中运行。显式声明网络边界。让默认值是”无访问”而不是”完全访问”。
这就是我们正在构建的。
如果你受影响了怎么办
如果你安装了 litellm 1.82.7 或 1.82.8:
- 假设所有凭证已泄露。 轮换一切:SSH 密钥、云凭证、API Token、数据库密码。
- 检查持久化后门:
ls ~/.config/sysmon/和ls /tmp/pglog。如果存在,你的系统有后门。 - 检查 .pth 文件: 在 Python site-packages 中搜索
litellm_init.pth,找到就删除。 - 固定安全版本:
pip install litellm==1.82.6 - 运行社区自查脚本: gist.github.com/sorrycc/30a765…
安全版本:litellm <= 1.82.6。版本 1.82.7 和 1.82.8 已被投毒,已从 PyPI 移除。