Supabase RLS 策略审查 Prompt:鉴权与角色模板

15 个 Supabase 行级安全审查 Prompt:auth.uid() 检查、WITH CHECK 覆盖、service_role 绕过、Storage 桶 RLS、SECURITY DEFINER,以及 (select auth.uid()) 性能修复。

大多数”帮我审一下 RLS”的 Prompt,回的都是”记得开行级安全”这种废话。真正会泄数据的 bug 更隐蔽:UPDATE 漏了 WITH CHECKservice_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;粘真实的 \d schema dump 和策略 DDL,别粘摘要。
  • AI 这一遍要配上 Supabase 免费的 Security Advisor,它会自动标 rls_disabled_in_public(lint 0013)。
  • 性能和安全一起修:把与行无关的调用写成 (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(\dpg_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_idorg_idworkspace_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 源码。工作流那一侧,建议在 CursorClaude 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 覆盖了这点。

相关阅读

标签: #Prompt #编程 #Supabase #安全