本文把没有安全背景的个人开发者常遇到的一类事故,隐去所有可识别的细节后整理成教训。文中不包含复现攻击的步骤。目的是防御,让你不再犯同样的错误。
- 类型
- API key 泄露 / 盗刷
- 严重度
- Critical(被利用的是公开的 CVSS 10.0)
- 根本原因
- Web 框架的认证前 RCE 数月未打补丁
- 泄露范围
- 环境里的所有 secret
- 执行范围
- 局限在非特权容器内(宿主机 root 未受损)
- 根治措施
- ①升级到修复版 ②轮换全部 key ③用机器监控 CVE
「止住了账单」≠「处理完成」
止住账单只是临时措施。封堵泄露途径是另一项工作。两件都做完,才算处理完成。
发生了什么(时间线)
Day 0 — 上线并运行
借助 AI 写好的应用上线,并在生产环境中运行。某一天 — 账单暴涨
云 AI 的账单突然飙升。跑的是本不会用到的模型,做的是与自己无关的大量批量处理。排查 — 第三方滥用
「自己的应用失控了」根本解释不通。是第三方拿着偷来的 key 在跑。追查 — 调查泄露途径
「那么 key 是从哪里漏出去的?」实际的调查从这里才开始。
为什么一开始没能察觉
按犯错的先后顺序写下来,因为这些错误本身就是教训。
过早认定了原因
→ 在下定论「是自己的错」还是「是别人的错」之前,先看数据。
grep 干净,差点就停止调查
掩盖症状就以为修好了
grep 干净不代表没事
即便任何文件里都没有 key,漏洞也可能从正在运行的进程中抽走环境变量。泄露不只发生在文件里,也发生在运行时(RCE、HTTP 请求头)。详见 什么是 RCE。
实际的原因:公开的 CVSS 10.0 一直没打补丁
把旧访问日志里一个奇怪的特征拿去和威胁情报比对,很快就匹配上了。所用 Web 框架的版本区间存在一个公开的认证前 RCE(CVSS 10.0),并且已经在实际中被利用,而它数月没打补丁一直在运行。
- 这不是被动的 bug,而是攻击者在服务器上执行代码、把环境变量带走。
- 限制了损害的一点是:执行范围局限在非特权容器内(宿主机 root 没有被拿下)。入侵调查没有发现常驻后门、挖矿程序或 C2,能确认的影响是 secret 被窃取。
- 由于内部 DB 是可达的,处置时假定DB 内容也已泄露。
教训:出现奇怪的行为时,先怀疑已知 CVE。不要认定是自己的 bug。
缩小泄露范围:那个 key 存放在哪里?
grep 什么也找不到时,就换一种查法。列出被滥用的 key 写在过哪些地方,候选的泄露途径就会变少。例如,如果被滥用的 key 只存在于生产服务器的环境变量中,就应怀疑生产服务器或托管服务商,而不是 Git 或浏览器。
| 泄露位置 | 常见原因 | 如何检查自己的环境 | 如何封堵 |
|---|---|---|---|
| Git 历史 / 公开仓库 | 提交了 .env、把 key 硬编码 | 用 gitleaks git 扫描完整历史。查看 GitHub 的 secret scanning 警报 | 先吊销,再在提交前拦截 |
| 发送到浏览器的 JavaScript | 把 key 放进 NEXT_PUBLIC_ 变量、直接从浏览器调用 API | 在构建产物中搜索 key 的前缀 | 把 API 调用移到服务端代码 |
| 公开的 Web 文件夹 | .env 或备份放在了公开文件夹里 | 在自己的网站上打开 /.env,确认返回 404 或 403 | 把机密文件移到公开文件夹之外 |
| 日志 / 错误页面 | 开着调试输出上线、原样记录请求头 | 检查生产环境的调试设置,以及日志中是否出现 Authorization 等 | 关闭调试输出,并在日志中屏蔽机密 |
| 正在运行的服务器 | 公开的漏洞(如 RCE)未打补丁 | 对比实际运行的版本与公告中的修复版本 | 升级,然后更换环境中的所有 secret |
| 托管服务商 | 服务商一侧被入侵 | 查看服务商的安全公告和邮件通知 | 以保存后无法再查看值的方式存放 secret,并轮换 |
| 你的开发机 | 恶意软件包或扩展 | 检查你安装的软件包或扩展版本是否与公开的入侵事件吻合 | 移除该版本,并更换这台机器上存放过的 secret |
本案例属于「正在运行的服务器」这一行。下面四行都是不会在代码或 Git 中留下痕迹的途径。OWASP 的 Secrets Management Cheat Sheet 也提醒,环境变量往往能被同一环境中的其他进程读取,还可能进入日志或转储。所以,一个 key 被滥用时,要把存放在同一处的所有 secret 都当作已泄露。
分开使用 key,调查会更快
如果每个服务、每个环境(生产与开发)使用不同的 key,仅凭被滥用的 key 就能知道你处在表中的哪一行。一个 key 到处复用,就只能怀疑所有位置。如何拆分 key,见 API key 被盗导致 OpenAI 账号被封。
按实际运行的版本来统计漏洞
检查其他依赖时,报告说「还有 8 个有漏洞的依赖」,这也错了。它是按 package.json 中的下限(^ caret 范围)来统计的。真正危险的,只有遗留下来的 2 个固定版本的依赖。
检查自己实际运行的版本
要看 lock 文件或正在运行的容器中实际解析出来的版本。
# npm: see what's actually installed
npm ls next react react-dom
# inside a running container
docker exec <container> npm ls <package>这里显示的数字才是实际运行的版本,而不是 package.json 里的 ^。
如何检查自己的环境
无论你刚发现奇怪的账单,还是什么都还没发生,都可以按这个顺序检查。每一步只查看你自己的账户和服务器。
查看用的是哪个 key、在什么时候
检查发送到浏览器的文件
NEXT_PUBLIC_ 开头的环境变量的值,会在构建时内联到发送给浏览器的 JavaScript 中。构建后,在产物中搜索 key 的前缀,例如 grep -rl "sk-" .next/static。在那里找到的东西,任何人都读得到。确认公开文件夹中访问不到机密文件
curl -sI https://example.com/.env(把 example.com 换成你的域名),确认第一行的状态码不是 200(应为 404 或 403)。详见 检查公开目录中的机密文件。对比生产版本与公告
npm ls 查看版本。osv-scanner scan -L pnpm-lock.yaml 可以检查 lock 文件中是否包含有已知漏洞的版本。检查日志中有没有 secret
gitleaks dir --redact,找出被写进日志的 key。--redact 让找到的值本身不显示在屏幕上。检查上限和提醒
首先做了什么(可复现的步骤)
立即吊销被滥用的 key
把框架升级到修复版
轮换所有 secret
入侵调查
账户侧的防御
防止复发:自动监控 CVE
归根结底,这次事故就是「一个公开的 CVSS 10.0 被人为疏忽了」。人工检查总会遗漏,自动监控能发现它们。
今天就能开始的依赖扫描
免费即可开始,在 CI 中加一个步骤即可。
# OSV scanner (Google) checks your lockfile
osv-scanner scan -L pnpm-lock.yaml
# On GitHub, enable Dependabot (repo Settings → Security)本站自己也按照这个教训,用 CVE 监控自身的依赖。我们推荐的做法,我们自己也在做。
常见错误及修正方法
- 把新 key 放到仍然敞开的服务器上:同一条途径也会把新 key 偷走。立刻停用被滥用的 key,修复漏洞,然后再完成其余 secret 的更换。
- 把预算提醒当成上限:Google Cloud 明确说明,只有提醒的预算不会自动停止用量或支出。想让它停下来,就要加上能阻止用量的上限,例如 OpenAI 的每月硬性上限或 Anthropic 的 Workspace 支出上限。
- 把 AI API key 放进浏览器或手机应用:任何到达用户设备的值都可能被取出。从你的服务器调用 AI API,让浏览器调用你的服务器。
- 从文件或 Git 历史中删掉就到此为止:已经复制了它的人仍然可以使用。先吊销,之后再清理历史(见 为什么 GitHub 上泄露的凭据仍然有效)。
- 开发和生产使用同一个 key:分不清是哪一边泄露的,只能两边都停。每个环境签发单独的 key。
公开案例:环境变量从服务商一侧泄露
任何文件搜索都找不到的泄露,也会发生在你自己的服务器之外。2026 年 4 月 19 日,Vercel 披露了其内部系统遭到未经授权访问。据该公司称,一名员工使用的第三方 AI 工具被入侵,导致部分客户未标记为「sensitive」(保存后无法再查看值的设置)的环境变量被盗。
Vercel 建议客户检查并轮换未标记为 sensitive 的环境变量,并查看活动日志和最近的部署。它还表示,删除项目或账户并不足以消除风险。
处于这种情况的客户,即使把代码和 Git 搜上一整天,也找不到泄露的痕迹。应对方法与本案例相同:更换存放在那个地方的所有 secret。具体步骤见 Vercel 安全事件(2026 年 4 月)。
教训
常见的错误
- 「止住了账单」就当作处理完成
- grep 干净就放心
- 在代理层掩盖症状,就觉得修好了
- 按
package.json的下限来数漏洞 - 只轮换确认被滥用的那 1 个 key
正确的防御
- 把止住盗刷和封堵泄露途径当作两件事,都要做
- 也要怀疑运行时泄露(RCE、请求头)
- 更新造成问题的框架
- 按实际运行的版本来判断
- 一旦泄露,就更换整个 env
总结一下:账单暴涨是症状,原因是一直没打补丁的 CVSS 10.0 RCE。正确的原因并不是第一次就找到的,而是错了几次、反复核实之后才找到的。
接着读
- 泄露之后:GitHub 上暴露的 543,699 个凭据仍然有效:为什么必须吊销而不只是删除
- 术语:什么是 RCE / 什么是 CVE / 什么是 CVSS
- 对策:安全运营 Next.js(跟进 CVE)
- 事故:.env 被全世界看到的时候
- 基础:.env 与 API key 的基础(泄露后该怎么办)
- 预防:用 gitleaks 在提交前拦住机密
- 事故:API key 被盗导致 OpenAI 账号被封
参考资料
- OWASP, "Secrets Management Cheat Sheet"(环境变量的风险、泄露时的应对):cheatsheetseries.owasp.org
- Next.js Docs, "How to use environment variables in Next.js"(
NEXT_PUBLIC_的内联):nextjs.org - OSV-Scanner, "Usage":google.github.io
- OpenAI, "Production best practices"(key 的管理、提醒和上限):developers.openai.com
- Claude Help Center, "Creating and managing Workspaces"(Workspace 的上限和 key):support.claude.com
- Google Cloud, "Create, edit, or delete budgets and budget alerts":docs.cloud.google.com
- Vercel, "Vercel April 2026 security incident":vercel.com