// docs / baas security / supabase rls scanner
Supabase RLS 扫描器:发现缺失或损坏的行级安全表
当你部署一个由 Supabase 支持的应用时,行级安全 (RLS) 是站在你的客户数据和互联网之间的唯一屏障。AI 编码工具生成可以编译、发布并悄悄泄露数据的 RLS 形状代码 — 创建时未启用 RLS 的表、可读但从不限制的策略、将列与自身比较的谓词。本文展示 Supabase RLS 扫描器从外部可以证明什么、出现在 vibe 编码应用中的四种损坏 RLS 形态,以及如何在不到一分钟内扫描你自己的部署。
外部 RLS 扫描可以证明什么
A passive RLS scan looks at your Supabase project the way an anonymous visitor would. It uses only the publishable anon key — the same key your browser uses — and never authenticates as a user or touches service-role privileges. Anything it can see, an unauthenticated attacker on the internet can see.
从数据库外部,扫描器可以高置信度地确认以下内容:
- 表上 RLS 被禁用。 当 RLS 关闭或策略允许时,PostgREST 会为匿名
SELECT返回行。任一情况都是一项发现。 - 匿名角色可以列出表。 使用 anon 密钥的
GET /rest/v1/会返回anon角色拥有任何权限的每个表的 OpenAPI 模式。AI 生成的应用经常授予模式的USAGE和每个表的SELECT,即使 RLS 拒绝实际读取,也会暴露完整的模式图。 - Read access does not establish write safety. A passive scan cannot prove that INSERT, UPDATE, or DELETE policies protect every user and tenant. Review write policies and test them with isolated fixtures under the intended roles.
- 服务角色密钥在浏览器包中。 与 RLS 相邻:如果扫描器在 JavaScript 包中发现
SUPABASE_SERVICE_ROLE_KEY或任何带有role: service_role的 JWT,RLS 就形同虚设 — 该密钥持有者可绕过所有策略。
外部扫描不能证明什么
请诚实面对扫描器的边界。外部 RLS 扫描无法读取你的 pg_policies 表、迁移文件或任何策略的确切谓词。它从黑盒行为推断,这意味着它有时会报告一个最终被证明是有意公开数据 (营销新闻通讯表、公共产品目录) 的 发现。当扫描器无法消除意图歧义时,FixVibe 报告会将其标记为中等置信度 — 请审查表名并自行判断。
AI 工具产生的四种损坏 RLS 形态
When you point Cursor, Claude Code, Lovable, or Bolt at Supabase, the same four broken-RLS patterns come up again and again. Each one passes type-check, compiles, and ships:
形态 1:从未启用 RLS
The most common failure mode. The migration creates the table but the developer (or the AI tool) forgets ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST happily serves the entire table to anyone with the anon key. Fix: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;, then add a policy per command. Enabling RLS with no policies denies all access through the API; the policies decide who gets in.
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;形态 2:启用了 RLS,无策略
一种更微妙的失败。RLS 已启用,但没有编写策略。PostgreSQL 的默认是拒绝,所以已认证用户什么也看不到 — 于是开发者添加 USING (true) 以让应用工作,这允许所有人读取所有内容。修复:编写按 auth.uid() 限定范围的策略:CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); 以及匹配的 INSERT/UPDATE/DELETE 策略。
CREATE POLICY "select_own"
ON public.[name]
FOR SELECT
USING (auth.uid() = user_id);形态 3:策略将列与自身比较
一个复制粘贴的产物。开发者写 USING (user_id = user_id) — 永远为真 — 而不是 USING (auth.uid() = user_id)。类型检查通过;策略允许每一行。修复:总是将列与函数调用 (auth.uid()、auth.jwt()->>'org_id' 等) 比较,绝不与自身或常量比较。
Shape 4: Overly permissive write policies
With RLS enabled, a role with no applicable policy is denied by default. A SELECT policy does not itself allow INSERT. Write exposure can arise from a permissive applicable INSERT or ALL policy, disabled RLS, or a role that bypasses RLS. Fix: inspect table privileges and every applicable policy, use ownership-scoped WITH CHECK conditions, and test writes with separate user identities.
FixVibe Supabase RLS 扫描器的工作原理
FixVibe's Supabase check works from the deployed app, in three steps:
- Find the project. FixVibe loads the deployed app and reads the Supabase project URL and public key from the same configuration your browser receives. No guessing, no brute force.
- See what is exposed. FixVibe checks which tables the anonymous role can see, without reading row data at this step.
- Stage 3 — inspect anonymous read access. The passive check reports observable access to discovered resources. It does not create, update, or delete database rows, and it cannot establish that all write policies or cross-user boundaries are correct.
Each finding ships with the exact request URL, response status, response shape (header-only), and the table name. FixVibe now labels whether the fix belongs in code/config, a provider console, DNS, secret rotation, or manual review so the next step matches who can actually apply it.
扫描器发现问题时该怎么做
每一项 RLS 发现都是运行时紧急事件。公共 PostgREST 端点会在几分钟内被攻击者扫描。修复顺序是机械化的:
- 审计每张表。在 Supabase SQL 编辑器中运行
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';。任何rowsecurity = false的行都是问题。 - Enable RLS on every public table. Default to
ENABLE ROW LEVEL SECURITYon every table created — make it a migration template. - 逐命令编写策略。不要使用
FOR ALL USING (true)。为 SELECT、INSERT、UPDATE、DELETE 各写明确策略 — 每个都按auth.uid()或来自auth.jwt()的组织 ID 列限定范围。 - 用第二个账户验证。注册为不同用户,尝试通过 REST API 直接读取另一用户的记录。如果响应是
200,则策略已损坏。 - Re-scan. After applying the fix, re-run a FixVibe scan against the same URL. The Supabase RLS finding should clear.
-- Audit every table for missing RLS. Run in the Supabase SQL editor.
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY rowsecurity, tablename;与其他扫描器的比较
大多数通用 DAST 工具 (Burp Suite、OWASP ZAP、Nessus) 不知道 PostgREST 是什么。它们会抓取你的应用,忽略 /rest/v1/ 路径,并对它们理解的 HTML 页面进行报告。Snyk 和 Semgrep 是静态分析工具 — 它们能在你的仓库中找到缺少 RLS 调用的迁移文件,但无法证明已部署的数据库配置错误。FixVibe 处于这个空隙:被动、BaaS 感知、专注于未认证攻击者从公共 URL 可证明的内容。
常见问题
扫描器会读取或修改我的数据吗?
The passive RLS check is read-only. It examines observable anonymous read access and reports limited evidence. It does not perform database write operations or prove that every user and tenant is correctly isolated.
如果我的 Supabase 项目已暂停或使用自定义域名,这个仍能工作吗?
已暂停的项目对每个请求返回 503 — 扫描器报告项目不可达。只要已部署的应用仍在浏览器中加载 Supabase 客户端 SDK,自定义域名就能工作;无论哪种方式,扫描器都从包中提取项目 URL。
如果我的 anon 密钥被轮换或可发布密钥更改了怎么办?
重新运行扫描。扫描器在每次运行时都从当前包重新提取密钥。轮换只会使先前的报告失效,而不会影响数据库的策略状态。
扫描器会检查新的 Supabase 可发布密钥模型 (sb_publishable_*) 吗?
会。检测器识别旧的 anon JWT 和较新的 sb_publishable_* 密钥,并将它们视为相同 — 两者都旨在公开,且都将 RLS 作为唯一的防线。
后续步骤
Run a free FixVibe scan against your production URL — the Supabase RLS check runs on every plan including the free tier. For a deeper read on what else can leak from a Supabase project, see Supabase service role key exposed in JavaScript and Supabase storage bucket security checklist. For the umbrella view across all BaaS providers, read BaaS misconfiguration scanner.
