RLS とは何か、なぜ要るのか

Supabase は、データベースの表を、そのまま Web の API(プログラムから呼び出す窓口)として外に出せるサービスです。public という区画(スキーマ)にある表は、この API から読み書きできます。

アプリの画面は、publishable key(古いプロジェクトでは anon キー)という鍵を使って、この API を呼びます。この鍵は画面側に置く前提のもので、Supabase の説明でも Web ページやアプリに置いてよいとされています。その代わり、鍵を見た人は誰でも同じ API を呼べます。

そこで、誰がどの行を読めるかを決めるのが RLS(Row Level Security、行単位のアクセス制御)です。Supabase の公式ドキュメントは、外に出している区画の表にはすべて RLS を有効にするよう求めていて、RLS の無い表は、権限を持つ役割なら誰でも読み書きできると注意しています。

設定漏れは実際に起きています。

  • Lovable で作られた1,645のアプリのうち170で、RLS の設定漏れからデータが読めた問題が、CVE-2025-48757 として登録されています。
  • 2026年9月には、セキュリティ企業 UpGuard の調査で、約16,000の Supabase のデータベースで個人情報が外から読める状態だったと TechCrunch が報じました。Supabase は、守りの責任は利用者と分け合うもの(shared responsibility)だと説明しています。

なお Supabase の説明では、管理画面(Dashboard)で作った表は最初から RLS が有効になりますが、SQL エディタや別の道具で作った表は、自分で有効にする必要があります。AI が SQL で表を作った場合は、特に確かめておきたい所です。

まず鍵の種類を確かめる

鍵は Supabase の管理画面の Settings → API Keys で見られます。画面の表示は変わることがあるので、合わない場合は公式の API キーの説明を見てください。

鍵見分け方画面側に置いてよいかRLS との関係
publishable keysb_publishable_ で始まるよいRLS に従う
secret keysb_secret_ で始まるいけないRLS をすべて素通りする
anon キー(古い形式)管理画面で anon と表示される長い文字列よいRLS に従う
service_role キー(古い形式)管理画面で service_role と表示される長い文字列いけないRLS をすべて素通りする

secret key と service_role キーが、ブラウザで動くコードやスマホのアプリに入っていたら大きな問題です。新しい secret key はブラウザから使うと拒否されますが、鍵が漏れれば別の道具から使えるので、作り直します(最後の節で説明します)。

また Supabase は、古い anon キーと service_role キーを2026年末までに廃止(deprecate)する予定だと案内しています。古い鍵を使っているなら、切り替えも計画しておきます。

Security Advisor で確かめる

いちばん手軽なのは、Supabase の管理画面にある Security Advisor(セキュリティの自動チェック)です。Advisors の中にあり、URL が /advisors/security で終わるページです。結果は ERROR、WARN、INFO の3つの段階で出ます。

RLS に関係する主な項目は次のとおりです。

項目名段階意味
rls_disabled_in_publicERRORpublic の表で RLS が無効。API から読み書きできる
sensitive_columns_exposedERRORパスワードなど機密らしい列を持つ表が、RLS 無しで API に出ている
security_definer_viewERRORビュー(表を組み合わせた見え方)が RLS を素通りしている
rls_references_user_metadataERROR利用者が自分で書き換えられる情報(user_metadata)で判定している
permissive_rls_policyWARN条件が常に成り立つポリシー(USING (true) など)がある
public_bucket_allows_listingWARN公開のファイル置き場(Storage のバケット)の中身を一覧できる
policy_exists_rls_disabledINFOポリシーはあるのに RLS が無効で、ポリシーが効いていない
rls_enabled_no_policyINFORLS は有効だがポリシーが無く、API からは何も読めない

公式の説明にもあるとおり、出た項目がすべて誤りとは限りません。商品の一覧のように、誰に見せてもよい表なら、全員が読めるのは意図どおりです。大事なのは、表ごとに「誰に見せてよいか」を自分で答えられることです。

Lovable Cloud を使っている場合

Lovable の組み込みのバックエンド(Lovable Cloud)は、Supabase をもとにしています。この場合は、Lovable のプロジェクトの Security の画面でスキャンの結果を見ます。Quick scan は、RLS の抜けや広すぎる設定を調べます。ポリシーは、Lovable の説明では More → Database → Manage → Policies で見られます。画面は変わることがあるので、公式の説明も合わせて見てください。

表とポリシーを SQL で一覧にする

Supabase の管理画面の SQL Editor では、SQL(データベースへの命令文)を実行できます。次の2つは読み取るだけの命令で、データは変わりません。

1つめは、public の表ごとに RLS が有効かどうかの一覧です。

select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by tablename;

rowsecurity が false の表は、RLS が無効です。その表に何が入っているかを確かめてください。

2つめは、ポリシー(誰がどの行を扱えるかの決まり)の一覧です。

select tablename, policyname, roles, cmd, qual, with_check
from pg_policies
where schemaname = 'public'
order by tablename;

列の意味は次のとおりです。

  • roles:誰に向けた決まりか。anon はログインしていない人、authenticated はログインしている人です。
  • cmd:どの操作か。SELECT は読む、INSERT は追加、UPDATE は変更、DELETE は削除、ALL はすべてです。
  • qual:どの行を対象にするかの条件です。
  • with_check:追加や変更のときに、新しい行が満たすべき条件です。

ポリシーの読み方

よく見る条件と、その意味を並べます。

見かける形意味確かめること
roles が anon で、qual が trueログインしていない人も含め、誰でも全部の行を読める本当に公開してよいデータか
roles が authenticated で、qual が trueログインした人なら誰でも全部の行を読める他人の行まで見えてよいか
qual が auth.uid() と user_id を比べているログイン中の人と、行の持ち主が同じ行だけuser_id に正しく持ち主が入るか
qual に user_metadata が出てくる利用者が自分で書き換えられる情報で判定しているSecurity Advisor で ERROR になる書き方

auth.uid() は、問い合わせてきた人の ID を返す関数です。「ログインしていれば誰でも全部読める」形は見落としやすい所です。会員だけのサービスでも、会員どうしで他人の情報が見えてよいとは限りません。

追加と変更(INSERT、UPDATE)では with_check も見ます。ここが true のままだと、他人の user_id を付けた行も追加できます。

外から試して確かめる

一覧で問題が無さそうでも、実際に試すのが確実です。ここでの試し方は、自分のプロジェクトにだけ行ってください。

アプリの画面で試す

テスト用のアカウントを2つ作り、片方で作ったデータが、もう片方やログアウトした状態から見えないかを確かめます。手順は「AIで作ったアプリの公開前チェック」にあります。

鍵だけで API を呼んでみる

画面を通さず、publishable key だけで表を読めるかを試します。ログインしていない第三者が、画面から鍵を拾って問い合わせた場合と同じ立場です。

  1. プロジェクトの URL を、管理画面の Integrations → Data API で確かめます。https://<プロジェクトID>.supabase.co の形です。
  2. publishable key を、Settings → API Keys で確かめます。
  3. ターミナル(Mac の「ターミナル」など、コマンドを打つ画面)で、次を実行します。<> の部分は自分の値に置き換えます。
curl "https://<project-ref>.supabase.co/rest/v1/<表の名前>?select=*&limit=1" \
  -H "apikey: <publishable key>"

limit=1 は、返ってくる行を1件までにする指定です。結果は次のように読みます。

  • [](空の一覧):ログインしていない人には、その表の行が1件も見えていません。RLS で隠れているか、表が空です。
  • データが返る:ログインしていない人でも、その表を読めます。公開してよいデータでなければ、すぐに直します。
  • エラーが返る:鍵や表の名前の誤りか、その表が API から届かない設定になっています。

Supabase の説明では、publishable key は apikey という欄(ヘッダー)で送ります。publishable key はログインしていない人(anon)の立場で動き、古い anon キーはその前身なので、アプリが古い鍵を使っていても、この確認で足ります。

見つかったときの直し方

RLS を有効にして、ポリシーを作る

RLS が無効の表は、次の命令で有効にします。

alter table public.<表の名前> enable row level security;

RLS を有効にしてポリシーが無いと、API からは何も読めなくなります。アプリの画面が空になることがあるので、続けてポリシーを作ります。次は「ログインした人が、自分の行だけ読める」ポリシーの例で、Supabase の公式の例と同じ形です。

create policy "自分の行だけ読める"
on public.todos for select
to authenticated
using ( (select auth.uid()) = user_id );

AI に直させるときは、表ごとに「誰が、どの操作をしてよいか」を書いた表を渡すと、意図とずれにくくなります。

ビューとファイル置き場

ビューは、標準では RLS を素通りします。Postgres 15 以降なら、ビューに security_invoker = true を付けると、元の表の RLS が効きます。ファイル置き場(Storage)は、Security Advisor の public_bucket_allows_listing が出ていないかを見ます。

鍵や個人情報が漏れていた場合

secret key や service_role キーが外に出ていた場合は、漏れた原因を直してから鍵を作り直します。手順は Supabase の「漏れた API キーの作り直し」の説明に沿ってください。古い service_role キーの場合は、古い鍵の無効化に加えて、JWT secret の取り消しまで必要です。

個人情報が外から読める期間があった場合、報告や本人への連絡が要るかどうかは、個人情報保護委員会の「漏えい等の対応」の案内を確かめ、弁護士に相談してください。

直したら、Security Advisor と外からの確認をもう一度行います。判断に迷う所は、経験のある人に一度見てもらうと早いです。