239 个模型的密钥,一个硬编码变量
大模型众测报告里最刺眼的例子:某产品把云平台 239 个模型的 API Key 硬编码在前端。密钥泄露不是新问题,但大模型把它放大了——一个 key 背后是一整片模型和一张账单。这篇讲怎么扫、怎么管、怎么轮换。

图:gitleaks 仓库首页,用于扫描代码与 Git 历史中的密钥(来源:github.com/gitleaks/gitleaks,截图于 2026-10-19)
众测报告里有一句话我看了很久:某大模型存在 API 密钥硬编码与泄露漏洞,暴露了所有用户共享使用的云平台 239 个模型的 API 密钥。
239 个模型,一组 key。这意味着任何拿到这组 key 的人,可以免费调用 239 个模型,账单记在厂商头上;如果这些模型里包含微调过的私有模型,还可能被逆向、被滥用、被拿去训练竞品。
密钥是怎么跑到前端去的
我见过和听过的方式,基本就这几种:
- 前端直连:为了省一个后端代理,把 key 写进
VITE_*/NEXT_PUBLIC_*环境变量,构建时打进 JS bundle; - 移动端/桌面端:key 编译进客户端,反编译就能拿到;
- Agent 工具配置:把 key 写进 prompt、工具描述、示例代码,模型会把它复述出来;
- CI 日志:
echo $API_KEY调试完忘了删,日志公开; - Git 历史:提交过
.env,后来删了文件但历史还在。
报告里的例子属于第一种的变体:共享 key 硬编码,所有用户共用一个凭证。这种设计下,限流、审计、计费全部失效,因为你分不清是谁在调用。
先扫一遍自己的仓库

图:GitHub Secret Scanning 文档,平台侧的兜底扫描(来源:docs.github.com,截图于 2026-10-19)
我常用两个工具,一个查当前文件,一个查历史:
# 当前工作区
gitleaks detect --source . --no-git
# 连同 git 历史
gitleaks detect --source . --log-opts="--all"
TruffleHog 也可以,它对高熵字符串的检测更激进,误报也多一些。两个都跑,交叉看。
除了工具,我还会手动 grep 几类模式:
grep -rn "sk-[A-Za-z0-9]\{20,\}" src/ --include="*.ts" --include="*.vue"
grep -rn "api[_-]\?key" src/ -i
grep -rn "Bearer " src/
扫完不是删掉就完事。只要 key 进过公开仓库、进过前端 bundle、进过日志,就必须当作已泄露,走轮换流程,而不是“删了就当没发生”。
正确的密钥管理长什么样

图:TruffleHog 仓库首页,对高熵字符串更敏感的另一种扫描器(来源:github.com/trufflesecurity/trufflehog,截图于 2026-10-19)
我现在的做法是三条:
1. 密钥只存在服务端
前端永远不碰 key。需要调用模型时,前端调自己的后端,后端用服务端环境变量里的 key 去请求。多一层代理,换来的是密钥不出内网。
如果站点是纯静态的(比如这个博客),就不该有“前端调模型”这种需求。真要有,也应该走一个受控的边缘函数,而不是把 key 塞进浏览器。
2. 按用途拆 key,按权限收窄
一个 key 干所有事,是 239 个模型共享密钥的根源。正确做法是:
| 用途 | key 类型 | 权限 |
|---|---|---|
| 线上聊天 | 生产 key | 只允许 chat 模型,限流 |
| 批处理 | 批处理 key | 只允许指定模型,低配额 |
| 开发测试 | 测试 key | 低配额,可随时吊销 |
| CI | 短期 token | 单次任务有效 |
云厂商大多支持子账号 / 项目级 key,用起来不麻烦,麻烦的是“一开始没设计”。
3. 短期凭证 + 自动轮换
长期 key 的问题是“泄露了也不知道”。我倾向用短期凭证:每次任务签发一个 15 分钟有效的 token,任务结束就失效。这样即使泄露,窗口也很小。
轮换要有自动化:定期生成新 key、灰度切换、旧 key 保留一个过渡期再吊销。手动轮换的结果通常是“永远没轮换”。
泄露之后的处置顺序
如果确认泄露,按这个顺序走:
- 先吊销,别先查原因。攻击者不会等你排查完。
- 查用量和日志,确认有没有异常调用、有没有数据外泄。
- 评估影响面:这个 key 能访问哪些模型、哪些数据、哪些下游服务。
- 轮换所有同源凭证(同一个环境里的其他 key 也要换)。
- 清理泄露源:前端 bundle、Git 历史(必要时 filter-repo)、日志、截图。
- 补上检测:CI 里加 secret scanning,提交前拦截。
第 5 步的 Git 历史清理要谨慎:重写历史会影响所有协作者,先备份、再通知、最后强推。能用 git filter-repo 就别用 filter-branch。
为什么大模型让这件事更严重
传统 API key 泄露,损失通常是“被白嫖调用”。大模型 key 泄露,损失可能是:
- 账单:模型调用按 token 计费,被刷一晚可能上千美元;
- 数据:如果 key 绑定了知识库、微调模型,攻击者能读到你的私有数据;
- 声誉:攻击者用你的 key 生成违规内容,责任算在你头上;
- 供应链:一个 key 打通多个模型,等于一次泄露影响整条产品线。
报告里那 239 个模型共享一个 key 的设计,等于把上面四条全占了。
前端 bundle 里怎么找 key
如果你怀疑自己的站点泄露了 key,别只搜源码,要搜构建产物:
# 在 dist 里找疑似密钥
grep -rEo "sk-[A-Za-z0-9_-]{20,}" dist/ | head
grep -rEo "(api[_-]?key|token)[\"']?\s*[:=]\s*[\"'][^\"']{16,}" dist/ | head
更彻底的办法是打开浏览器开发者工具,在 Sources 里全局搜索 Bearer、apiKey、sk-。前端能看到的,攻击者都能看到。
找到之后别急着删代码。先确认这个 key 的权限范围,再决定是轮换还是吊销。删代码只是止血,泄露的 key 还在外面。
CI 里加一道 secret scanning
光靠人记不住。CI 里加一步扫描,提交前就拦住:
- name: secret scan
run: |
npx gitleaks detect --source . --no-git --exit-code 1
GitHub 自带 secret scanning 也可以开,但它只覆盖已知格式的密钥,自建服务的 token 不一定能识别。两层一起用,误报可以调规则。
扫描规则要定期更新:新的模型厂商、新的 key 前缀,旧规则不一定覆盖。我每季度会看一次误报和漏报,把规则调一遍。
轮换要自动化
手动轮换的结果通常是“永远没轮换”。我的做法是:
- 密钥存进 secret manager(Vault、云厂商的 KMS、或者至少是加密的环境变量);
- 服务启动时拉取,不落盘;
- 设置过期时间,到期前自动生成新版本;
- 旧版本保留一个过渡期,确认无调用后吊销。
这套流程初期麻烦,但一旦跑起来,泄露的处置就从“紧急事故”变成“例行操作”。
算一笔账
假设一个 key 能调 239 个模型,平均每次调用 2000 token,单价按每百万 token 几美元算。被刷一晚上(8 小时,每秒 10 次请求),大概是 5.7 亿 token——账单能到几千美元。如果模型里有贵的推理模型,数字还要翻几倍。
这还没算数据泄露和声誉损失。相比之下,花半天时间做密钥管理,投入产出比高得离谱。
常见误区
误区一:“key 只在前端用,后端没有,泄露也没事”。前端 key 就是公开的,任何人打开开发者工具都能拿到。所谓“只在前端用”,等于把 key 印在广告牌上。
误区二:“设了额度上限就安全了”。额度上限能限制损失,但不能阻止泄露。攻击者可以在额度内慢慢用,或者把 key 转卖。额度是止损,不是防御。
误区三:“轮换太麻烦,等出事再说”。轮换麻烦一次,泄露麻烦一年。把轮换做成脚本、接进 CI,麻烦就只剩第一次。
我的判断
密钥管理是个“做了没人夸、不做会出事”的事。大部分团队不是不知道要做,而是“先上线再说”。大模型时代这个拖延的代价变高了,因为 key 背后的资源比传统 API 值钱得多。
最低成本的起步动作:今天花十分钟,跑一遍 gitleaks,把仓库里的 key 扫出来;然后把前端直连的调用挪到服务端。这两步做完,风险能降一大半。
补充一个细节:扫描时别忘了 CI 配置、Dockerfile、示例代码、测试 fixture。这些地方经常藏着“临时用一下”的 key,而且很少被清理。我见过最离谱的一个案例,key 写在 README 的示例里,仓库是公开的,挂了半年没人发现。这类问题的共同点是“没人负责”:写的人觉得是临时,看的人觉得不关我事。把 secret scanning 接进 CI,等于给这件事找了个自动负责人。扫描规则会误报,但误报的代价远低于漏报。漏报一次,可能就是 239 个模型一起裸奔。把这件事当成上线检查的一部分,而不是一次性的运动。

评论
…