# v0・Lovable・Boltで作ったアプリの脆弱性 — AIアプリビルダーの落とし穴と公開前検査

> v0・Lovable・BoltなどAIアプリビルダー製で、公開済み・実ユーザーがいるアプリの脆弱性を自分の責任で潰すガイド。Lovableの実例CVE-2025-48757（RLS不備・CWE-863・CVSS9.3）を読み解き、エクスポート後のコードをscan→RLS手検査→probeで検査する手順を解説。

- 公開日: 2026-07-07
- 著者: 友田 陽大
- タグ: セキュリティ, バイブコーディング, AI駆動開発, Supabase, RLS
- URL: https://tomodahinata.com/blog/v0-lovable-bolt-ai-app-builder-security-vulnerability-guide
- カテゴリ: バイブコーディング
- 総合ガイド: https://tomodahinata.com/blog/vibe-coding-what-is-tools-risks-production-guide

## 要点

- v0・Lovable・Boltは認証とUIを自動で足すが、『その行を誰が読めるか』という行レベル認可（Supabase RLS）は生成されない、あるいは緩いまま——だから『認証はするが認可しない』アプリがそのまま公開されがち
- その現実をNVDに記録した実例がCVE-2025-48757（Lovable、2025-04-15まで、RLS不備で未認証の攻撃者が任意テーブルを読み書き、CWE-863・CVSS 9.3 CRITICAL）。ベンダーは『データ保護は各利用者の責任』としてdisputed——責任境界こそが論点
- 公開済み・実ユーザーありでも手遅れではない。エクスポート後のコードとmigrationsを scan → RLS手検査 → probe の3手で検査すれば、最頻出の穴（RLS未設定・USING(true)・IDOR）を機械的に潰せる
- anonキーがクライアントに見えるのは設計上は正常。危険なのはRLSが正しくないままanonキーが露出しているとき。service_roleキーはRLSを完全バイパスするので絶対にクライアントに出さない——守りの境界はキーではなくRLSにある
- `npx @aegiskit/cli scan` は水平の統制を自動化し、認可/IDOR・RLS設計といった縦のリスクは検出・警告にとどめる。閉じるのは人間の設計判断であって、ツールはそれを補完するのであって置き換えない

---

最初に結論を述べます。**v0・Lovable・BoltといったAIアプリビルダーは「動くアプリ」を数分で吐き出しますが、その多くは『認証はするが認可しない』——つまりログインは実装されているのに、「その行を誰が読み書きしてよいか」の境界（Supabase RLS）が空いたまま公開されています。** そしてこれは仮説ではありません。Lovable製サイトのRLS不備は、未認証の攻撃者が任意のテーブルを読み書きできる欠陥として **CVE-2025-48757（CWE-863・CVSS 9.3 CRITICAL）** にNVDへ登録されています。

私自身、一人 × 生成AI（Claude Code）で本番SaaSを作ってきた側です。だからビルダーの速さは否定しません。むしろ言いたいのは逆で——**公開済み・実ユーザーがいるなら、今日やるべきはエクスポート後のコードを「自分の責任で」検査すること**です。本記事は、その検査を `scan → RLS手検査 → probe` の3手に落とし込み、開発者の視点で実コード付きに示します。

なお、「AIが生成したコード一般の脆弱性」を4層（SCA/シークレット/SAST/DAST）で診る方法は[AI生成コードの脆弱性診断【2026年版】](/blog/ai-generated-code-vulnerability-assessment-vibe-coding-security-guide)で、発注者視点の本番化（ハードニング）は[vibe codingの本番化ガイド](/blog/vibe-coding-ai-generated-code-production-hardening-guide)で扱っています。本記事は**「ビルダー製 × 公開済み」に固有の穴**へ絞ります。

---

## 1. ビルダーは何を作り、何を作らないのか

まず、道具の守備範囲を正直に切り分けます。v0・Lovable・Boltは種類が違いますが、**「あなたのアプリ固有の認可は作らない」という一点は共通**です。

| ビルダー | 主に作るもの | 作らない/緩いままの部分 |
|---|---|---|
| v0（Vercel） | UIコンポーネント・画面 | データアクセスの認可、RLS、サーバー側の検証 |
| Lovable | フルスタック雛形＋Supabase接続 | 行レベルの所有者スコープ（RLSポリシの中身） |
| Bolt | フルスタック雛形＋DB接続 | テナント分離・IDOR対策・状態遷移の検証 |

ビルダーが自動で足してくれるのは、Aegisの言葉でいう **「水平の統制」に近い足回りと、ハッピーパスのUI・ログイン**です。ログイン画面が出て、サインアップが通り、自分のデータが表示される——ここまでは驚くほど速い。

問題は、その先の **「縦のリスク」** です。「ログインした利用者Aが、利用者BのIDを指す正規リクエストを送ったら、Bのデータが返らないか」。これは認証（誰であるか）ではなく認可（何をしてよいか）の問題で、**アプリ固有の「誰が何を所有するか」に依存する**ため、ビルダーには原理的に埋められません。オブジェクト単位の認可欠陥（IDOR/BOLA）が[OWASP API Security Top 10で2019年以来ずっと第1位](https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/)であり続けているのは、ここが「人間が設計しないと閉じない」場所だからです。

---

## 2. 実例：CVE-2025-48757（Lovable）を一次情報で読む

抽象論で終わらせないために、NVDの一次情報を引きます。CVE-2025-48757の説明は、原文でこうです。

> "An insufficient database Row-Level Security policy in Lovable through 2025-04-15 allows remote unauthenticated attackers to read or write to arbitrary database tables of generated sites."
> （Lovable（2025-04-15まで）の不十分な行レベルセキュリティポリシにより、リモートの未認証攻撃者が、生成されたサイトの任意のデータベーステーブルを読み書きできる）

要点を表に落とします（出典: [NVD — CVE-2025-48757](https://nvd.nist.gov/vuln/detail/CVE-2025-48757)）。

| 項目 | 値 |
|---|---|
| CWE | CWE-863 Incorrect Authorization（不適切な認可） |
| CVSS 3.1 | 9.3 CRITICAL（AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N） |
| 影響 | Lovable through 2025-04-15 |
| 公開日 | 2025-05-29 |
| 状態 | Disputed（ベンダーが異議） |

読み解くべきは3点です。

1. **前提条件が「未認証（PR:N）」**。つまりログインすら不要で任意テーブルに手が届いた。これはまさに「認証の外側で認可が空いている」典型で、[『認証はするが認可しない』の構造](/blog/supabase-rls-authenticated-vs-authorized-owner-scope-guide)そのものです。
2. **根本原因が「不十分なRLSポリシ」**。RLSを完全に切っていたか、`USING (true)` のように全開のポリシだったかは事案ごとに違いますが、いずれも「行を所有者に絞る」設計が欠けていた、という一点に収束します。
3. **状態が Disputed** ——ベンダーは「生成されたアプリのデータ保護は各利用者の責任だ」と主張しています。これは逃げ口上ではなく、**責任境界の宣言**として読むべきです。ビルダーは足回りを作るが、行レベルの認可は「あなたの責任」——CVEが公式に、そう言っているのです。

だからこそ、次章以降の「自分で検査する」が要になります。

---

## 3. なぜビルダー製アプリは、ここで漏れるのか

構造的な理由は3つに整理できます。

### ① 「ログインが動く」が「安全そう」を演出する

Supabase Authを繋いでサインイン画面が出た瞬間、多くの人は「認証できたから守られた」と感じます。しかし認証は**入り口の身元確認**にすぎません。入館証を配ることと、各部屋の施錠は別の仕事です。RLSという施錠が甘いと、正規の入館証（有効なログイン、時には匿名アクセス）で全部屋に入れてしまう。

### ② anonキーは「クライアントに出る前提」で配られる

Supabaseの匿名（anon）キーは、ブラウザに埋め込まれる前提の公開キーです。**それ自体は設計通りで、露出＝脆弱ではありません。** 危険なのは「RLSが正しくないまま anonキーが世界に配られている」状態です。守りの境界はキーの秘匿ではなく、**RLSポリシの中身**にあります。逆に、RLSを完全にバイパスする `service_role` キーがクライアント側やビルダーのプレビューに紛れ込んでいたら、それは一発アウト——[Supabase公式が明記する通り](https://supabase.com/docs/guides/database/postgres/row-level-security)、service_roleはPostgreSQLの `BYPASSRLS` で動き、RLSを完全に無視します。

### ③ ビルダーの既定は「まず動く」に最適化される

生成物の第一目標は「デモが動く」です。だからRLSが未設定のテーブルや、`USING (true)` の全開ポリシが、疑われないまま残りがちです。これは体感ではなく、公開Supabaseアプリの一次調査で見えている傾向です——

> [公開Supabaseアプリ1,000件の一次調査](/blog/supabase-rls-security-field-study)で、RLSを持つアプリのうち約9.2%が『認証はするが行を所有者に絞らない』ポリシを持っていた（公開リポジトリ標本ゆえ下限値。所有者非スコープ＝即脆弱ではなく要確認）。

「約1割が下限値」で、しかもこれは**セキュリティ意識が相対的に高い公開リポジトリ**の数字です。ビルダーで作って非公開のまま公開している実プロダクト群では、体感的にこれより高いと私は見ています。

---

## 4. 責任境界を正しく引く——「あなたの責任」の中身

CVE-2025-48757がDisputedであることを、ネガティブに受け取る必要はありません。むしろ**契約として明快**です。境界を図にするとこうなります。

```text
[ ビルダー/プラットフォーム側 ]        [ あなた（開発者/事業者）側 ]
  ・UI・画面の生成                       ・行レベルの認可設計（RLS の中身）
  ・Auth（サインイン）の配線             ・auth.uid() による所有者スコープ
  ・ホスティング・足回り                 ・service_role 経路の所有権チェック
  ・anon キーの発行                      ・テナント分離・IDOR 対策
  ─────────────────────                  ─────────────────────
  「動く」までを速く                     「漏れない」を担保する
```

つまり、ビルダーを使う判断そのものは正しい。**ただし「エクスポート/公開した瞬間、右列はすべてあなたに移る」**という自覚が要ります。ここを埋めるのが、次章の検査手順です。

---

## 5. エクスポート後の検査手順：scan → RLS手検査 → probe

公開済み・実ユーザーがいる前提で、**最短で最も刺さる穴から潰す**3手を示します。順番に意味があります——静的で疑い、目で認可の意味を確かめ、動的で裏を取る、です。

### Step 1. scan で「水平の穴」と「RLSの疑い」を機械的に洗う

エクスポートしたコード（またはビルダーが繋いだリポジトリ）の直下で、まず静的解析を回します。インストールも設定も不要です。

```bash
# いまのプロジェクトをそのまま静的解析する
npx @aegiskit/cli scan
```

`scan` が拾うのは、ビルダー製アプリで頻出する次のような穴です。

- **RLS未設定テーブル / `USING (true)` の全開ポリシ / `WITH CHECK` 欠落**（`supabase/migrations/**.sql` を読む）
- **秘密情報の混入**（`service_role` キーやトークンがコードに焼き込まれていないか）
- **セキュリティヘッダー / CSP・レート制限・CSRF の欠落**（＝水平の統制）
- **所有権スコープのない汚染入力＝IDOR の疑い**（source→sink のデータフローで追跡）

ここで重要なのは、Aegisが正直に線を引くことです。ヘッダーやレート制限のような **水平の統制は自動化・自動修正の対象**ですが、認可/IDOR・RLS設計のような **縦のリスクは「検出・警告」にとどめます**。直せないものを直したとは決して主張しません。だから `scan` の出力は「安全証明」ではなく「疑いのリスト」として読みます。

### Step 2. RLS を手で読む——「認証」と「認可」を分けて確かめる

`scan` が疑ったRLSは、最後は人間が**意味**を確かめます。機械はポリシの“形”を見ますが、「このテーブルは共有が意図か、所有者限定が意図か」という**事業ルールの意味**は知らないからです。読むべきは1点——**`auth.uid()` で行が所有者に絞られているか**です。

```sql
-- ❌ ありがちなビルダー既定：認証はするが、行を所有者に絞らない
--    ログインさえしていれば、他人の行も全部読める（IDOR の温床）
create policy "read" on public.orders
  for select
  using ( auth.role() = 'authenticated' );

-- ✅ 所有者スコープ：自分が所有する行だけを読める
create policy "read own" on public.orders
  for select
  using ( auth.uid() = user_id );

-- ✅ 書き込みは USING だけでなく WITH CHECK も要る（なりすまし INSERT を止める）
create policy "insert own" on public.orders
  for insert
  with check ( auth.uid() = user_id );
```

`USING (true)` や `auth.role() = 'authenticated'` が **常に悪いわけではありません**。掲示板の公開投稿のように「そのテーブルは本当に全員に見せる」意図なら正解です。だから判断基準はただ一つ——**「このテーブルは公開が意図か？」**。意図が“所有者限定”なら、`auth.uid()` によるスコープが要ります。この「認証 vs 認可」の切り分けと、`WITH CHECK` を落としたときの書き込みバイパスは、それぞれ[認証はするが認可しない問題](/blog/supabase-rls-authenticated-vs-authorized-owner-scope-guide)で詳しく扱っています（ここでは再説しません）。

あわせて、次の“ビルダー固有”のチェックも目視します。

| 見る場所 | 確認すること |
|---|---|
| `service_role` キーの置き場所 | クライアント・プレビュー・公開リポジトリに出ていないか（出ていたら即ローテート） |
| SECURITY DEFINER 関数 | `search_path` を固定しているか（未固定は権限昇格の入口） |
| Edge Function / サーバー処理 | anon経由の呼び出しでも、内部で所有権を再チェックしているか |
| RLS未設定テーブル | 「まだ作っただけ」のテーブルにこそポリシ空白が残る |

### Step 3. probe で「疑い」を実行時に裏取りする

静的解析は「疑い」、動的解析は「確証」です。最後に、**自分が所有するステージング環境**へ非破壊のプローブを送り、Step 1–2で見つけた疑いが実際に再現するかを確かめます。

```bash
# 自分のアプリ（所有物）へ、安全・スコープ固定・非破壊で裏取りする
# テストIDを渡すと missing-auth / IDOR の再現も確認できる
npx @aegiskit/cli probe https://staging.example.com --correlate
```

ここは強く念を押します——**probe を向けてよいのは、自分が所有・管理する環境だけ**です。他人のサイトへ許可なく送るのは、たとえ非破壊でも不正アクセスになり得ます。probeはlocalhost既定・スコープ固定・リクエスト予算つきで安全側に倒す設計ですが、**「所有物にのみ」**は運用者の責任です。

再現したものだけを「確定した露出」として最優先で直す——この静的×動的の相関が、ノイズに溺れず実害だけを潰す肝です。

### そして CI に固定する

一度潰した穴を再発させないために、`scan` をCIゲートに組み込みます。高信頼の検出だけがビルドを止め、SARIFがGitHubコードスキャニングに残ります。ビルダーで“また生成し直す”たびに同じ穴が開くのを、機械が止めてくれます。CIゲートの設計思想は[AI駆動開発の品質ゲート](/blog/ai-driven-development-quality-gates-ci-type-safety-test-security)に、そもそも生成前に仕様で認可を固定するやり方は[仕様駆動開発のワークフロー](/blog/spec-driven-development-claude-code-ai-agent-production-workflow)にまとめてあります。

---

## 6. 正直なスコープ——ツールが「しないこと」

最後に、油断を生まないために境界を明記します。**「scanを回したから安全」は、最悪のセキュリティ結果を生む考え方**です。

- `npx @aegiskit/cli scan` は **水平の統制を自動化**し、**縦のリスク（認可/IDOR・RLS設計・テナント分離）は検出・警告にとどめます**。閉じるのはあなたの設計判断です。
- クリーンな結果は「よくある罠は踏んでいない」であって、「このコードは安全だ」ではありません。
- データフロー解析は関数内（intraprocedural）が中心で、モジュールやフレームワークを跨ぐ流れは見逃し得ます。
- RLS設計・コードレビュー・脅威モデリング・手動ペネトレーションテストを**置き換えず、補完します**。

それでも、最頻出の穴を機械的に潰せる価値は大きい。ビルダーの速さを殺さずに、公開後でも「認証はするが認可しない」状態を体系的に閉じにいけます。まずは無料のOSSで、いまのプロジェクトを1コマンド——[Aegis（`npx @aegiskit/cli scan`）](/aegis)から始めてください。そして `scan` が炙り出した**縦のリスク**を実際に塞ぐ設計まで手が要るなら、そこは私が承ります。

## まとめ

- v0・Lovable・Boltは「動く」までを速く作るが、**行レベルの認可（RLS）はあなたの責任**。CVE-2025-48757（Lovable、CWE-863・CVSS 9.3、Disputed）が、その責任境界を公式に示している。
- 公開済み・実ユーザーありでも手遅れではない。**scan（静的で疑う）→ RLS手検査（認可の意味を確かめる）→ probe（所有物で裏取り）**の3手で、最頻出の穴を機械的に潰せる。
- **anonキーの露出は正常、危険なのはRLSが甘いまま露出していること**。service_roleキーは絶対にクライアントに出さない。守りの境界はキーではなくRLSにある。
- ツールは水平の統制を自動化し、縦のリスクは検出・警告のみ。**閉じるのは人間の設計**——ツールは置き換えず、補完する。
