Vibe Coding安全清单:12项必做检查

针对Vibe-Coded应用的12项安全自查清单,57%可测数据库无需登录即可读取,漏洞根源在配置而非代码。
一次对约3.1万个AI辅助构建应用的扫描发现,57%可测试的Supabase数据库无需登录即可读取。文章以Moltbook等真实泄露事故为例,指出Vibe-Coded应用的安全漏洞主要来自三类出厂配置错误:开放的数据库、打包进浏览器的密钥、不校验记录归属的路由,而非AI生成代码本身的质量问题。为此,文章提供了一份12项检查清单,覆盖git历史密钥扫描、前端bundle密钥检测、Supabase RLS验证、越权访问测试、依赖漏洞与虚假包检查、限流及响应头审查等关键环节,其中6项直接对线上URL或打包产物发起请求。文章还说明了如何将关键检查项集成进CI流水线,以及如何通过AGENTS.md向编码Agent注入持久安全规则,实现预防与侦测的双层防护。
当AI替你写代码,谁来替你守安全?
一次针对约3.1万个用Lovable、Bolt、v0和Replit构建的线上应用的自动化扫描给出了触目惊心的结果:在能够测试到的Supabase数据库中,有57%可以在无需登录的情况下被任何人直接读取(数据来源:Reeve, 2026年8月)。这些数据库默认对整个互联网开放,而没有人把这个开关关掉。
问题的核心不在于AI写的代码本身有多糟。一项2026年3月的研究对比了真实代码库中数万个AI生成和人工编写的代码单元,发现AI代码触发的扫描告警反而略少,硬编码密钥也更少(Mao et al., 2026年3月)。真正的漏洞来自应用“出厂时”携带的配置——一个开放的数据库、一个被打包进浏览器的密钥、一条从不校验调用者身份的路由。这三类都是可以用命令测出来的配置错误。

为什么Vibe-Coded应用会漏数据
今年最清晰的反面案例是Moltbook。这是一个在1月下旬上线的AI社交网络,创始人声称自己“没写过一行代码”。上线四天内,Wiz的研究人员就在网站的JavaScript里找到了数据库密钥,而行级安全(RLS)处于关闭状态——任何人都能读写整个数据库。结果约150万个API令牌和数万个邮箱地址被暴露(Wiz, 2026年2月)。
同月,一个被Lovable放在自家Discover页面推荐的考试平台泄露了18697条用户记录。它的登录校验逻辑写反了,数据库没有行级安全,也没有任何地方校验用户角色。发现该漏洞的研究员称主要缺陷是“一个人类安全审查者几秒钟就能发现的经典逻辑反转”(The Register, 2026年2月)。
这些事故都指向同一个根因:过去要把应用送到用户面前,你必须自己配置认证,因为不配好什么都跑不起来;AI工具省掉了这一步,于是应用就运行在默认设置上——而新数据库的默认设置就是“开放”。
如何使用这份清单
建议在首个真实注册发生前运行全部12项,在接受第一笔付款前再跑一次,上线后每月一次。命令假定你用的是Next.js App Router项目、npm和Unix shell(Windows用户用Git Bash或WSL)。其中检查3到5是Supabase专属的,因为Supabase正是大多数Vibe-Coding工具交给你的默认后端。
一个关键认知贯穿全文:本文列举的大多数损害都发生在源代码之外——数据库设置、落入部署文件的密钥、回应错误用户的路由。只读你代码仓库的扫描器看不到这些。因此12项检查中有6项是直接对打包产物或线上URL发起请求,而代码扫描被放在最后。先安装四个扫描器:
brew install gitleaks trufflehog osv-scanner semgrep
密钥:三个最常泄露的地方(检查1–3)
密钥通常从三个地方漏出:git历史、发往浏览器的JavaScript、以及错误类型的数据库密钥。GitGuardian的2026年报告发现,用Claude Code写的提交泄露密钥的概率约是普通公开提交的两倍(GitGuardian, 2026年3月),而且大多数多年前泄露的密钥至今仍然有效,因为没人轮换。所以本节每项检查的修复第一步都一样:先轮换密钥,再清理现场。
检查1:git历史里有没有密钥?
gitleaks git . --redact -v
trufflehog git file://. --results=verified --fail
失败标志:gitleaks打印出发现项并以退出码1结束。trufflehog那一行会把结果收窄到供应商确认仍然有效的密钥。轮换一个密钥只需几分钟,而重写历史删除它约需一小时——轮换更重要,因为公开提交里的密钥早已被复制。
检查2:有密钥被打包进浏览器了吗?
npm run build && grep -rIoE \
-e 'sb_secret_[A-Za-z0-9_-]+' -e 'sk_(live|test)_[A-Za-z0-9]+' \
-e 'sk-[A-Za-z0-9_-]{20,}' -e 'postgres(ql)?://[^:/]+:[^@]+@' \
.next/static | sort -u
失败标志:任何内容被打印出来。Next.js会在构建时把所有以NEXT_PUBLIC_开头的变量以明文形式复制进浏览器包。所以当有人为了消掉一个“undefined”错误,给STRIPE_SECRET_KEY加上这个前缀时,密钥就公开了。本文作者正是这样让一个Stripe密钥在.next/static里明文躺了九天。
检查3:你发出去的是哪种Supabase密钥?
Supabase有两种密钥。可发布密钥(旧称anon)面向浏览器,受行级安全限制;而secret密钥(旧称service_role)完全无视行级安全,一旦打包发出,等于把整个数据库拱手送人。用grep扫描bundle,并解码任何JWT令牌的中间段,查看是否出现"role":"service_role"。
JWT(JSON Web Token)是一种由三段Base64编码组成的令牌,格式为header.payload.signature。中间段(payload)包含明文声明,只需在浏览器控制台运行atob('中间段字符串')即可解码,无需任何密钥。Supabase的两种密钥都是JWT:可发布密钥的payload包含"role":"anon",service_role密钥包含"role":"service_role"。后者在Supabase内部会触发一个特殊的超级用户路径,完全绕过所有RLS策略——这不是配置失误,而是设计如此,目的是让服务器端管理脚本能不受限制地操作数据。因此它只应出现在服务器环境变量里,绝不能出现在任何发往浏览器的文件中。
数据库:它真的关上了吗?(检查4–5)
在Supabase应用里,浏览器直接与数据库对话,所以数据库必须自己决定谁能读哪一行。这项工作由行级安全(RLS)负责,而它在你用SQL创建的每张表上默认是关闭的。开头那57%测量的正是这种开放的表。
检查4:哪些表的RLS是关的?
select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;
失败标志:任何行显示rowsecurity = f。随后打开Supabase仪表盘的Security Advisor,优先处理0013_rls_disabled_in_public、0002_auth_users_exposed和0024_permissive_rls_policy(即USING (true)的策略)。
检查5:从外部证明它
curl "https://abcdefghijkl.supabase.co/rest/v1/orders?select=*&limit=5" \
-H "apikey: sb_publishable_AbC123..."
失败标志:真实数据行被打印出来。空数组或代码42501的错误对象都说明RLS在工作。这正是当初发现Moltbook数据库、以及Reeve扫描中那些开放数据库所用的同一个请求。
行级安全(Row-Level Security,RLS)是PostgreSQL 9.5引入的功能,允许数据库在每一次数据读写时自动附加一层过滤条件。以Supabase为例,当浏览器直接通过PostgREST API查询数据库时,没有中间服务器层可以拦截请求、验证归属——RLS是唯一的防线。启用RLS后,表默认变为"拒绝所有"状态,必须显式创建策略才能放行。一条典型的用户只能读自己数据的策略写法是:CREATE POLICY "users_own_rows" ON orders USING (auth.uid() = user_id);。USING (true)则意味着无条件放行所有行,等同于没有开启RLS,这正是检查4里0024_permissive_rls_policy告警所捕捉的情形。
授权:这条记录是谁的?(检查6–7)
失效的访问控制高居OWASP Top 10:2025榜首。说白了,应用校验了“你已登录”,然后不检查记录归属就把记录交给你。这正是今年两起Lovable事故的共同缺陷。
检查6:有多少路由处理器从不校验授权?
find app src/app -name 'route.*' 2>/dev/null | wc -l
grep -rlE 'auth\(\)|protect\(|getSession\(|getClaims\(|getUser\(' \
--include='route.*' app src/app 2>/dev/null | wc -l
失败标志:第二个数字比第一个小得超出合理范围。注意Clerk的clerkMiddleware()本身并不保护任何东西,官方文档明确建议把访问校验放在“尽可能靠近资源的地方”。
检查7:用户A能读到用户B的记录吗?
不用终端的版本很简单:以用户A登录,打开属于你的订单或设置页,然后把地址栏里的id换成用户B的记录id。如果页面照常加载,你就失败了。终端版本则是带着用户A的访问令牌,用curl请求用户B的记录。
curl -si 'https://app.example.com/api/orders/9f2c' \
-H 'cookie: __session=eyJhbGciOi...' | head -1
失败标志:请求对用户B的id返回200。你期望看到的是空数组、403或404。修复方法是给每个查询加上归属条件,例如where: { id, organizationId }。这是AI工具最常跳过的修复,因为缺它的时候表面上什么都没坏。
依赖项:已知漏洞与虚假包(检查8–9)
你的package.json可能藏着两类问题:有已知漏洞的包,以及从未真实存在过的包。
检查8:你发布的东西里有已知漏洞吗?
npm audit --audit-level=high --omit=dev
osv-scanner scan source -r .
--omit=dev(即旧的--production)能把构建工具的噪声排除出报告。osv-scanner则能捕捉npm audit看不到的——其他生态的包和npm尚未镜像的公告。
检查9:每个依赖都是真实的包吗?
AI模型会凭空捏造包名,而且会一遍遍捏造同样的错误名字。USENIX Security 2025的一项研究发现,商业模型约每20次就会编造一个包名,开源模型约每5次一个(Spracklen et al., 2025)。攻击者抢注这些虚构的名字守株待兔——这种手法叫slopsquatting。2026年2月,npm就把unused-imports(模型想写eslint-plugin-unused-imports时产生的名字)置于安全封禁状态。CI里加上npm ci --ignore-scripts,下一个假包就无法在安装时运行。
供应链攻击是指攻击者不直接攻击目标应用,而是污染目标所依赖的上游组件。slopsquatting是其中一种新型变体:攻击者监控AI模型反复生成的拼写错误或捏造包名,提前在npm等注册表上注册这些名字,并在其中植入恶意代码(如窃取环境变量、建立反向Shell)。由于AI工具的用户往往直接运行npm install而不核查包名来源,恶意包的安装过程对用户完全透明。npm ci --ignore-scripts能阻止包在安装时执行preinstall/postinstall钩子——绝大多数安装阶段的恶意代码正是通过这些钩子运行的,而合法包极少需要安装脚本。
限流、响应头与代码扫描(检查10–12)
检查10:有任何东西被限流吗?
for i in $(seq 1 40); do
curl -s -o /dev/null -w "%{http_code} " \
'https://app.example.com/api/generate' \
-H 'content-type: application/json' -d '{"prompt":"hi"}'
done; echo
失败标志:40次响应全部返回且没有一个是429。务必对预览部署而非生产环境运行。
检查11:你的主机还在对外提供什么?
探测/.env、/.git/config等点文件是否返回200,检查HSTS、CSP、X-Frame-Options等安全响应头是否存在,以及.next/static里是否残留.map源映射文件。源映射会把你未压缩的代码全盘交出——Reeve扫描中约每八个Vibe-Coded应用就有一个在提供源映射。
检查12:跑一遍代码扫描器
semgrep scan --config p/owasp-top-ten --config p/secrets --error .
代码扫描之所以放在最后,正是因为它看不到前面11项里真正发生过事故的那些配置问题。
让你的编码Agent替你跑
12项里有10项是shell命令,所以一个带终端的编码Agent(Claude Code、Cursor、Codex)可以替你跑完并出报告。有两条规则能保证安全:给Agent预览URL而非生产环境,因为检查7会读真实数据、检查10会写40次请求;让Agent保持只读,因为能编辑的Agent有时会通过“削弱检查本身”来“修复”发现项。
AGENTS.md(有时也写作CLAUDE.md或.cursor/rules)是放在项目根目录、专门为编码Agent设置持久指令的文件。Agent每次启动新会话时会读取它,相当于给模型注入永久性的项目规范。把安全规则写进这里而非聊天消息,能避免规则在长对话中被"遗忘"——大语言模型的注意力在极长上下文中对早期内容的权重会下降。典型的安全规则示例:NEVER disable RLS on any table、NEVER prefix secret keys with NEXT_PUBLIC_、ALWAYS add ownership filter (user_id = auth.uid()) to queries。这些规则是减少新漏洞产生的预防层,而CI里的检查命令是发现已有漏洞的侦测层,两者互补而非替代。
把检查放进CI
跑一次清单只能抓住今天的问题。放进CI意味着它们也会在AInext月提交的每一行代码上运行。其中三项现在就该进CI:每次push跑gitleaks,每个PR跑npm audit和Semgrep,安装步骤用npm ci --ignore-scripts。四项线上检查(5、7、10、11)每月手动约二十分钟即可,因为它们捕捉的是从未经过提交的配置变更。
AGENTS.md里的“绝不”规则(绝不关RLS、绝不硬编码密钥、绝不跳过归属校验)能减少发现项数量;而把检查包裹在模型外的“护栏”则保证闸门无论模型是否记得规则都会运行。规模化的Vibe-Coded应用两者都需要。
相关推荐

Orca多Agent编排实战:Claude与Codex并行协作指南
Orca是Stable AI开源的免费Agent工作台,支持Claude Code、Codex、Kimi等34个agent并行运行。本文介绍其Git worktree隔离机制、多模型编排分工方案(强模型拆解+便宜模型编码)、手机远程管理功能及实操上手步骤,帮助你高效管理多个AI编程助手。

思维链不止属于AI:人类如何用CoT提升思考质量
思维链(Chain of Thought)不只是大模型的提示词技巧。本文从Hacker News讨论出发,分析CoT为何对人类思考同样有效,拆解可操作的迁移方法,并指出人类CoT与机器CoT的关键差异。
Cloudflare联手OpenAI:用AI打通漏洞发现与防御闭环
Cloudflare联手OpenAI:用AI打通漏洞发现与防御闭环
Cloudflare 使用 OpenAI Daybreak 调查安全漏洞并生成可评审的修复方案,开发者同时借助 Codex 提升编码效率与代码质量,探索漏洞发现到防御响应的一体化路径。