大多数”帮我审一下 RLS”的 Prompt,回的都是”记得开行级安全”这种废话。真正会泄数据的 bug 更隐蔽:UPDATE 漏了 WITH CHECK、service_role key 借一个辅助函数绕过 RLS、auth.uid() 比错了列。这不是空谈。2025 年 5 月公布的 CVE-2025-48757(CVSS 9.3)正是因为表对未鉴权请求 + 公开 anon key 可读,导致 170 个用 Lovable 搭的应用、303 个接口被暴露。下面 15 个 Prompt 各打一类具体的 RLS 失败模式,让 AI 审出肉眼一扫而过会漏掉的东西。
TL;DR
- 先跑 Prompt 1。RLS 没开,后面所有策略都是摆设——CVE-2025-48757 钻的就是这个空子。
- 收益最高的两个检查是
WITH CHECK覆盖(Prompt 4)和service_role审计(Prompt 5)。真正的泄漏多半在这里,而不在人人都看的USING子句。 - 把这些喂给强推理模型。截至 2026 年 6 月,Claude Opus 4.7(SWE-bench Verified 87.6%)和 GPT-5.5 最擅长揪 SQL 信任边界 bug;粘真实的
\dschema dump 和策略 DDL,别粘摘要。 - AI 这一遍要配上 Supabase 免费的 Security Advisor,它会自动标
rls_disabled_in_public(lint0013)。 - 性能和安全一起修:把与行无关的调用写成
(select auth.uid())触发 initPlan,并给每个和它比较的列建索引。
适合哪些人
公开上线前要冲一遍的 Supabase 独立创业者、审 schema PR 的 reviewer、准备渗透测试材料的安全工程师。如果你的 schema 是 AI 搭建工具(Lovable、Bolt、v0)生成的,这套审查不是可选项,是必做项。
这些情况别用
带 auth.uid() 和 storage.objects 的 Prompt 默认你用的是 Supabase Auth 和 Supabase Storage,对纯自建 Postgres、或故意关掉 RLS 的原型不适用。
每个 RLS 审查 Prompt 都该带什么
想让 Prompt 回出有用的发现(而不是”建议开启 RLS”),得带齐这六样:
| 要素 | 你要给模型的内容 |
|---|---|
| 角色 | Supabase 安全 reviewer / SQL 信任边界审计员 |
| 上下文 | schema dump(\d 或 pg_dump --schema-only)、策略 DDL、框架 + Supabase 版本 |
| 目标 | 一个交付物:发现清单表、migration 计划,或红队测试种子 |
| 限制 | 不许悄悄重写、不许编造列名、不确定就标出来 |
| 输出格式 | 编号清单、markdown 矩阵,或 unified SQL diff |
| 信号 | 给一个真实 bug 类型示例,让模型知道”坏”长什么样 |
最佳用途:上线前 RLS 审计、schema migration PR review、事故后硬化、service_role 使用审计、Storage 桶访问审查。
15 个可直接复制的 Prompt 模板
1. RLS 启用覆盖检查
第一个跑。RLS 开关一漏,它下面的策略全失效。
You are a Supabase security reviewer. Audit the schema dump below. For every table in `public`: (1) Is RLS enabled? (2) Does it have at least one policy? (3) Is FORCE ROW LEVEL SECURITY enabled (relevant for table-owner access)? Output a table: schema.table | RLS enabled | policy count | force RLS | risk.
这对应 Supabase 自带的 rls_disabled_in_public linter(lint 0013):任何没开 RLS 的 public 表,只要拿到项目 URL 和 anon key 就能任意增删改查。
2. auth.uid() 比较正确性
Review every policy USING / WITH CHECK clause that references `auth.uid()`. For each: (1) Is it compared against the column that actually holds the owner (e.g., user_id, owner_id, profile_id)? (2) Could the comparison silently pass for nulls? (3) Are joined tables also constrained, or is the JOIN unprotected? List findings: policy name | issue | fix sketch.
3. INSERT / UPDATE / DELETE 覆盖
For each table with RLS enabled, list which commands have policies: SELECT, INSERT, UPDATE, DELETE. Flag tables missing policies for any command. The default is deny, but partial policies leave gaps (e.g., UPDATE without WITH CHECK lets a user move a row to another owner). Output as a matrix.
4. WITH CHECK vs USING 审计
这套里收益最高的一个 Prompt。
Review every UPDATE and INSERT policy. For each: (1) Does it have BOTH USING (pre-image) and WITH CHECK (post-image)? (2) On UPDATE, does WITH CHECK stop reassigning ownership to another user? (3) On INSERT, does WITH CHECK stop inserting on behalf of another user? List violations with file:line.
5. service_role 绕过审计
Find every usage of the `service_role` key in the codebase (server functions, edge functions, migrations, background jobs). For each: (1) Is it strictly necessary, or could `authenticated` work? (2) Is the call wrapped in a function that re-checks ownership? (3) Are any service_role queries reachable from user input without validation? File:line + severity.
service_role 直接绕过 RLS。每条用到它的路径都是特权面,所以这个审计找出的真泄漏,往往比策略文本本身还多。
6. Storage 桶 RLS 审查
Audit Supabase Storage bucket policies. For each bucket: (1) public vs private flag, (2) SELECT/INSERT/UPDATE/DELETE policies on `storage.objects` filtered to this bucket, (3) Does the path convention encode ownership (e.g., a user-id prefix) and does the policy enforce that prefix via storage.foldername? (4) Are signed URLs used where private buckets need temporary access? Output: bucket | policies | risks.
7. SECURITY DEFINER 函数审计
List every Postgres function marked SECURITY DEFINER (including any Supabase generated). For each: (1) Does it set `search_path` explicitly (e.g., `set search_path = ''`) to prevent search_path hijacking? (2) Does it re-validate auth.uid() if it bypasses RLS? (3) Is it callable by anon? (4) Are the GRANTs correct? Findings with severity.
SECURITY DEFINER 函数以 owner 身份运行,能悄悄绕过 RLS。Supabase 的 function_search_path_mutable advisor 就是用来标”没设 search_path”这种情况的。
8. Realtime 订阅策略审计
Audit Supabase Realtime publication and policies: (1) Which tables are in the `supabase_realtime` publication? (2) Do those tables have SELECT policies that constrain rows visible per user? Realtime respects RLS, so a loose SELECT policy leaks broadcasts. (3) Are any sensitive columns published when they should not be? List risks.
9. 多租户隔离审查
This app is multi-tenant via a tenant column (default tenant_id). Audit: (1) Every tenant-scoped table has the tenant column and an RLS policy comparing it to the user's tenant claim, (2) JWT claim extraction (auth.jwt() ->> 'tenant_id' or similar) is consistent across policies, (3) Cross-tenant reads via JOINs are blocked. Output: table | tenant policy | leak risk.
可替换变量: 租户列名,例如 tenant_id、org_id 或 workspace_id。
10. 角色策略审计
If this schema uses role columns (admin, member, viewer), audit role policies: (1) Are roles read from a profile table or a JWT claim? Pick one; mixing is a footgun. (2) Do admin-only mutations check role inside the policy, or only in the app? (3) Can a user escalate their own role via UPDATE on the profiles table? List findings.
11. Migration 策略 diff review
在 migration PR 上用。
Below is a migration diff. For RLS changes: (1) Any policy dropped without replacement? (2) Any new table missing RLS? (3) Any column type change that breaks an existing policy comparison? (4) Are policy statements idempotent (CREATE OR REPLACE / DROP IF EXISTS)? Output PR-ready review comments.
可替换变量: migration diff 文本。
12. anon vs authenticated 拆分审查
For every policy, classify which role it applies to (anon, authenticated, service_role). Flag: (1) policies applied to anon that should be authenticated only, (2) policies applied to authenticated when only specific roles should access, (3) policies with no role filter (default = all roles).
13. 策略性能审查
Review RLS policies for performance: (1) Policies that call a function per-row instead of inlining or caching the auth check, (2) Policies that JOIN large tables, which become per-row subqueries, (3) Missing index on the column compared against auth.uid(), (4) auth.uid() invoked multiple times per policy. Suggest specific indexes and rewrites using the (select auth.uid()) initPlan pattern.
性能和安全在这里交汇。把与行无关的调用写成 (select auth.uid()),Postgres 就能每条语句只算一次(initPlan),而不是每行算一次;再给被比较的列建索引,能把百万行表上”超时”的全表扫描压到几毫秒。
14. RLS 漏洞 → 红队场景
Take the policies below and write 5 red-team scenarios. Each is a JWT + a SQL query + the expected (denied) result. Then say which of the 5 would actually succeed against the current policies and why. Use this as a regression-test seed.
15. RLS 发现 → 修复计划
最后跑。把发现转成可部署的 migration 序列。
Take all RLS findings above and group them into ordered migrations. For each migration: (1) Title, (2) SQL diff (DROP / CREATE / ALTER), (3) Pre-deploy check (count rows that would now be denied, as a sanity check), (4) Rollback. Mark which migrations need a maintenance window.
该用哪个模型来跑
截至 2026 年 6 月,把真实 schema 和策略 DDL 粘进高推理模型,别用聊天默认档。Claude Opus 4.7 以 87.6% 领跑 SWE-bench Verified,在 SQL 信任边界推理上一向很稳;GPT-5.5(Thinking 模式)是稳妥的第二选择,在长终端类任务上略胜。想要便宜的第二意见,可以用 Gemini 3.1 Pro,价格是每百万 token $2/$12。三者都带 1M token 上下文,所以一次能塞下完整 pg_dump --schema-only 加上 edge function 源码。工作流那一侧,建议在 Cursor 或 Claude Code 里跑,让模型能直接打开真正的 migration 文件。
select 包裹改写,讲具体点
(select auth.uid()) 这个写法是最值得记住的一处改写。裸写 auth.uid() = user_id 会对每个候选行都重新算一次函数;写成 (select auth.uid()) = user_id 会触发 Postgres 的 initPlan,每条语句只算一次并缓存结果,因为这个值不依赖行数据。Supabase 的性能 advisor 会把没包裹的写法标在 auth_rls_initplan(lint 0003)下。再配上 user_id 上的索引:在大表上,给被比较的列建索引据报能比全表扫描快 100 倍以上。只对”跨行结果恒定”的调用用这个包裹。
容易踩的坑
- 只看
USING不看WITH CHECK——UPDATE/INSERT泄漏悄悄溜过去。 - “内部代码”无脑信
service_role不重新检查所有权——每条service_role路径都是特权面。 auth.uid()比错列(id而不是user_id)——零行结果把 bug 掩盖了。- 开了 RLS 但忘了
FORCE ROW LEVEL SECURITY——表 owner 查询会绕过策略。 - Realtime 发布的表
SELECT策略松——泄漏面成倍放大。 - 混用角色查询来源(profile 表 vs JWT claim)——改角色时有竞态。
SECURITY DEFINER函数没显式设search_path——留下劫持入口。- RLS PR 跳过 migration review——被删的策略看着像”加了安全”,其实是回归。
进一步压榨效果
- 永远先跑 Prompt 1。RLS 没开,后面所有审计都没意义,linter
0013也是这么判的。 - 强制 reviewer 写一个红队场景(Prompt 14),能挖出静态 review 漏掉的。
- 多租户应用里,所有策略的
auth.jwt()claim 提取写法要完全一致。 - 把与行无关的调用包成
(select auth.uid()),并给被比较的列建索引。 - 策略 review 配着
service_role审计一起跑,大多数泄漏在那里。 - 每次 migration PR 后重跑,RLS 状态会悄悄漂移。
- 把策略快照存到
/supabase/policies.snapshot.sql,长期看 diff。
常见问题
- 不写应用代码怎么测策略?:静态 review 覆盖结构。运行时用
select set_config('request.jwt.claims', '...', true)加set role authenticated在 SQL editor 里模拟用户,再跑查询、核对行数。 - AI 能直接帮我生成策略吗?:能出不错的初稿,但每条生成的策略都要跑这套 review。最常漏的就是
WITH CHECK和角色过滤——CVE-2025-48757 钻的正是这个空子。 - 应用已经做了鉴权,还需要 RLS 吗?:需要。只要有人绕到数据库直连(PostgREST、SQL editor、anon key 泄漏),应用层鉴权就立刻失守。RLS 是最后一道。
- 开了 RLS 后 SELECT 零行怎么办?:默认 deny。至少要一个匹配该用户行的
SELECT策略。跑 Prompt 3 找缺哪条命令。完整修法见 Supabase RLS 挡住数据。 - service_role 调用都要重新检查所有权吗?:只要接收用户提供的标识符,就要。在调用里重新检查,或用
SECURITY INVOKER函数包一层让 RLS 仍然生效。 - 怎么防止用 UPDATE profiles 提权?:加列级限制,或用 trigger 拒绝
authenticated用户改role字段。Prompt 10 覆盖了这点。