Zum Hauptinhalt springen
友田 陽大
Running the app you built with AI
バイブコーディング
セキュリティ
Supabase
データベース
個人開発

Supabaseの「RLS」って何? — AIが作ったアプリでデータが全公開になる仕組みを、コードなしで理解する

「ChatGPTにRLSを有効にしろと言われたけど意味が分からない」——そんな方向けに、RLS(行レベルセキュリティ)とは何か、なぜ設定しないと全ユーザーのデータが誰でも読めるのかを、コードを読まずに理解できるよう解説します。確認手順とAIに貼れる修正指示つき。

Published
Reading time
10 min read
Author
友田 陽大
Teilen

「ChatGPTに聞いたら『RLSを有効にしろ』と言われた。でも意味も、やり方も分からない」

この記事は、その状態から抜け出すためのものです。コードは読めなくて構いません。仕組みが分かれば、あとはAIに指示を出せば直せます。


1. RLS とは何か — 表計算で考える

RLS(Row Level Security、行レベルセキュリティ)は「データベース側で、誰がどの行を見られるかを決める設定」です。

「行」というのは、表計算ソフトの1行だと思ってください。たとえば、あなたのアプリに「メモ」という表があるとします。

iduser_id内容
1Aさん銀行の暗証番号のメモ
2Aさん買い物リスト
3Bさん転職活動のメモ

RLS が設定されていれば、Aさんがログインしているとき、データベースは 1 と 2 の行しか返しません。3 は存在しないかのように扱われます。

RLS が設定されていなければ、誰が問い合わせても3行すべてが返ります

ポイントは、この判定が データベース側で行われる ことです。アプリのコードに「Aさんの分だけ表示する」と書くのとは、防御の階層が違います。


2. なぜログイン画面があっても守られないのか

ここがいちばん直感に反する部分です。

「ログインしないと使えないアプリなんだから、ログインしていない人はデータを見られないはずでは?」——そう思うのが自然です。

実際に起きていること

Supabase のようなサービスでは、ブラウザからデータベースへ直接問い合わせが飛びます。あなたのアプリのコードを経由せず、です。

【あなたが想定している流れ】
ユーザー → ログイン画面 → あなたのアプリ → データベース

【実際にあり得る流れ】
攻撃者 ──────────────────────────────→ データベース

なぜ直接飛ばせるかというと、接続に必要な情報が どちらも公開されているから です。

  • データベースのURL — ブラウザの開発者ツールで見える
  • anon(匿名)キー — 同上

そして、これは異常ではありませんanon キーは、設計上「公開されるもの」です。公開されることを前提に作られています。

では何が安全性を担保しているのか。それがRLSです。 anon キーは「データベースに話しかける権利」でしかなく、「何を見られるか」はRLSが決める——そういう設計になっています。

だからRLSが無いと、公開キーだけで全部が取れてしまう。鍵が漏れたのではなく、最初から鍵をかけていなかったというのが正確な表現です。

例えるなら

  • ログイン画面 = 建物の玄関の鍵
  • RLS = 各部屋の金庫の鍵

玄関に鍵をかけても、窓が開いていて、しかも金庫に鍵がかかっていなければ、中身は持っていかれます。Supabase の構成では「窓」が最初から開いている(それが正常な設計)ので、金庫の鍵が必須になります。


3. これは実際に起きている — CVE-2025-48757

これは理論上の話ではありません。

AI アプリビルダーの Lovable で生成されたアプリについて、次の内容の脆弱性が CVE として登録されています。

生成されたサイトのデータベースの行レベルセキュリティポリシーが不十分で、認証されていない遠隔の攻撃者が任意のデータベーステーブルを読み書きできる

深刻度は CVSS 9.3 CRITICAL。10点満点で9.3、最も深刻な部類です。

注目すべきは、ベンダーの反論

この CVE には、ベンダー側から異議申し立てが出ています。その主張の内容が重要です。

ベンダーの立場は「アプリのデータ保護は利用者の責任である」というものでした。

これは責任逃れというより、事実として正しい指摘です。そしてそれが意味するのは——あなたが作ったアプリのデータを守る責任は、あなたにあるということです。

「AIツールが自動でやってくれているはず」という期待は、公式に否定されています。ここは自分で確認するしかありません。


4. 有効にしただけでは足りない — USING (true) の罠

RLS を有効にすると、多くの場合アプリが動かなくなります

これは正常です。RLS は「ポリシー(許可の条件)が1つも無ければ、全部拒否」という設計だからです。有効にした瞬間、データが1行も返ってこなくなります。

ここで起きるのが、この記事でいちばん伝えたい事故です。

アプリが動かなくなって焦り、AIに「データが取れなくなった、直して」と頼む。するとAIは、動く状態に戻すための最短の答えを出します。

-- AI が「動くようにする」ために書きがちな条件
CREATE POLICY "..." ON メモ FOR SELECT USING (true);

USING (true) は「常に許可」という意味です。つまり「全員が全行を見てよい」。アプリは動きます。そしてセキュリティ上は、RLSを無効にしていたのと結果が同じです。

RLS が「有効」と表示されているので、確認したつもりになってしまう。これが最も危険なパターンです。

正しい条件

本人に絞るには、こう書きます。

CREATE POLICY "自分のメモだけ読める" ON メモ
  FOR SELECT
  USING ( (select auth.uid()) = user_id );

auth.uid() は「いま問い合わせている人のID」です。それが行の user_id と一致するときだけ許可する——これが「本人の行だけ」の意味になります。

(select auth.uid()) という書き方をしているのは性能上の理由です。裸の auth.uid() だと行ごとに評価されてしまい、行数が増えたときに極端に遅くなります。Supabase の公式ドキュメントでも推奨されている書き方です。


5. 最頻出の抜け — 書き込みを忘れる

読み取りの制限を書いて、書き込みの制限を忘れる。これが実務でいちばんよく見る抜けです。

  • USING → 「どの行を 見られる/触れる か」
  • WITH CHECK → 「どんな値の行を 作れる/変更後の値にできる か」

USING だけ書いた状態だと、こうなります。

  • 他人の user_id を指定したメモを、勝手に作れる
  • 自分のメモの user_id を他人に書き換えて、押し付けられる

どちらも「読めない」だけで「書ける」ので、動作確認では気づきません。

正しい形

-- 作成: 自分名義でしか作れない
CREATE POLICY "自分のメモだけ作れる" ON メモ
  FOR INSERT
  WITH CHECK ( (select auth.uid()) = user_id );

-- 更新: 自分の行だけ触れて、かつ自分名義のまま
CREATE POLICY "自分のメモだけ更新できる" ON メモ
  FOR UPDATE
  USING ( (select auth.uid()) = user_id )
  WITH CHECK ( (select auth.uid()) = user_id );

UPDATE に両方必要なのは、「どの行を触れるか」と「どんな値に変えられるか」が別の問いだからです。片方だけだと、自分の行を他人のものに書き換えられます。


6. 自分で確認する手順

コードを読まずにできる確認です。

手順1: 管理画面で有効/無効を見る

  1. Supabase の管理画面にログイン
  2. 左メニューの Table Editor を開く
  3. テーブル一覧を見る

「RLS disabled」「Unrestricted」といった警告バッジが出ているテーブルがあれば、それは全公開のテーブルです。

手順2: ポリシーの中身を見る

  1. AuthenticationPolicies を開く
  2. 各テーブルのポリシーを1つずつ見る
  3. 条件(USING / WITH CHECK)に true だけが書かれているものを探す

true だけのポリシーがあれば、それは制限していません。

手順3: 書き込みのポリシーがあるか数える

SELECT のポリシーはあるのに INSERT / UPDATE のポリシーが無い、という状態を探します。無い場合、その操作は拒否されているか(正常)、別のポリシーで緩く許可されているか(危険)のどちらかです。


7. AIに貼る修正指示

ここまでの確認をAIに任せる場合は、これをそのまま貼ってください。

Supabase のマイグレーション/SQL を全部読んで、次を表にして報告してください。
(1) RLS が有効になっていないテーブル
(2) RLS は有効だがポリシーが1つも無いテーブル
(3) ポリシーの条件が USING (true) など実質的に全員を許可しているテーブル
(4) SELECT のポリシーはあるが INSERT / UPDATE の WITH CHECK が無いテーブル

そのうえで、各テーブルについて「ログインした本人の行だけ」に絞る
RLS ポリシーの SQL を書いてください。次の条件を守ってください。

- 性能のため auth.uid() は (select auth.uid()) の形で書く
- INSERT と UPDATE には WITH CHECK を必ず付ける
- UPDATE には USING と WITH CHECK の両方を付け、その理由を SQL の
  コメントとして書く
- service_role キーを使って RLS を迂回している箇所があれば、
  それも一覧にする

私は SQL が読めないので、各ポリシーが「誰に何を許可しているか」を
日本語で1行ずつ説明してください。

書いたポリシーが本当に絞れているかは、無料のRLSチェッカーに貼り付けると判定できます。ブラウザ内で完結するので、SQLがどこかに送信されることはありません。


8. よくある勘違い

「テーブルを非公開にすればRLSは要らない」 Supabase の API は、公開スキーマのテーブルを自動的に公開します。「非公開にする」設定は別途必要で、かつアプリから使うテーブルは結局公開する必要があります。RLSの代わりにはなりません。

「service_role キーを使えば楽」 確かに動きます。RLSを全部素通りするからです。しかしそのキーがブラウザ側に出た瞬間、全データが誰でも読み書きできます。AI が生成したコードでこのキーが使われがちなのは、まさに「エラーが出ないから」です。楽な道が最も危険、という典型例です。

「自分しか使わないアプリだから大丈夫」 本当に自分しか使わず、URLも公開していないなら、リスクは限定的です。ただし「あとで人に見せるかも」と思っているなら、そのときに設定する保証はありません。今やるほうが安上がりです。

「ユーザーが増えてから考える」 RLSはあとから足せますが、すでに漏れたデータは戻りません。そしてRLSを後付けすると、既存のクエリがまとめて動かなくなるので、結局その場で USING (true) を書く羽目になりがちです。


まとめ

  • RLS は「データベース側で、誰がどの行を見られるかを決める設定」
  • Supabase ではブラウザから直接データベースに問い合わせが飛ぶので、ログイン画面は防御にならない。anon キーが公開されているのは正常で、RLSが無いことのほうが異常
  • 有効にするだけでは足りない。USING (true) は無効と結果が同じ
  • 読み取り(USING)だけでなく書き込み(WITH CHECK)も必要
  • 責任の所在は、ツールベンダー自身が「利用者にある」と明言している

RLS は、AIで作ったアプリで最も事故が多い場所です。逆に言えば、ここさえ押さえれば最大のリスクは消えます。設定自体は難しくありません——忘れられるだけです。

まだ全体像を掴めていない方は、AIで作ったアプリを公開する前に — 非エンジニアのための本番リリース全ガイドから読むと、他に何を確認すべきかも分かります。

Häufige Fragen

RLS を一言で言うと何ですか?
「データベース側で、誰がどの行を見られるかを決める設定」です。表計算ソフトの1行1行に対して「この行はAさんだけ」「この行はBさんだけ」と鍵をかけるイメージです。アプリのコード側ではなくデータベース側で判定するため、どの経路から問い合わせが来ても同じ制限がかかります。
ログイン画面があるのに、なぜデータが全部見えるのですか?
ログイン画面はブラウザ側の話で、データベースはそれを知らないからです。Supabaseではブラウザからデータベースへ直接問い合わせが飛びます。このとき使う anon キーは公開情報なので、攻撃者はログイン画面を一切通らずに問い合わせできます。ログイン画面は玄関の鍵で、RLSは金庫の鍵です。玄関を通らず窓から入られたら、玄関の鍵は意味がありません。
anon キーがブラウザから見えるのは問題ですか?
問題ありません。anon キーは設計上「公開されるもの」で、ブラウザで読めるのは正常な状態です。危険なのは service_role キーのほうで、これはすべての制限を無視できるため、ブラウザ側に置くと RLS が完全に無意味になります。つまり anon キーが見えること自体ではなく、RLSが無いことが問題です。
RLS を有効にすればもう安全ですか?
有効にしただけでは足りません。RLSは「ポリシー(許可の条件)が1つも無ければ全部拒否」なので有効化した瞬間はアプリが動かなくなり、そこで慌てて USING (true) という条件を書いてしまうケースが非常に多いです。これは「全員が全行を見てよい」という意味で、無効なのと結果は同じになります。条件は auth.uid() = user_id のように本人に絞る必要があります。
読み取りだけ制限すれば十分ですか?
不十分です。読み取りの制限(USING)を書いて書き込みの制限(WITH CHECK)を忘れるのが最も多い抜けで、この状態だと他人のIDを指定したデータを作ったり、自分のデータを他人のものに書き換えたりできてしまいます。UPDATE には USING と WITH CHECK の両方が必要です。前者が「どの行を触れるか」、後者が「どんな値に変えられるか」を決めます。

Quellen

友田

友田 陽大

Entwickler eines mit dem METI-Ministerpreis ausgezeichneten Produkts. Mit TypeScript + Python + AWS liefere ich SaaS, Industrie-Digitalisierung und produktionsreife generative KI (RAG) durchgängig — von der Anforderung über die Infrastruktur bis zum Betrieb, allein.

“It works. But is it safe to launch?”

Twenty questions, no signup — a pre-launch self-check for the app you built with AI

You don't need to read any code. Answer yes / no / not sure to questions like “can only the person who logged in see their own data?” You get your risks ordered by severity, plus a fix instruction you can paste straight into Claude Code or Cursor. Nothing you type leaves your browser.

Verfügbar für Projektarbeit (Werkvertrag) und als technischer Berater. Beginnen Sie mit einem kostenlosen 30-minütigen Gespräch.

最短ルート:カレンダーから直接予約

相談内容が固まっている方は、フォーム送信よりその場で日程を確定する方がスムーズです。下記から空き時間をお選びください。

  • 30分のオンライン無料相談
  • Google Meet / Zoom / Microsoft Teams
  • NDA 商談前締結可・無理な営業はいたしません
無料相談の空き枠を予約する

Ebenfalls lesenswert