AI 生成应用上线前的密钥泄露自查
文章解释如何检查 AI 编程工具生成的前端是否包含私密 API 密钥,并区分可公开配置与服务端秘密。Vite 的 VITE_ 和 Next.js 的 NEXT_PUBLIC_ 变量会进入浏览器代码,环境变量与压缩均不能保护其中的秘密。
AI 深度解读
AI 帮你把 Stripe 支付、AI 聊天接通了,页面也能正常运行,但上线前还缺一次检查:访问者下载的 JavaScript 里,有没有本该只留在服务端的密钥?这篇指南的核心判断很直接:前端代码都是公开的,环境变量和代码压缩都不能替你保密。
它值得程序员读的地方,是把「发现泄露、立即止损、修正调用方式」串成了可操作的流程。不过,公开标识与私密凭据要分清,搜索不到也不能当成安全证明。
放进环境变量,为什么还是会泄露
Lovable、Bolt、Replit、Cursor 能快速完成服务接入,但「能用」和「密钥放对位置」是两件事。
作者讨论的常见项目是 React 前端,通常使用 Vite,再连接 Supabase、Firebase、Stripe 或 OpenAI。发布时,前端代码会打包成 JavaScript,发送给每一位访问者。浏览器必须拿到代码才能执行,访问者自然也能查看。
只要密钥进入浏览器,就应该按已经公开处理。
Vite 的 VITE_ 变量会在构建时写入前端产物;Next.js 的 NEXT_PUBLIC_ 也是如此。把密钥从组件挪到 .env,如果组件仍通过这些变量读取它,交付给用户的内容并没有变得保密。压缩代码只增加阅读难度。
作者列出几种常见诱因:让 React 聊天组件直接调用 OpenAI;遇到权限错误后,换上权限更大的密钥;把密钥贴进提示词,随后被硬编码;为了让前端读取配置,给所有变量都加上 VITE_。
其中最危险的是用 Supabase service_role 绕过行级安全策略(RLS)报错。错误消失,可能只是权限检查被绕开了,原来的访问规则并没有修好。
看到 key 不必慌,先判断它能做什么
作者真正要读者区分的,是「设计成公开的标识」和「持有后就能执行敏感操作的凭据」。
适合出现在前端的,包括 Supabase anon / publishable key、Firebase web config 中的 apiKey、authDomain、projectId,以及 Stripe 的 pk_live_…、pk_test_…。地图和分析服务的公开 key,则应按服务商支持的方式限制使用域名。
这些配置可以公开,不代表后面的数据可以随便访问。Supabase 依靠 RLS 控制数据权限,Firebase 依靠 Firebase Security Rules。前端隐藏按钮或过滤列表,无法代替这些规则。
不能进入前端的,则包括:
- Supabase
service_role,它可以绕过 RLS,具有很高的数据访问权限。 - Stripe
sk_live_…和rk_live_…,后者即使限制了权限,也属于私密凭据。 - OpenAI、Anthropic 等 AI 服务的
sk-…、sk-proj-…、sk-ant-…,泄露后可能被盗用额度。 - Resend、SendGrid、Twilio、Mailgun 等发送服务的密钥,以及数据库连接串
postgres://user:password@…、webhook signing secrets、JWT secrets 和 GitHub tokens。
这里需要补一个边界:原文把 Stripe 私密密钥能够执行的操作放在一起描述,但 restricted key 的实际能力取决于授权范围。判断泄露后果时,仍要查看该凭据到底拥有哪些权限。
检查线上产物,发现后先轮换密钥
作者建议花 5 分钟检查,入口必须是正式发布的 URL。编辑器预览不能代表真实访问者收到的产物。
用 Chrome 或 Edge 打开网站,按 F12,进入 Sources,通过 Ctrl+Shift+F 搜索已加载文件;原文给出的 Mac 快捷键是 Cmd+Option+F。重点搜索 service_role、sk_live、sk-、secret,以及自己曾贴进 AI 工具的密钥前缀。sk- 可能产生噪声,匹配结果需要人工判断。
Supabase 的旧式密钥是 JWT,通常以 eyJ 开头。作者建议用可信的本地 JWT 解码工具查看载荷:"role":"anon" 对应公开 key,"role":"service_role" 则需要立即处理。这一识别方法针对旧式 JWT,不能覆盖所有密钥格式。
再用 Ctrl+U 查看页面源码,检查 HTML 是否内嵌了凭据。随后打开 Network,刷新页面,查看发往 api.openai.com、api.stripe.com 等服务的请求及其 Authorization 请求头。浏览器直接发送了私密凭据,即使全文搜索没找到,泄露也已经成立。
发现私密密钥后,先撤销或轮换,再修代码。
删除代码无法收回别人已经复制的内容,也清不掉所有缓存。作者建议到对应控制台处理:Stripe 在 Developers → API keys 中 roll key;OpenAI 等服务撤销旧 key 并新建;Supabase 则按项目对应的文档轮换 JWT secret 或 service key。
接着把敏感调用迁到服务端。Lovable、Bolt 的 Supabase 项目可使用 Supabase Edge Function 和 Supabase secrets;Replit 使用 Secrets 加后端路由;Cursor / Next.js 项目使用 server route 或 server action,密钥变量不要带 NEXT_PUBLIC_。
如果泄露的是 service_role,还要修复当初促使它被加入的 RLS 问题,例如按 auth.uid() = user_id 限制用户读取自己的记录。最后检查异常调用、账单、陌生数据及 Git 历史。历史清理属于后续工作,真正让旧凭据失效的是轮换。
我的判断
这篇指南最有用的部分,是把安全边界放回正确的位置:公开配置负责连接服务,服务端权限规则决定谁能访问什么。
但「修复通常只需一个 prompt」只能视为作者的经验判断。原文没有提供项目样本、修复成功率或对照数据。把调用挪到服务端以后,接口仍需要验证调用者身份,并限制可执行的操作;否则只是换了一个可以被滥用的入口。
浏览器搜索也适合作为初筛。它能发现已经加载的文件和已经触发的请求,不能证明其他页面、延迟加载代码或尚未操作的流程都没有问题。原文对 Authorization 的判断也偏宽:请求头出现认证信息,需要继续确认它是否为必须保密的凭据。
作者最后推荐了自己开发的 ReplayWatch,并披露了利益关系。按原文描述,它扫描公开可访问的内容,不登录,也不运行注册流程。因此,扫描结果不能替代权限测试和业务验收。
能拿来做什么
- 给现有 React / Vite 或 Next.js 项目做一次线上检查,结合 Sources、HTML 和 Network 核对凭据,尤其关注
VITE_、NEXT_PUBLIC_最终带进浏览器的内容。 - 给 AI 编程助手明确边界:OpenAI、Stripe secret、
service_role等调用放到服务端,前端只调用业务接口;修复后重新发布,再检查实际产物。 - 为 Supabase 或 Firebase 补一次权限验证:退出登录后尝试读取数据,再确认普通用户无法读取其他用户的记录,避免只验证登录后的正常页面。
- 把密钥检查加入发布流程,每次 AI 大幅修改服务接入后重跑,同时人工验证注册、登录和核心业务流程。