1. 供应链投毒之后,pip install litellm 还能不能继续用
LiteLLM 是一个把几十家模型厂商 API 统一成一套 OpenAI 风格接口的 Python 库,你写litellm.completion(model="...")就能在 GPT、Claude、Gemini 之间切换,不用为每家单独写 SDK。它常被当成 AI 应用里的“网关层”,Agent 框架、评测脚本、内部工具都会依赖它。也正因为下载量大、又天然要接触各种 API Key,它成了供应链投毒的高价值目标。
这次事件的核心不是 LiteLLM 功能有漏洞,而是 PyPI 上出现了两个非正常发布的版本,其中.pth文件会在 Python 解释器启动时自动执行,不需要你import litellm也会触发。这意味着只要恶意版本进过你的环境,任何 Python 脚本启动都可能被牵连。所以“重建安全配置”要解决两件事:一是把依赖版本钉死并做哈希校验,二是把散落在各处的厂商 Key 收拢成一条可控通道,降低单点泄露后的爆炸半径。
我试过在本地把 LiteLLM 的 Key 管理从“每个项目一份环境变量”改成统一网关,最直接的收益不是省事,而是出事时只需要轮换一个地方。下面按“先锁依赖、再统一通道、最后验证”的顺序走一遍,你可以直接照着改。
2. 前置准备:TaoToken 统一 Key 通道与 LiteLLM 的角色分工
在讲配置之前先把分工说清楚,不然后面 config.yaml 容易写乱。
LiteLLM 在你的项目里扮演两个角色:作为 Python 库被业务代码调用,或者作为 proxy 服务对外暴露一个/v1/chat/completions兼容端点。无论哪种,它都需要知道“请求发给谁、用什么 Key”。传统做法是把OPENAI_API_KEY、ANTHROPIC_API_KEY等直接写进环境变量,Key 分散、轮换麻烦、日志里还容易漏。
TaoToken 在这里的作用是提供一条统一的 API 通道:你只需要持有 TaoToken 的 Key,由它去对接后端模型,业务侧不再直接持有各厂商原始 Key。这样 LiteLLM 的配置里只出现一个 base_url 和一个 Key,供应链投毒即使偷到了环境变量,拿到的也是可快速吊销的通道凭证,而不是你所有厂商的长期密钥。
需要提前准备的东西:
- 一个 TaoToken 账号,用来生成 API Key。入口在控制台的 API Keys 页面:https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
- 确认你要用的模型名,TaoToken 的模型对话页可以直接试:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite
- 接入文档放在手边,配置字段对不上时查它:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
- 如果你打算长期跑编码类 Agent,可以了解 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
注意:不要把厂商原始 Key 和 TaoToken Key 混写在同一个
.env里。统一通道的意义就是让业务侧只认一个 Key,混写等于白做。
3. 可复制配置:锁定 litellm 版本 + config.yaml 骨架
3.1 先锁版本,再谈安装
事件之后第一件事不是升级,而是把版本钉死。LiteLLM 官方建议回退到安全版本,我们用==精确锁定,并配合哈希校验,避免 pip 拉到被替换过的包。
# 1. 查看当前环境里装了什么版本 pip show litellm 2>/dev/null | grep -i version # 2. 卸载可能存在的异常版本 pip uninstall -y litellm # 3. 安装锁定版本(示例用 1.82.6,以官方公告的安全版本为准) pip install "litellm==1.82.6"如果你要更严格,用 requirements 文件加哈希。先生成带哈希的锁定文件:
pip install pip-tools # 在 requirements.in 里写一行:litellm==1.82.6 pip-compile --generate-hashes requirements.in -o requirements.txt生成的requirements.txt会长这样,每行带--hash:
litellm==1.82.6 \ --hash=sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \ --hash=sha256:yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy安装时强制校验:
pip install --require-hashes -r requirements.txt--require-hashes的作用是:只要下载到的包哈希对不上,pip 直接报错退出,不会静默安装。这一步是防投毒的关键动作,比单纯锁版本更硬。
3.2 config.yaml 骨架
LiteLLM proxy 模式用config.yaml描述模型列表。下面这份骨架把上游统一指向 TaoToken 通道,你只需要替换 Key 和模型名。
model_list: - model_name: gpt-4o litellm_params: model: openai/gpt-4o api_base: https://taotoken.net/api api_key: os.environ/TAOTOKEN_API_KEY - model_name: claude-sonnet litellm_params: model: anthropic/claude-sonnet-4-20250514 api_base: https://taotoken.net/api api_key: os.environ/TAOTOKEN_API_KEY general_settings: master_key: os.environ/LITELLM_MASTER_KEY database_url: os.environ/DATABASE_URL几个字段说明:
| 字段 | 作用 | 注意点 |
|---|---|---|
model_name | 业务侧调用的别名 | 自定义,建议语义化 |
model | 上游真实模型标识 | 前缀决定走哪家适配器 |
api_base | 统一通道地址 | 固定为https://taotoken.net/api |
api_key | 通道凭证 | 用os.environ/引用,不写明文 |
master_key | proxy 自身的访问密钥 | 和上游 Key 分开管理 |
环境变量这样设:
export TAOTOKEN_API_KEY="你的TaoToken Key" export LITELLM_MASTER_KEY="给proxy用的随机串"提示:
api_key写成os.environ/TAOTOKEN_API_KEY而不是直接填值,是为了让配置文件可以进 Git,而密钥留在运行环境里。这是供应链安全里最基本的一条隔离。
3.3 启动 proxy
litellm --config config.yaml --port 8000启动后 LiteLLM 会在本地 8000 端口暴露 OpenAI 兼容接口,业务代码把 base_url 指向http://127.0.0.1:8000即可,不再直接接触任何厂商 Key。
4. 验证请求:确认通道打通且版本干净
配置写完必须验证两件事:通道能通、环境里没有恶意残留。
4.1 检查恶意 .pth 文件
# 找到 site-packages 路径 python3 -c "import site; print(site.getsitepackages()[0])" # 在该目录下查找可疑的 .pth find "$(python3 -c 'import site; print(site.getsitepackages()[0])')" -name "litellm_init.pth"如果这条命令有输出,说明环境里存在异常文件,先删掉再继续。同时检查一下持久化痕迹:
ls -la ~/.config/sysmon/sysmon.py 2>/dev/null systemctl --user status sysmon.service 2>/dev/null4.2 发一个真实请求
用 curl 打 proxy 的兼容端点:
curl http://127.0.0.1:8000/v1/chat/completions \ -H "Authorization: Bearer $LITELLM_MASTER_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o", "messages": [{"role": "user", "content": "只回复两个字:通了"}] }'预期返回结构里choices[0].message.content有内容,model字段回显你配置的别名。如果返回 401,检查LITELLM_MASTER_KEY是否和启动时一致;如果返回上游错误,检查TAOTOKEN_API_KEY和api_base。
4.3 用 Python 库方式验证
如果你不用 proxy,而是直接import litellm,可以这样指定通道:
import os import litellm os.environ["TAOTOKEN_API_KEY"] = "你的TaoToken Key" resp = litellm.completion( model="openai/gpt-4o", api_base="https://taotoken.net/api", api_key=os.environ["TAOTOKEN_API_KEY"], messages=[{"role": "user", "content": "ping"}], ) print(resp.choices[0].message.content)跑通说明库本身工作正常,且请求确实走了统一通道。想快速对比不同模型输出,可以直接在模型对话页试:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite
5. 本篇常见错排查
5.1 pip install 报哈希不匹配
--require-hashes模式下报THESE PACKAGES DO NOT MATCH THE HASHES,通常是你本地缓存的 wheel 和 requirements 里的哈希对不上。先清缓存再装:
pip cache purge pip install --require-hashes -r requirements.txt如果清了还报,说明 requirements.txt 里的哈希是旧版本生成的,重新pip-compile --generate-hashes一次。
5.2 启动 proxy 报 model 找不到
litellm.BadRequestError: model not found多半是model字段前缀写错。走统一通道时,前缀要和适配器匹配,比如openai/gpt-4o、anthropic/claude-sonnet-4-20250514。前缀不对,LiteLLM 会按错误的适配器去拼请求。
5.3 401 但 Key 明明是对的
分两种:proxy 返回 401,是master_key不匹配;上游返回 401,是TAOTOKEN_API_KEY无效或没被读到。用echo $TAOTOKEN_API_KEY确认环境变量在当前 shell 里真的存在,os.environ/引用的是进程启动时的环境,不是配置文件里的值。
5.4 装完还是担心有残留
跑一遍完整自查:
pip show litellm | grep -i version find "$(python3 -c 'import site; print(site.getsitepackages()[0])')" -name "*.pth" | xargs grep -l "litellm" 2>/dev/null kubectl get pods -A 2>/dev/null | grep node-setup第三条只在你有 K8s 环境时才有意义。任何一条有异常输出,先隔离机器再处理。
5.5 依赖树里间接引入 litellm
pip install dspy这类框架可能把 litellm 作为传递依赖拉进来。用这条命令看谁引入了它:
pip show litellm | grep -i "required-by"如果required-by里有你不认识的项目,去它的依赖声明里确认 litellm 的版本约束,必要时用pip install "litellm==1.82.6"覆盖,再跑一次哈希校验。
6. 把 Key 通道收拢之后,长期怎么维护
供应链投毒这件事真正改变的不是某一次安装动作,而是“Key 放在哪、依赖怎么锁”这两个习惯。统一通道的价值在于:业务代码里不再出现厂商原始 Key,LiteLLM 的配置只认一个api_base和一个可吊销凭证。这样即使某天又出现类似的包污染,你能做的响应从“轮换所有厂商 Key”变成“吊销一个通道 Key 再换一个”。
如果你在跑编码类 Agent 或长期任务,建议把通道 Key 和 proxy 的 master key 分开管理,前者管上游访问,后者管本地服务入口。Coding Plan 页面有这类长期场景的说明:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
最后留一个我踩过的坑:config.yaml里用os.environ/引用变量时,如果你用 systemd 或容器启动 proxy,环境变量要在 service 文件或 compose 里显式传入,光在.bashrc里 export 是不生效的。启动前先env | grep TAOTOKEN确认一遍,能省掉很多“配置明明对却 401”的排查时间。