FixVibe

// docs / baas security / supabase rls scanner

Supabase RLS 扫描器:发现缺失或损坏的行级安全表

当你部署一个由 Supabase 支持的应用时,行级安全 (RLS) 是站在你的客户数据和互联网之间的唯一屏障。AI 编码工具生成可以编译、发布并悄悄泄露数据的 RLS 形状代码 — 创建时未启用 RLS 的表、可读但从不限制的策略、将列与自身比较的谓词。本文展示 Supabase RLS 扫描器从外部可以证明什么、出现在 vibe 编码应用中的四种损坏 RLS 形态,以及如何在不到一分钟内扫描你自己的部署。

外部 RLS 扫描可以证明什么

被动 RLS 扫描针对 Supabase 在 https://[project].supabase.co/rest/v1/ 暴露的 PostgREST 端点运行。它只使用可发布的 anon 密钥 — 与你浏览器使用的相同密钥 — 并探测表列表元数据、匿名读取和匿名写入。它从不以用户身份进行身份验证,也从不接触服务角色权限。它能做的任何事,互联网上的未认证攻击者都能做。

从数据库外部,扫描器可以高置信度地确认以下内容:

  • 表上 RLS 被禁用。 当 RLS 关闭或策略允许时,PostgREST 会为匿名 SELECT 返回行。任一情况都是一项发现。
  • 匿名角色可以列出表。 使用 anon 密钥的 GET /rest/v1/ 会返回 anon 角色拥有任何权限的每个表的 OpenAPI 模式。AI 生成的应用经常授予模式的 USAGE 和每个表的 SELECT,即使 RLS 拒绝实际读取,也会暴露完整的模式图。
  • 匿名角色可以插入。 如果 RLS 没有拒绝 INSERT 的策略,猜测列形状的探测性 POST 将成功 — 即使 SELECT 已被锁定。
  • 服务角色密钥在浏览器包中。 与 RLS 相邻:如果扫描器在 JavaScript 包中发现 SUPABASE_SERVICE_ROLE_KEY 或任何带有 role: service_role 的 JWT,RLS 就形同虚设 — 该密钥持有者可绕过所有策略。

外部扫描不能证明什么

请诚实面对扫描器的边界。外部 RLS 扫描无法读取你的 pg_policies 表、迁移文件或任何策略的确切谓词。它从黑盒行为推断,这意味着它有时会报告一个最终被证明是有意公开数据 (营销新闻通讯表、公共产品目录) 的 发现。当扫描器无法消除意图歧义时,FixVibe 报告会将其标记为中等置信度 — 请审查表名并自行判断。

AI 工具产生的四种损坏 RLS 形态

当你将 Cursor、Claude Code、Lovable 或 Bolt 指向 Supabase 时,数千个应用中会出现相同的四种损坏 RLS 模式。每一种都通过类型检查、编译并发布:

形态 1:从未启用 RLS

最常见的失败模式。迁移创建了表,但开发者 (或 AI 工具) 忘了 ALTER TABLE ... ENABLE ROW LEVEL SECURITY。PostgREST 愉快地将整张表提供给任何拥有 anon 密钥的人。修复: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;。FORCE 是必需的 — 没有它,表所有者 (以及任何拥有表所有权的角色) 会绕过 RLS。

sql
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE  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 策略。

sql
CREATE POLICY "select_own"
  ON public.[name]
  FOR SELECT
  USING (auth.uid() = user_id);

形态 3:策略将列与自身比较

A copy-paste artefact. The developer writes <code>USING (user_id = user_id)</code> — which is always true — instead of <code>USING (auth.uid() = user_id)</code>. Type-checks pass; the policy permits every row. <strong>Fix:</strong> always compare a column to a function call (<code>auth.uid()</code>, <code>auth.jwt()->>'org_id'</code>, etc.), never to itself or to a constant.

形态 4:SELECT 上有策略但 INSERT/UPDATE 上没有

开发者锁定了读取,但忘记了写入。RLS 策略是按命令的。FOR SELECT 只保护读取;如果没有策略拒绝,匿名客户端仍可 INSERT修复:为每个命令编写策略,或使用带有明确 USINGWITH CHECK 子句的 FOR ALL

FixVibe Supabase RLS 扫描器的工作原理

baas.supabase-rls 检查分三个阶段运行,每个阶段都有明确的置信度级别:

  1. 阶段 1 — 指纹识别。扫描器抓取已部署的应用,解析其 JavaScript 包,并从运行时配置中提取 Supabase 项目 URL 和 anon 密钥。无 DNS 猜测,无暴力破解 — 它读取浏览器读取的内容。
  2. 阶段 2 — 模式发现。使用 anon 密钥的单次 GET /rest/v1/ 返回 anon 角色能看到的每个表的 OpenAPI 模式。此阶段扫描器记录表名,但不读取行数据。
  3. 阶段 3 — 读写探测。对每个发现的表,扫描器发出一次带有 limit=1 的匿名 SELECT。如果返回行,则 RLS 是允许的。扫描器就此停止 — 不枚举行,不分页,不修改数据。INSERT 探测被验证的域名所有权和明确的选择加入所限制;它们从不针对未验证的目标触发。

每项发现都附带确切的请求 URL、响应状态、响应形状 (仅标头) 和表名。发现底部的 AI 修复提示是一个可复制粘贴的 SQL 块,你可以在 Supabase SQL 编辑器中运行。

扫描器发现问题时该怎么做

每一项 RLS 发现都是运行时紧急事件。公共 PostgREST 端点会在几分钟内被攻击者扫描。修复顺序是机械化的:

  1. 审计每张表。在 Supabase SQL 编辑器中运行 SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';。任何 rowsecurity = false 的行都是问题。
  2. 在每个公共表上启用 RLS。对创建的每个表默认使用 ENABLE ROW LEVEL SECURITYFORCE ROW LEVEL SECURITY — 使其成为迁移模板。
  3. 逐命令编写策略。不要使用 FOR ALL USING (true)。为 SELECT、INSERT、UPDATE、DELETE 各写明确策略 — 每个都按 auth.uid() 或来自 auth.jwt() 的组织 ID 列限定范围。
  4. 用第二个账户验证。注册为不同用户,尝试通过 REST API 直接读取另一用户的记录。如果响应是 200,则策略已损坏。
  5. 重新扫描。应用修复后,对同一 URL 重新运行 FixVibe 扫描。baas.supabase-rls 发现应被清除。
sql
-- 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 页面进行报告。SnykSemgrep 是静态分析工具 — 它们能在你的仓库中找到缺少 RLS 调用的迁移文件,但无法证明已部署的数据库配置错误。FixVibe 处于这个空隙:被动、BaaS 感知、专注于未认证攻击者从公共 URL 可证明的内容。

常见问题

扫描器会读取或修改我的数据吗?

不会。被动扫描对每个发现的表最多发出一次 SELECT ... limit=1,以确认 RLS 是否允许匿名读取。扫描器记录响应形状,而非行内容。INSERT、UPDATE 和 DELETE 探测被验证的域名所有权所限制,并且永远不针对未验证的目标运行。

如果我的 Supabase 项目已暂停或使用自定义域名,这个仍能工作吗?

已暂停的项目对每个请求返回 503 — 扫描器报告项目不可达。只要已部署的应用仍在浏览器中加载 Supabase 客户端 SDK,自定义域名就能工作;无论哪种方式,扫描器都从包中提取项目 URL。

如果我的 anon 密钥被轮换或可发布密钥更改了怎么办?

重新运行扫描。扫描器在每次运行时都从当前包重新提取密钥。轮换只会使先前的报告失效,而不会影响数据库的策略状态。

扫描器会检查新的 Supabase 可发布密钥模型 (<code>sb_publishable_*</code>) 吗?

会。检测器识别旧的 anon JWT 和较新的 sb_publishable_* 密钥,并将它们视为相同 — 两者都旨在公开,且都将 RLS 作为唯一的防线。

后续步骤

对你的生产 URL 运行免费的 FixVibe 扫描 — baas.supabase-rls 检查在每个套餐 (包括免费层) 上都已启用。如需深入了解 Supabase 项目还可能泄露什么,请参阅 暴露在 JavaScript 中的 Supabase 服务角色密钥Supabase 存储桶安全清单。对于所有 BaaS 提供商的总览,请阅读 BaaS 配置错误扫描器

// 扫描你的 baas 面

在其他人之前找到开放的表。

输入一个生产 URL。FixVibe 会列举你的应用通信的 BaaS 提供商,识别它们的公共端点,并报告未经身份验证的客户端可以读取或写入什么。免费,无需安装,无需信用卡。

  • 免费层 — 每月 3 次扫描,注册无需信用卡。
  • 被动 BaaS 指纹识别 — 无需域名所有权验证。
  • Supabase、Firebase、Clerk、Auth0、Appwrite 等。
  • 每项发现都附带 AI 修复提示 — 可粘贴回 Cursor / Claude Code。
Supabase RLS 扫描器:发现缺失或损坏的行级安全表 — Docs · FixVibe