API Key 是怎么泄漏的:从 .env 到公开仓库
先说结论
- Key 泄漏是一个规模问题,不是运气问题。 GitGuardian 的《State of Secrets Sprawl 2026》(2026 年 3 月 17 日发布)统计:仅 2025 一年,公开 GitHub 提交里新增了 2,865 万个硬编码密钥,同比增长 34%;其中 AI 服务相关的密钥有 1,275,105 个,同比增长 81%。
- 发现泄漏后,第一步是撤销,不是删代码。 GitHub 官方文档的原话:「as a first step you need to revoke and/or rotate that secret」。
- 改写历史清不干净。 fork、clone、缓存视图、引用它的 pull request——GitHub 自己列出了这几条改完之后仍然存在的路径。
- 轮换的顺序不能反:先撤销旧的,再签发新的。反过来会留一段你已经不再盯着、但旧 Key 仍然有效的真空期。
- 真正把损失封顶的是硬性消费上限,不是检测。OpenAI 文档:「Hard spend limits stop affected API traffic when tracked spend reaches the limit.」
- 让它成为最后一次的办法是提交前拦截:本地
gitleaks做 pre-commit 钩子,远端打开 GitHub 推送保护(仓库级默认关闭,要自己开)。
还有一个数字值得单独看:GitGuardian 对 2022 年确认有效的那批凭证做了回测——到 2026 年 1 月,仍有超过 64% 依然有效。也就是说,大多数泄漏出去的密钥,从来没有被撤销过。
一、Key 真正逃逸的五条路径
按实际发生频率排,不是按想象中的危险程度排。
路径一:提交进了仓库,而且进了历史。
最经典的一条。.env 没进 .gitignore、配置文件里留了一行、测试脚本硬编码了一个。危险的不是当前文件,是历史——你后来把那行删掉了,但那次提交还在。
路径二:写进了跑在用户浏览器或 App 里的代码。 前端代码是公开的,没有例外。OpenAI 的生产最佳实践文档说得很直白:
Avoid exposing the API keys in your code or in public repositories; instead, store them in a secure location.
正确做法是用环境变量或密钥管理服务把 Key 提供给你的服务端,由服务端去调用模型 API,前端只和你自己的服务端说话。
路径三:截图。 排查问题时截一张终端或控制台的图发到群里、发给 AI、贴进工单。截图里带着完整的 Key,而群聊和工单系统的保存期通常比你想象的长得多。这一条和《跨境团队的数据红线清单》第四节是同一个问题。
路径四:粘进了 AI 对话或第三方服务。 排查一段报错时,把整段日志或整个配置文件粘进对话框——报错日志里最常见的就是带着 token 的请求头。如果对话是经过某个镜像站的,那么这个 Key 已经明文落在陌生人的服务器上了(见《「ChatGPT 中文版」和镜像站》)。
路径五:交给了「中转 API」服务商。 这条在国内开发者里特别常见,因为它被包装成「省钱」。实际上你是把账单权限交给了一个你无法审计的第三方,且违反条款——见第六节。
AI 辅助编码让前两条变快了。 GitGuardian 那份报告的一项对比:Claude Code 辅助的提交,密钥泄漏率为 3.2%,而全部公开 GitHub 提交的基线是 1.5%。这不是某个工具的问题,是生成速度上升而审阅速度没跟上的结构性结果——所以下面第七节的自动闸门比以前更重要。
二、先查自己有没有已经漏了
别猜,扫一遍。 下面的命令在你自己的仓库目录里跑,不需要任何外部服务。
第一步:看当前工作区有没有不该被跟踪的文件。
# 这些文件是否已经被 git 跟踪?有输出就是已经进仓库了
git ls-files | grep -E '\.env|\.envrc|secrets|credentials|\.pem$|\.p12$|id_rsa'
第二步:搜整个历史,而不只是当前版本。
# 在所有历史提交的内容里搜 Key 的特征前缀
git log -p --all -S 'sk-' --pickaxe-regex | head -50
# 同样方式可以搜其他厂商的前缀,例如:
# AWS: AKIA Google: AIza GitHub token: ghp_
第三步:用专门的工具跑一遍全历史。
# gitleaks 扫描整个仓库历史(不只是当前工作区)
gitleaks git --verbose
判读标准:任何一条命中都当作已泄漏处理,走第三、四节。不要因为「那个仓库是私有的」就跳过——私有仓库会被设为公开、会被 fork、会被拷贝到别的地方,而 Key 的生命周期往往比仓库的可见性设置长得多。
第四步(可选但值得):去各家厂商的控制台看一眼用量曲线。一段你解释不了的用量,比任何扫描结果都确凿。
三、已经推到公开仓库了:正确顺序
很多人的第一反应是「赶紧把那次提交删掉」。这个顺序是错的。
GitHub 官方文档在讲移除敏感数据时,把这件事放在最前面:
It is important to note that if the sensitive data you need to remove is a secret (e.g. password/token/credential), as is often the case, then as a first step you need to revoke and/or rotate that secret.
为什么撤销要排在改历史前面? 因为改历史并不能把已经扩散出去的副本收回来。同一份文档列出了改写之后仍然可能存在的路径:
- 仓库的 fork 里继续存在——「If the commit that introduced the sensitive data exists in any forks, it will continue to be accessible there.」
- 已有的 clone 里继续存在;
- 通过 SHA-1 哈希在 GitHub 的缓存视图里仍可访问;
- 引用这些提交的 pull request 里仍可访问。
所以正确顺序是:
- 撤销那个 Key。(这一步做完,后面的所有事情都变成了整洁问题,而不是安全事故)
- 签发新 Key 并更新所有使用它的地方。
- 再决定要不要改写历史。 官方推荐的工具是
git-filter-repo(至少 2.47 版,配合--sensitive-data-removal)。注意改写历史会影响所有协作者,需要事先沟通。 - 如果仓库有 fork,联系 GitHub 支持处理缓存与 fork 中的引用。
一句话记住:撤销是止血,改历史是打扫。先止血。
四、轮换:为什么顺序不能反
错误的做法:先签发新 Key,部署上去,「回头再把旧的删掉」——然后就忘了。旧 Key 在这段时间里一直有效,而没有人再盯着它。
正确的默认顺序(适用于停机几分钟没关系的场景):
- 撤销旧 Key;
- 签发新 Key;
- 更新所有用到它的地方(服务端环境变量、CI 的 secrets、本地
.env、部署平台的配置); - 确认业务恢复正常;
- 检查厂商控制台的用量曲线,确认没有用旧 Key 的残留调用。
不能停机的场景怎么办? OpenAI 文档给的思路是提前规划而不是临时补救:
We strongly recommend setting an expiration date when you create a project API key and establishing a regular key rotation process.
具体流程是在旧 Key 过期之前创建替代 Key、更新应用、确认替代 Key 正常工作之后再撤销旧的。同一份文档还提到管理员可以在组织或项目层面强制 API Key 的最长有效期,避免任何一个 Key 无限期有效。
从这段文档里可以抄走的一条做法:给每个 Key 都设一个到期时间。到期时间强迫轮换变成例行公事,而例行公事不会被忘记。
五、把损失封顶:限额才是真正的保险
这一节比前面所有节都重要,因为它决定了一次泄漏的最坏结果是一个已知数字,还是一张不确定的账单。
OpenAI 的文档写明了两层控制:
- 消费提醒:在 limits 页面设置,用量超过某个金额时发通知;
- 硬性上限:「To enforce a monthly cap, set a hard spend limit. Hard spend limits stop affected API traffic when tracked spend reaches the limit.」——达到额度时直接停掉受影响的 API 流量。文档同时提醒,在生产环境启用之前先读一遍 spend limits 指南。
Anthropic 这边需要如实说明:我们在 2026 年 9 月 20 日核对其 Admin API 文档时,看到 spend limits 是作为 Claude Enterprise 专有的接口列出的(与分组、自定义角色读取并列),没有在这份文档里找到面向个人或普通组织账号的硬性消费上限说明。查不到就是查不到——请以你自己控制台里实际提供的选项为准。
三条通用做法,不依赖具体厂商:
- 一个用途一个 Key。 生产一个、测试一个、本地开发一个、每个第三方集成一个。这样撤销一个的时候,不会波及其他。
- 给每个 Key 设到期时间(见上一节)。
- 把上限设成你愿意直接损失的金额,而不是你预计会用到的金额。上限的作用是封顶,不是预测。
六、永远不要把 Key 交给你不运营的第三方
这里说的是「中转 API」「便宜 Key」这一类服务。它在条款和技术两个层面都不成立。
条款层面,OpenAI 使用条款(标注 Effective: January 1, 2026)Registration 一节:
You may not share your account credentials or make your account available to anyone else and are responsible for all activities that occur under your account.
不得共享账号凭证,且账号下发生的一切由你负责。 这句话的两个方向都要看清楚:你买别人的 Key,对方在违约;你把自己的 Key 交给一个中转服务,你就是那个要为对方全部调用负责的人。
技术层面,中转层在链路中间:
- 它能看到你全部的请求内容(包括提示词里带的业务数据);
- 它能用你的 Key 发起任何调用,账单算你的;
- 它随时可以停止服务,而你的业务依赖已经建立了。
本站不提供、也不链接任何 Key 转售或中转 API 的来源。 如果你现在正在用这类服务,第三、四节的流程今天就该跑一遍。
七、让这是最后一次:两道闸门
检测是事后的,拦截是事前的。 两道闸门配合,一道在你的机器上,一道在服务端。
闸门一:本地 pre-commit 钩子
用 gitleaks。它的官方仓库提供了 .pre-commit-hooks.yaml,配合 pre-commit 框架使用:
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1 # 2026-09-20 查询 GitHub 发布接口得到的最新版本
hooks:
- id: gitleaks
pre-commit install # 装好之后,每次 commit 前自动跑
钩子实际执行的命令是 gitleaks git --pre-commit --redact --staged --verbose——只扫暂存区,所以很快。如果确实要提交一个会被误报的测试用字符串,在那一行加 gitleaks:allow 注释即可放行。
rev要填你实际使用的版本号,请到该仓库的 Releases 页面取当前版本,不要照抄任何文章里的数字。
闸门二:GitHub 推送保护
GitHub 官方对它的描述是:
Push protection is a secret scanning feature designed to prevent hardcoded credentials, such as secrets or tokens, from ever being pushed to your repository.
检测到时的行为:「it will block the push and provide a detailed message explaining the reason for the block」——直接拦下这次推送并说明原因,你需要移除敏感信息后重新推。
一个必须知道的默认值:仓库级的推送保护是默认关闭的,需要在仓库设置里自己打开;而面向用户的推送保护在 GitHub.com 上对个人账号默认开启。两者不是一回事,别以为「GitHub 会帮我拦着」。
再加一条不花钱的习惯
.gitignore 里从第一天就写上:
.env
.env.*
*.pem
*.p12
secrets/
新建仓库的第一个提交就该包含它,而不是等到有东西要忽略的时候再加。
相关阅读:国内访问 GitHub、Docker、npm 的网络问题见《开发者翻墙指南》与《如何解决在中国访问 GitHub 速度慢的问题》;团队层面哪些内容不该进 AI 对话见《跨境团队的数据红线清单》;调用境外模型 API 时的地区与条款问题见《挂香港节点,为什么 Claude 和 ChatGPT 反而更容易出问题》。
参考资料
以下页面均于 2026 年 9 月 20 日核对。
- GitGuardian — The State of Secrets Sprawl 2026,2026 年 3 月 17 日(2025 年公开 GitHub 新增 28,650,000 个硬编码密钥、同比 +34%;AI 服务密钥 1,275,105 个、同比 +81%;Claude Code 辅助提交泄漏率 3.2% vs 基线 1.5%;2022 年有效凭证到 2026 年 1 月仍有超过 64% 有效),核对日期 2026-09-20
- GitHub Docs — Removing sensitive data from a repository(「as a first step you need to revoke and/or rotate that secret」;
git-filter-repo≥ 2.47 与--sensitive-data-removal;fork/clone/缓存视图/pull request 中的残留),核对日期 2026-09-20 - GitHub Docs — About push protection(功能描述、拦截行为、仓库级默认关闭而用户级默认开启),核对日期 2026-09-20
- OpenAI — Production best practices(不要在代码或公开仓库中暴露 Key;设置 Key 过期时间与轮换流程;消费提醒与硬性消费上限),核对日期 2026-09-20
- OpenAI — Terms of Use(标注 Effective: January 1, 2026;Registration 一节关于不得共享账号凭证)。原站对自动抓取返回 403,本文通过 Wayback Machine 2026-09-20 的快照读取:
https://web.archive.org/web/20260920011501/https://openai.com/policies/row-terms-of-use/,核对日期 2026-09-20 - Anthropic — Admin API 文档(组织成员、工作区、邀请与 API Key 的程序化管理;spend limits 列为 Claude Enterprise 专有接口),核对日期 2026-09-20
- Gitleaks — 官方仓库与其
.pre-commit-hooks.yaml(pre-commit 钩子命令与gitleaks:allow用法),核对日期 2026-09-20
将本指南加入收藏夹
跨境网络环境瞬息万变。建议按下 Ctrl+D (Windows) 或 Cmd+D (Mac) 收藏本页,以便在连接波动时快速查阅解决方案。
加入 5,000+ 跨境从业者,第一时间获取最新的 GFW 封锁动态与协议升级提醒。
* 我们绝不发送垃圾邮件,您可以随时取消订阅。
KUAJIE VPN