RLS とは何か、公開キーがなぜそれ一つに懸かるのか
Supabase はブラウザがデータベースに直接話しかける構造です。だからアプリを作るとキーが 2 本出てきます。1 本は publishable キー(旧称 anon)、もう 1 本は secret キー(旧称 service_role)です。前者はブラウザに載って出ていくのが正常で、後者は決してブラウザに置いてはいけません。
Supabase の公式ドキュメントは、publishable キーについてウェブページ・モバイルアプリ・デスクトップアプリ・ソースコードに露出しても安全だと明記しています。ただしその安全は条件付きです。キー自体には「誰が何を見てよいか」が入っていないからです。その判断を代わりに行うのが RLS です。
RLS は Row Level Security、行レベルセキュリティです。テーブルの 1 行ごとに「この要求者はこの行を読んでよいか」をデータベース自身に判定させる機能です。もともと Postgres にある機能で、Supabase はそれを有効にしてルールを書く画面を提供しているだけです。
つまり流れはこうなります。キーは公開される。誰でもデータベースに話しかけられる。RLS が有効でポリシーがあれば、ポリシーが許したものだけが出ていく。RLS が無効なら全部出ていく。その間に段はありません。
| キーの種類 | 新しい名称 | 旧名称 | ブラウザでの扱い | RLS の適用 |
|---|---|---|---|---|
| 公開用 | sb_publishable_... | anon | 正常(露出が前提) | 受ける |
| 非公開用 | sb_secret_... | service_role | 禁止 | 回避する(BYPASSRLS) |
Moltbook はどう開いていたのか

Moltbook は AI エージェント同士が投稿し合うソーシャルサービスで、公開直後から急速に広まりました。創業者は X に「コードは 1 行も書いていない。技術構成の構想があっただけで、それを現実にしたのは AI だ」と書いています。
セキュリティ企業 Wiz が、配信されている JavaScript ファイルの中に Supabase のアドレスと publishable キーを見つけました。ここまでは事故ではありません。前述のとおり、そのキーは見えているのが正常です。
問題はその次でした。研究者がそのキーでデータベースに直接リクエストを送ると、権限エラーもなくデータがそのまま返ってきました。RLS が無効だったのです。スキーマをたどると約 475 万件のレコードが開いており、その中に API 認証トークン 150 万件、所有者のメールアドレス 3 万 5 千件、事前登録者のメールアドレス 29,631 件、エージェント同士の非公開メッセージが含まれていました。
Wiz はこう書いています。行レベルセキュリティを正しく設定していれば公開 API キーは露出しても安全だが、RLS ポリシーがなければそのキーは持っている誰にでもデータベース全体へのアクセス権を与えてしまう。Moltbook の実装にはこの決定的な防御線が欠けていた。Wiz が公開したタイムラインでは、1 月 31 日 21:48 UTC に最初の通報、2 月 1 日 01:00 UTC に全テーブルの対応が終わり修正完了となっています。
この件から持ち帰るべきなのは「バイブコーディングは危険だ」ではありません。AI は動くコードを作ることに最適化されており、アクセス権の設計まで代わりに判断してはくれない、ということです。Wiz も同じ趣旨を書いています。
3 分点検その 1 — 公開中の自分のサイトでキーを探す
まず、自分のアプリが実際にブラウザへ何を送り出しているかを見ます。開発中の画面ではなく、インターネットに上がっているアドレスを開きます。
Chrome でそのページを開き、F12(Mac は Command+Option+I)で開発者ツールを開きます。Sources タブに移り、Command+F または Ctrl+F で全体検索を開いて supabase と入力します。Network タブで再読み込みしてから検索しても同じものが見えます。
ここでプロジェクトのアドレスと、sb_publishable_ または eyJ で始まる長い文字列が見えれば正常です。繰り返しますが、これが見えること自体は事故ではありません。ブラウザからデータベースを呼ぶ構造では隠しようがないからです。
事故なのは、sb_secret_ で始まる値や service_role という語が見える場合です。このキーは RLS をまるごと回避するので、ポリシーをどれだけ丁寧に書いても意味がありません。見つけたら Supabase ダッシュボードでそのキーを失効(rotate)させ、それを使っていたコードをサーバー側へ移します。
3 分点検その 2 — Table Editor で Unrestricted を探す
次に本当の防御線を見ます。supabase.com にログインしてプロジェクトを開き、左メニューの Table Editor に入ります。
テーブル一覧では、RLS が無効なテーブルに保護されていない印(Unrestricted バッジ)が付きます。この印が付いたテーブルは、今この瞬間 publishable キーを持つ誰もが読み書きできる状態です。
ダッシュボードの Table Editor で作ったテーブルは RLS が既定で有効です。危ないのは SQL Editor で create table 文を使って作ったテーブルです。AI に「テーブルを作って」と頼むとたいていこの経路になり、そのとき RLS は無効のまま残ります。バイブコーディングで作ったアプリがこの罠に頻繁にかかる理由がここにあります。
テーブルが複数あるなら全部見ます。1 つでも開いていれば、そこに入っているものは公開されているのと同じです。
有効化は 1 行、そして画面が空に見える理由
左メニューの SQL Editor を開き、テーブルごとに 1 行を実行します。profiles のところに自分のテーブル名を入れます。
有効にしてアプリを再読み込みすると、一覧がまったく空に見えることがあります。慌てなくて大丈夫です。故障ではなく、仕様どおりに動いた結果です。Supabase のドキュメントが明記しているとおり、RLS を有効にしてポリシーを 1 つも作らなければ、publishable キーではどのデータにもアクセスできません。
データが消えたわけではありません。データベースにはそのままあり、ただ「この要求者に見せてよい」と言ってくれるポリシーがまだ無いだけです。次の節でそのポリシーを足します。
順序を逆にしないでください。ポリシーを先に作って RLS を後から有効にするのではなく、有効にして全部閉じたうえで必要なものだけ開ける。この順序なら、見落としたテーブルが開いたまま残りません。
alter table profiles enable row level security;ポリシーを書く — まず読み取りから

ポリシーは「どの操作を(select・insert・update・delete)、誰に(anon・authenticated)、どんな条件で許すか」を書く一文です。名前は後で見て分かるように付けます。
お知らせのようにログインなしで誰が見てもよいテーブルなら、こう書きます。to anon はログインしていない訪問者を指し、using ( true ) は条件なしに全部許すという意味です。
ログインした人が自分の分だけを見るテーブルなら条件が変わります。auth.uid() は今ログインしている利用者の識別子で、それが行の user_id と一致するときだけその行を返します。
書き込みは読み取りとは別に許可する必要があります。select ポリシーだけを付ければ読み取りだけが開き、insert・update・delete は閉じたままです。操作ごとに別々のポリシーを書くのが正しく、既定が閉じていることが安全の要です。
よくある失敗が 1 つ。画面が空なのがもどかしくて、すべてのテーブルに 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 も 1 つ覚えておくとよいでしょう。ダッシュボードをテーブルごとにクリックせず、一覧で一度に見られます。rowsecurity が false の行が、今開いているテーブルです。
今日残すものは 3 つです。1 つ、公開中のアプリに secret キーが見えないという確認。2 つ、public スキーマのすべてのテーブルで RLS が有効だという確認。3 つ、テーブルごとになぜそのポリシーなのかを 1 行のメモに残すこと。3 つ目が、次にテーブルを足すときに同じ失敗を防いでくれます。
これはサービスを作る前にやることではなく、すでに上げてあるものに今日やることです。Moltbook は人が集まっている間ずっと開いており、直すのに数時間かかりました。
私の Supabase プロジェクトの public スキーマにある全テーブルについて、RLS の有効・無効を表にまとめて。
無効なテーブルごとに、(1) 中のデータが公開されてよいかをまず私に尋ね、
(2) 私の答えに合わせて alter table 文と最小権限のポリシー SQL を別々に提案して。
ポリシーは select・insert・update・delete をそれぞれ書き、
using ( true ) は私が公開と答えたテーブルにだけ使って。select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;