TL;DR
「RLS Disabled in Public」(lint 0013_rls_disabled_in_public、レベルはERROR)は、公開されたスキーマのテーブルでrow level security(RLS)がオフなのに、anonまたはauthenticatedがselectできる状態を指します。私のローカルのテストでは、公開キーはそのようなテーブルの3行中3行すべてを読み、201 Createdで新しい行も挿入しました。RLSを有効にし、操作ごとにポリシーを追加し、grantを確認する作業を、1つのマイグレーションで行ってください。
私は無料のSupabaseセキュリティチェッカーを作りましたが、その最初のルール「公開されたテーブルで RLS が無効」は、SupabaseのSecurity Advisorがエラーとして報告するものと同じ指摘です。AIツールで作られたアプリで私が最もよく見つける穴なので、これが何を意味するかと、再現して何が可能だったかを説明します。
「RLS disabled in public」とは何か?
Security Advisorは、rls_disabled_in_publicというlintを実行します。そのSQLは、次の3つが同時に成り立つときにテーブルを指摘します。
- row level securityがオフの、通常のテーブルである。
anonまたはauthenticatedが、そのテーブルにSELECTを持っている。- スキーマが、Data APIのExposed schemasリストにある。デフォルトは
publicです。
lintのページは、これをERRORとし、インターネット経由での作成・読み取り・更新・削除がすべて可能な状態を「a critically unsafe configuration」(極めて危険な設定)と呼んでいます。名前の「public」はスキーマのことですが、lintは公開されているすべてのスキーマを対象にします。だから、そのリストにある余分なスキーマも確認する価値があります。これはSupabase Exposed schemasに書きました。
公開キーはそのテーブルに何ができる?
ローカルのSupabaseスタック(CLI 2.109.1、Postgres 17.6、PostgREST 14.14)に、偽の顧客3人のordersテーブルを作りました。既存プロジェクトが新しいテーブルに付けるgrantを持たせ、RLSはオフです。そのうえで、ブラウザと同じようにpublishable keyでData APIを呼び出しました。
| publishable keyでのリクエスト | RLSオフ | RLSオン、ポリシーなし |
|---|---|---|
GET /rest/v1/orders?select=* |
200、名前とメールを含む3行すべて |
200、[] |
新しい行をPOST /rest/v1/orders |
201 Created |
401、42501 “new row violates row-level security policy” |
アプリを開いた人は誰でも、ページからpublishable keyをコピーできます。RLSがオフなら、そのキーはこのテーブルへの読み書きの通行証になります。
アプリを壊さずに直すには?
RLSを有効にするだけだと、まだポリシーがないので、テーブルはアプリに何も返さなくなります。この空の画面のときに、人も、AIツールも、using (true)のポリシーを足してしまいます。それは、すべての行を全員に返します。そのあと更新や削除が、何も変えないのに成功と報告する理由はSupabase RLSを有効にする方法と結果にあります。本当のポリシーは、同じマイグレーションの中で書いてください。
alter table public.orders enable row level security;
revoke all on table public.orders from anon, authenticated;
grant select, insert on table public.orders to authenticated;
create policy "Customers read their orders"
on public.orders for select
to authenticated
using ((select auth.uid()) = user_id);
create policy "Customers create their orders"
on public.orders for insert
to authenticated
with check ((select auth.uid()) = user_id);
公開記事の一覧のように、テーブルを公開する場合でも、RLSはオンのままにして、anon向けのselectポリシーを1つ書き、書き込みのgrantは付けません。Data APIを使わず、サーバーからだけPostgresに接続するなら、Supabaseのメンテナーが同じissueで提案したように、Exposed schemasからpublicを外してください。
新しいテーブルがRLSなしで始まらないようにするには、Supabaseのevent triggersのガイドに、publicに作られるすべてのテーブルでRLSを有効にするトリガーがあります。また、2026年10月30日からは、新しいテーブルにData APIのgrantがデフォルトで付かなくなり、grantするまで42501を返します。これはSupabase 42501 permission deniedで扱いました。
問題なしという結果は、何を証明しない?
RLSがオンであることは、ポリシーが正しいかどうかについて何も教えてくれません。同じテストで、using (true)と書いたポリシーは、公開キーに再び3行すべてを返し、RLSがオンになるとこのlintは何も言わなくなります。チェッカーには、そのための別のルール「すべての行を許可するポリシー」があり、Supabase RLSのテスト方法では、実在のユーザーで各ポリシーを証明する方法を示しています。出発点にできるポリシーを、実在のユーザーでAPI経由で実行した結果とともに、Supabase RLSポリシー例:APIで検証済みにまとめました。