Insights·2026-08-14

Supabase RLS — 你的应用数据库现在是否对任何人开放

用 Supabase 做的应用,密钥出现在浏览器里本来就是正常的,这个密钥是否安全只取决于一件事:每张表是否开启了 RLS(行级安全)。RLS 关闭时,任何拿到该密钥的人都能读写你的数据库。安全公司 Wiz 在 2026 年 2 月公开的 Moltbook 事件正是这种情况,浏览器 JavaScript 里的一个 publishable 密钥让大约 475 万条记录处于开放状态。自查只要三分钟:在已上线的站点里找到密钥,再到 Supabase 控制台的 Table Editor 里查看 Unrestricted 标记。本文讲清这套检查步骤、开启 RLS 后画面为何变空,以及策略该按什么顺序添加。

Supabase RLS 요약 도식. 위쪽은 「RLS 꺼짐」과 「RLS 켜짐」을 좌우로 나란히 놓았다. 꺼진 쪽에는 publishable 키를 가진 누구나 데이터베이스를 읽고 쓸 수 있다는 설명과, Moltbook이 브라우저 키 하나로 약 475만 건의 레코드를 열어 두고 있었다는 사례가 적혀 있다. 켜진 쪽에는 alter table … enable row level security 명령과 함께, 정책이 허용한 것만 나가며 정책을 하나도 만들지 않으면 아무 데이터에도 접근되지 않는다는 설명이 붙어 있다. 가운데는 점검 3단계다 — 배포된 사이트에서 F12 → Sources → supabase로 키 찾기, Table Editor에서 Unrestricted 배지 찾기, 켜고 정책 붙이기. 아래는 Supabase SQL Editor 화면으로, 테이블마다 한 줄인 alter table profiles enable row level security와, 지금 열려 있는 테이블을 rowsecurity가 false인 줄로 찾는 select tablename, rowsecurity from pg_tables where schemaname = 'public' order by rowsecurity 질의가 찍혀 있다.
왼쪽과 오른쪽을 가르는 것은 키가 아니라 RLS 한 줄이다. 켠 직후 화면이 비어 보이는 것은 고장이 아니라 정책이 아직 없는 것이다.

RLS 是什么,公开密钥为何只系于它

Supabase 的结构是浏览器直接与数据库对话。所以做完应用会得到两把密钥:一把是 publishable 密钥(旧称 anon),另一把是 secret 密钥(旧称 service_role)。前者本来就要随页面发到浏览器,后者绝不能出现在浏览器里。

Supabase 官方文档明确写道,publishable 密钥可以安全地暴露在网页、移动端或桌面应用以及源代码中。但这份安全是有条件的,因为密钥本身并不包含「谁可以看到什么」。做出这个判断的是 RLS。

RLS 全称 Row Level Security,即行级安全。它让数据库自己逐行判断「这个请求方是否可以读取这一行」。这是 Postgres 原生功能,Supabase 只是提供了开启它和编写规则的界面。

于是链条是这样的:密钥是公开的,任何人都能向数据库发起请求;如果 RLS 已开启且存在策略,只有策略允许的内容才会出去;如果 RLS 关闭,全部都会出去。中间没有别的环节。

密钥类型新名称旧名称浏览器中受 RLS 约束
公开sb_publishable_...anon正常(本就会暴露)
私密sb_secret_...service_role禁止否(BYPASSRLS)

Moltbook 是怎么被打开的

Moltbook 事件的摘要图示。标题写着数据在没有权限错误的情况下原样返回,四行列出公开密钥打开的内容:150 万个 API 认证令牌用一把密钥就能查到,3.5 万个所有者邮箱与 29,631 个预注册邮箱,以及智能体之间往来的私信。最后一行写着通报 21:48 → 修复 01:00,并注明依据 Wiz 公开的时间线(UTC)。

Moltbook 是一个 AI 智能体之间互相发帖的社交服务,上线后迅速走红。创始人在 X 上写道,他没有为它写过一行代码,只是有一个技术架构的构想,是 AI 把它变成了现实。

安全公司 Wiz 在其部署的 JavaScript 文件中找到了 Supabase 地址和 publishable 密钥。到这一步还不算事故。如前所述,那把密钥本来就应该是可见的。

问题出在下一步。研究人员用这把密钥直接向数据库发请求,数据没有任何权限报错就返回了。RLS 是关闭的。梳理表结构后发现约 475 万条记录处于开放状态,其中包括 150 万个 API 认证令牌、3.5 万个所有者邮箱、29,631 个早期注册邮箱,以及智能体之间往来的私信。

Wiz 写道:正确配置行级安全时,公开 API 密钥可以安全暴露;但没有 RLS 策略时,这把密钥会把整个数据库的访问权限交给任何持有它的人,而 Moltbook 的实现中恰恰缺少这道关键防线。按 Wiz 公布的时间线,1 月 31 日 21:48 UTC 首次通报,2 月 1 日 01:00 UTC 全部表完成加固、修复结束。

这件事的教训不是「氛围编程很危险」,而是 AI 被优化为写出能跑的代码,它不会替你决定访问权限模型。Wiz 表达的也是同一个意思。

三分钟自查之一:在已上线的站点里找密钥

先看你的应用实际向浏览器发出了什么。要打开真正部署在互联网上的地址,而不是开发中的页面。

在 Chrome 里打开该页面,按 F12(Mac 上是 Command+Option+I)打开开发者工具。切到 Sources 标签,用 Command+F 或 Ctrl+F 打开全局搜索,输入 supabase。在 Network 标签下刷新后搜索也能看到同样的内容。

如果看到项目地址和以 sb_publishable_ 或 eyJ 开头的长字符串,这是正常的。再说一遍,看到它本身不是事故。浏览器直连数据库的结构里,它根本藏不住。

真正的事故是看到以 sb_secret_ 开头的值或 service_role 这个词。这把密钥会完全绕过 RLS,策略写得再好也没用。一旦发现,要在 Supabase 控制台轮换(rotate)该密钥,并把用到它的代码挪到服务端。

三分钟自查之二:在 Table Editor 里找 Unrestricted

现在看真正的防线。登录 supabase.com,打开项目,在左侧菜单进入 Table Editor。

表列表中,RLS 关闭的表会被标记为未受保护(Unrestricted 标记)。带着这个标记的表,此刻任何持有 publishable 密钥的人都能读写。

通过控制台 Table Editor 创建的表默认开启 RLS。危险的是在 SQL Editor 里用 create table 语句创建的表。让 AI「帮我建个表」通常走的就是这条路,而 RLS 会保持关闭。这就是氛围编程做出的应用常常掉进这个坑的原因。

有多少张表就看多少张。哪怕只有一张表是开的,里面的内容就等同于公开。

开启只要一行,以及画面为何变空

打开左侧菜单的 SQL Editor,为每张表执行一行,把 profiles 换成你自己的表名。

开启后刷新应用,列表可能完全变空。不用慌,这不是故障,而是完全按规则运行的结果。正如 Supabase 文档所写,开启 RLS 后若没有任何策略,用 publishable 密钥访问不到任何数据。

数据并没有消失,它还在数据库里,只是还没有一条策略说「可以给这个请求方看」。下一节就来补上这条策略。

不要把顺序反过来。不是先写策略再开 RLS,而是先开启、全部关闭,再只打开需要的部分。这样才不会有被遗漏的表继续敞着。

SQL Editor
alter table profiles enable row level security;

写策略:从读开始

两段 RLS 策略 SQL —— 公开表用 to anon 与 using ( true ),仅本人可见的表用 auth.uid() 条件。

策略是一句话,写明允许哪种操作(select、insert、update、delete)、对谁(anon、authenticated)、在什么条件下。名字要起得日后一眼能认出来。

像公告这种任何人不登录也能看的表,写法如下。to anon 指未登录的访问者,using ( true ) 表示无条件允许。

如果是每个登录用户只看自己那几行的表,条件就不同了。auth.uid() 是当前登录用户的标识,只有它与该行的 user_id 相等时才返回这一行。

写入必须单独授权。只加 select 策略就只开放了读取,insert、update、delete 依然是关闭的。按操作分别写策略才是对的,默认关闭正是安全所在。

一个常见错误:画面空着让人着急,于是给所有表都贴上 using ( true )。那就完全回到了开启 RLS 之前的状态。目标不是让画面能显示,而是只让该出去的出去。

任何人都能读的表
create policy anyone_can_read
on notices for select
to anon
using ( true );
只看自己那几行的表
create policy own_rows_only
on profiles for select
using ( (select auth.uid()) = user_id );

怎么指挥 AI,以及今天要留下什么

这项检查可以交给 AI,但指令必须具体。只说「帮我做个安全检查」,得到的是泛泛而谈,最坏情况下它会建议一条把一切都打开的策略。

再记住一条核查用的 SQL,就不必逐张表点击,可以一次看到全貌。rowsecurity 为 false 的那几行,就是现在敞着的表。

今天要留下三样东西。第一,确认已部署的应用里看不到 secret 密钥。第二,确认 public schema 里所有表都开启了 RLS。第三,为每张表写一行备注说明为何是这条策略。第三样能防止你在下次新增表时重复同样的错误。

这些都不是做服务之前的工作,而是对已经上线的东西今天就该做的工作。Moltbook 在用户涌入的同时一直是敞开的,而修复花了几个小时。

给 AI 的指令
把我的 Supabase 项目 public schema 下所有表的 RLS 开启状态整理成表格。
对每一张关闭的表,先问我里面的数据是否可以公开,
再根据我的回答分别给出 alter table 语句和最小权限策略 SQL。
策略要把 select、insert、update、delete 分开写,
using ( true ) 只用在我回答可以公开的表上。
一眼看清 RLS 状态
select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;
来源: Wiz Research