Перейти к основному содержимому
友田 陽大
Vibe coding: shipping AI-generated code safely
セキュリティ
バイブコーディング
AI駆動開発
Supabase
RLS

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

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

Published
Reading time
13 min read
Author
友田 陽大
Поделиться

最初に結論を述べます。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年版】で、発注者視点の本番化(ハードニング)はvibe codingの本番化ガイドで扱っています。本記事は**「ビルダー製 × 公開済み」に固有の穴**へ絞ります。


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位であり続けているのは、ここが「人間が設計しないと閉じない」場所だからです。


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)。

項目
CWECWE-863 Incorrect Authorization(不適切な認可)
CVSS 3.19.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)」。つまりログインすら不要で任意テーブルに手が届いた。これはまさに「認証の外側で認可が空いている」典型で、『認証はするが認可しない』の構造そのものです。
  2. 根本原因が「不十分なRLSポリシ」。RLSを完全に切っていたか、USING (true) のように全開のポリシだったかは事案ごとに違いますが、いずれも「行を所有者に絞る」設計が欠けていた、という一点に収束します。
  3. 状態が Disputed ——ベンダーは「生成されたアプリのデータ保護は各利用者の責任だ」と主張しています。これは逃げ口上ではなく、責任境界の宣言として読むべきです。ビルダーは足回りを作るが、行レベルの認可は「あなたの責任」——CVEが公式に、そう言っているのです。

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


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

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

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

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

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

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

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

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

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

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


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

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

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

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


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

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

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

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

# いまのプロジェクトをそのまま静的解析する
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() で行が所有者に絞られているかです。

-- ❌ ありがちなビルダー既定:認証はするが、行を所有者に絞らない
--    ログインさえしていれば、他人の行も全部読める(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 を落としたときの書き込みバイパスは、それぞれ認証はするが認可しない問題で詳しく扱っています(ここでは再説しません)。

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

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

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

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

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

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

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

そして CI に固定する

一度潰した穴を再発させないために、scan をCIゲートに組み込みます。高信頼の検出だけがビルドを止め、SARIFがGitHubコードスキャニングに残ります。ビルダーで“また生成し直す”たびに同じ穴が開くのを、機械が止めてくれます。CIゲートの設計思想はAI駆動開発の品質ゲートに、そもそも生成前に仕様で認可を固定するやり方は仕様駆動開発のワークフローにまとめてあります。


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

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

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

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

まとめ

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

Частые вопросы

v0・Lovable・Boltで作ったアプリは、そのまま公開して安全ですか?
いいえ。ビルダーは認証(ログイン)とUIは自動で足しますが、『その行を誰が読めるか』という行レベルの認可(Supabase RLS)はあなたの責任です。実際、Lovable製サイトのRLS不備でCVE-2025-48757(CVSS 9.3)が登録されました。公開済みでも、エクスポート後のコードを検査すれば穴は潰せます。
CVE-2025-48757はどんな脆弱性ですか?
Lovable(2025-04-15まで)で生成されたサイトの行レベルセキュリティ(RLS)ポリシが不十分で、未認証の攻撃者が任意のテーブルを読み書きできたというものです。CWE-863(不適切な認可)、CVSS 9.3 CRITICAL。ベンダーは『データ保護は各利用者の責任』として異議を唱えており(disputed)、まさに責任境界が論点になっています。
すでに公開してユーザーがいます。今から何をすべきですか?
まずエクスポート済み、または接続済みのコードとSupabaseのmigrationsを検査します。`npx @aegiskit/cli scan` でRLS未設定・USING(true)・秘密情報混入を洗い出し、次にauth.uid()で所有者に絞れているかを手で確認、最後に自分が所有するステージングへ probe で実行時に裏取り。この3手で最頻出の穴を機械的に潰せます。
anonキーがクライアントに見えていますが、危険ですか?
anon(公開)キーはブラウザに出る前提の設計で、それ自体は問題ではありません。危険なのは、RLSが正しくないままanonキーが露出しているときです。守りの境界はキーではなくRLSポリシにあります。逆にservice_roleキーはRLSを完全にバイパスするため、絶対にクライアントへ出してはいけません。
Aegisを入れれば完全に安全になりますか?
いいえ。そう謳うツールはむしろ危険です。`npx @aegiskit/cli scan` は水平の統制(ヘッダー・レート制限・秘密情報衛生)を自動化し、認可/IDOR・RLS設計といった縦のリスクは検出・警告にとどめます。閉じるのはあなたの設計判断であって、ツールはそれを置き換えず補完するだけです。

Источники

友田

友田 陽大

Разработчик продукта, отмеченного премией Министра (METI). На TypeScript + Python + AWS создаю SaaS, цифровизацию промышленности и готовый к продакшену генеративный ИИ (RAG) — от требований до инфраструктуры и эксплуатации, в одиночку.

Already live, with real users?

A security audit of your AI-generated app — authorization, RLS, tenant boundaries

An app that's live with real users needs more than “it works.” The vertical risks in authorization and RLS design that no tool can fix — I take them on as an audit, from review through fix design and implementation. Feel free to visualize the current state with the free OSS first.

Доступен как для проектной работы, так и для технического консультирования. Начните с бесплатной 30-минутной консультации.

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

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

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

Также стоит прочитать