
把密钥挪出进程,救不了被投毒的 CI
· 阅读约 3 分钟
七月初 dev.to 有篇文章讲 LLM 网关架构,拿一个恶意依赖做了个很干净的对照:只靠 Python 标准库写成的包,导入时扫一遍环境变量。场景 A,它读到了 PROVIDER_API_KEY;场景 B,同一个包只能摸到一把 GATEWAY_TOKEN。密钥住在哪个进程里,决定恶意包到你机器上之后能翻到哪张牌。
这个演示的道理没错。网关令牌就算被偷,也是限定过操作、可以单独轮换的东西;提供商主 key 进了应用进程,等于跟所有第三方依赖睡同一间屋。谁躺你旁边,谁就能翻你口袋。
但文章拿 LiteLLM 当案例那一段,我读着不对劲。LiteLLM 三月那次事故,坏的根本不在运行时。
攻击者篡改了上游某个 GitHub Action——不是 LiteLLM 自己代码里的洞——偷了项目 CI 里的发布令牌,顺手往 PyPI 推了 1.82.7 和 1.82.8。坏包会搜集云凭据、SSH 密钥、Kubernetes 令牌、数据库凭据,然后外带。官方说坏版本大概待了 40 分钟,第三方翻日志说接近 3 小时。这两个数字之间的差,通常就是真实暴露窗口的样子。Mercor 是公开确认被点名的一家下游,没公开的名单大概率更长。
换成攻击者的思路:想一次性把手伸进一堆 AI 应用的调用链,他不会蹲在某个进程里翻环境变量。翻一个只赚一个。他更乐意端掉发布管道,让所有后来安装的人自动把他的代码抱回家。破坏上游→偷发布令牌→脏版本上 PyPI→批量中招,这条链又短又省力。你光把主 key 挪出进程,拦不住这条链上的任何一环。
这个我一开始差点判错。看到“凭据泄露”就往运行时上靠,觉得换个地方放 key 就稳了。把 LiteLLM 时间线捋一遍才反应过来:这次攻击压根没摸运行时,摸的是“你拿什么签发信任”的那个源头。密钥挪出去,也不代表你离安全更近了。
但全文我最想留住的,是另一句近乎嘲讽的事实:LiteLLM 官方 Docker 部署因为钉死了依赖版本,毫发无损。你懒得动版本号,坏版本就砸不到你。平时大家嫌依赖锁烦,觉得每次升级都要重测一遍,这次倒好,那些“不升级主义者”成了唯一没被波及的人。安全有时候就是这么不讲理:救你一命的不是高明防线,是那一行没动过的版本号。
至于文章后段重点介绍的他们自研的开源网关,我当软广跳了。网关的中央控制面听起来不错,但我不会为哪家实现写推荐语。值钱的是“提供商主凭据跟应用进程分开”这个决定,不是卖网关的那帮人。
拆弹清单就三条:
- 提供商主 key 不进应用进程,应用只拿网关令牌,且令牌可单独轮换、可限定到按代理按团队。
- CI 令牌按最小权限给:能用只读的别用发布令牌;发布令牌单独一把,锁到具体 repo,别让整个流水线共用一个能发版的凭证。LiteLLM 那把发布令牌要是从一开始就没待在那个被打穿的 CI 里,整件事的剧本就得重写。
- 依赖锁定 + 可重复构建,不是工程洁癖,是把供应链上坏一环的爆炸半径压小的硬要求。这次 Docker 部署替你演示过了。
别慌,三条不用今晚全干完。但你得先回答一个问题:你们最贵的那把密钥,正住在一间谁都能进出的房间里,还是住在一间只认门禁卡的房间里?
排雷记录:钥匙放在哪个进程里,决定被偷时损失多少;但发钥匙的那条链要是断了,钥匙藏哪都一样。
评论
还没有评论,写下第一条讨论。