リリース前にLovableアプリを保護する方法

リリース前にLovableアプリを保護するとは、その大部分がSupabaseデータベースを保護することを意味します。すべてのテーブルで行レベルセキュリティ(RLS)を有効にし、ユーザーが自分の行にしかアクセスできないよう操作ごとにポリシーを書き、2人の異なるユーザーとしてサインインして結果をテストしてください。次に、シークレットキーをブラウザに置かないようにし、エッジ関数にはセッションから呼び出し元を特定させ、決済ステータスは決済プロバイダーのWebhookからのみ取得し、公開前にスキャンを実行してください。

1. RLSのないテーブルをすべて見つける

Lovableアプリのブラウザ側のコードは公開キーを使ってSupabaseと直接通信するため、そのキーとデータの間を隔てているのはRLSです。SupabaseのSQLエディタで、publicスキーマ内のRLSのないテーブルを一覧表示します。

select tablename
from pg_tables
where schemaname = 'public' and rowsecurity = false;

アプリを開いた人は誰でも、このクエリが返すすべてのテーブルを読み取り、変更できます。それぞれのテーブルでRLSを有効にしてください。

alter table public.notes enable row level security;

SupabaseのSecurity AdvisorとLovableの公開時スキャンも同じ内容を報告するので、どちらでもこの一覧が空であることを確認できます。

2. 操作ごとにポリシーを書く

RLSが有効でポリシーがないテーブルは、誰に対しても何も返しません。それでページが動かなくなったとき、ページを動かす最も手早い変更はすべての人を許可するポリシーであり、これはRLSの効果が失われるよくあるパターンです。代わりに、操作ごとに1つずつ、サインイン中のユーザーに紐づけたポリシーを書いてください。

create policy "Read own notes" on public.notes
  for select to authenticated using ((select auth.uid()) = user_id);

create policy "Add own notes" on public.notes
  for insert to authenticated with check ((select auth.uid()) = user_id);

create policy "Change own notes" on public.notes
  for update to authenticated
  using ((select auth.uid()) = user_id)
  with check ((select auth.uid()) = user_id);

create policy "Delete own notes" on public.notes
  for delete to authenticated using ((select auth.uid()) = user_id);

usingは、ユーザーが既存のどの行を表示または変更できるかを制御します。with checkは、ユーザーが作成できる行、または変更後に持つことになる行を制御するため、ユーザーは他人のIDでノートを挿入できません。Supabaseは、値が行ごとではなくクエリごとに1回だけ計算されるよう、auth.uid()ではなく(select auth.uid())と書くことを推奨しています。

次の2種類のポリシーを見かけたら、必ず見直してください。

  • using (true)は、テーブルを誰でも読み取れるようにします。公開済みの投稿のように本当に公開してよいデータに適していますが、selectに限ります。
  • using (auth.role() = 'authenticated')は、サインイン済みのすべてのユーザーがすべての行を読み取れるようにします。サインアップが開放されていれば、そこには誰でも含まれます。

このようなパターンは、AIで構築したアプリにおけるSupabaseの行レベルセキュリティのミスでほかにも紹介しています。

3. サインアウトした状態、ユーザーA、ユーザーBでテストする

2つのテストアカウントを作成してそれぞれにデータを入れ、次の点を確認します。

  1. サインアウトした状態(プライベートウィンドウ):非公開のデータが読み込まれないこと。
  2. ユーザーAとして:Aのデータだけが表示されること。
  3. ユーザーBとして:URLやリクエスト内のIDを書き換えても、Aのデータを読み取ったり変更したりできないこと。

公開キーだけを使ってAPIにクエリを送ることもできます。リクエストに閲覧が許可されていない行については、RLSはエラーではなく空のリストを返します。

curl "https://YOUR-PROJECT.supabase.co/rest/v1/notes?select=*" \
  -H "apikey: YOUR_PUBLISHABLE_KEY"

4. シークレットキーをブラウザに置かない

Supabaseは2種類のキーを発行します(Supabaseのドキュメント)。

キー 形式 ブラウザのコードに含めても安全か RLSに従うか
公開可能キー sb_publishable_... はい はい
レガシーのanonキー eyJで始まる長いトークン はい はい
シークレットキー sb_secret_... いいえ いいえ、RLSをバイパスします
レガシーのservice_roleキー eyJで始まる長いトークン いいえ いいえ、RLSをバイパスします

公開可能キーは公開される前提のものであり、それを安全にしているのがRLSです。シークレットキーがフロントエンドのコードに含まれていると、すべての訪問者がデータベースにフルアクセスできてしまいます。2つのレガシーキーは見た目が似ており、違いはトークン内のroleクレーム(anonまたはservice_role)です。Supabaseは、2026年末までにレガシーキーを廃止する予定です。

ビルドツールも、変数名によって何をブラウザに届けるかを決めます。VITE_(Lovableの構成)、NEXT_PUBLIC_、PUBLIC_で始まるものはすべて、フロントエンドのコードにバンドルされます。SupabaseのURLや公開可能キーならそれで問題ありませんが、Stripeのシークレットキー(sk_live_...)、AIプロバイダーのAPIキー、Webhookの署名シークレットなら漏えいです。コード内でこれらを検索し、さらに公開済みサイトのJavaScriptをブラウザの開発者ツールで開いて、そこも検索してください。

シークレットキーが一度でも露出した場合は、コードから削除した後でも置き換えてください。古いビルドやGitの履歴にはまだ残っているためです。新しいキーを作成し、すべてのサーバーと関数をそのキーに切り替え、古いキーを使っているものがないことを確認してから、古いキーを削除します。

5. エッジ関数に呼び出し元を特定させる

リクエスト本文でuser_idを受け取るエッジ関数は、受け取ったIDがどれであっても、そのユーザーとして動作します。リクエストのAuthorizationヘッダーを使ってSupabaseクライアントを作成し、supabase.auth.getUser()を呼び出し、ユーザーが返ってこなければリクエストを拒否し、返ってきたユーザーのIDを使用してください。

6. 決済ステータスは決済プロバイダーからのみ取得する

注文を支払い済みにしたりプランを解放したりするのは、プロバイダーの署名(Stripeの場合はStripe-Signatureヘッダー)を検証するWebhookハンドラー内だけにしてください。ブラウザからの成功メッセージは偽装できます。

7. ロールをユーザーメタデータに置かない

Supabaseのユーザーは自分のuser_metadataを編集できます。そこからロールを読み取るポリシーやチェックがあると、ユーザーは自分自身を昇格させられます。ロールは、サーバーだけが変更できるapp_metadataか、RLSで保護されたテーブルに保存してください。

8. 非公開ファイルは非公開バケットに保存する

公開ストレージバケット内のファイルは、リンクを知っている人なら誰でも読み取れます。非公開ファイルは非公開バケットに置き、storage.objectsに対するポリシーで各ユーザーを自分のフォルダに制限してください。

9. 公開前にスキャンする

Lovableのクイックスキャンは、公開ダイアログを開いたときに実行され、データベースのルール、依存関係の緊急レベルの脆弱性、MCPの露出を対象とします。ディープスキャンはコードベース全体をレビューしますが、Securityビューから開始したときにしか実行されないため、リリース前に開始してください。サインイン、データアクセス、決済に大きな変更を加えた後は、ステップ3と9を繰り返してください。

変更のたびにこれらの確認を実行する

CyfendoはMCPコネクタとしてLovableに接続し、サインイン、データアクセス、Supabaseのポリシー、決済、アップロード、シークレットに関わるタスクごとに確認を行います。Supabaseアプリの場合は、マイグレーションファイルから2人のテストユーザーを持つ代替データベースを構築し、ステップ3のリクエストを再実行します。再現できた問題は修正手順とともにLovableのチャットに返され、Lovableは同じリクエストでテストされたパッチをCyfendoに依頼できます。Lovableセットアップガイドを参照してください。

Cyfendoがコードから何を検出するかをご確認ください。

10,000行までのアプリは無料です。クレジットカードは不要です。