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

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

- 公開日: 2026-08-06
- 著者: 友田 陽大
- タグ: バイブコーディング, セキュリティ, Supabase, データベース, 個人開発
- URL: https://tomodahinata.com/blog/supabase-rls-explained-for-non-engineers
- カテゴリ: AIで作ったアプリの運営
- 総合ガイド: https://tomodahinata.com/blog/ai-app-pre-launch-guide-for-non-engineers

## 要点

- RLSは「データベース側で、誰がどの行を見られるかを決める設定」。ログイン画面の有無とは無関係で、ログイン画面があってもRLSが無ければ全データが読める
- Supabaseの anon キーは設計上「公開されるもの」。ブラウザで読めるのは正常。異常なのは、その公開キーだけで全データが取れてしまう状態のほう
- RLSを有効にしただけでは足りない。ポリシーが USING (true) だと「全員が全行を見られる」意味になり、無効なのと結果は同じ
- 読み取り（USING）だけ書いて書き込み（WITH CHECK）を忘れるのが最頻出の抜け。他人になりすましたデータを作れてしまう
- CVE-2025-48757はまさにこの不備で CVSS 9.3 CRITICAL。ベンダーは「アプリのデータ保護は利用者の責任」と主張して異議を申し立てている

---

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

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

---

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

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

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

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

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は、**動く状態に戻すための最短の答え**を出します。

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

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

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

### 正しい条件

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

```sql
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` を他人に書き換えて、押し付けられる

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

### 正しい形

```sql
-- 作成: 自分名義でしか作れない
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. **Authentication** → **Policies** を開く
2. 各テーブルのポリシーを1つずつ見る
3. 条件（USING / WITH CHECK）に `true` だけが書かれているものを探す

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

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

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

---

## 7. AIに貼る修正指示

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

```text
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チェッカー](/aegis/rls-checker)に貼り付けると判定できます。ブラウザ内で完結するので、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で作ったアプリを公開する前に — 非エンジニアのための本番リリース全ガイド](/blog/ai-app-pre-launch-guide-for-non-engineers)から読むと、他に何を確認すべきかも分かります。
