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

Cyfendo · 2026年10月20日

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

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

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

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

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

```sql
alter table public.notes enable row level security;
```

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

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

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

```sql
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の行レベルセキュリティのミス](/blog/supabase-row-level-security-mistakes)でほかにも紹介しています。

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

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

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

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

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

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

Supabaseは2種類のキーを発行します（[Supabaseのドキュメント](https://supabase.com/docs/guides/api/api-keys)）。

| キー | 形式 | ブラウザのコードに含めても安全か | 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セットアップガイド](/docs/integration/lovable)を参照してください。

Source: https://cyfendo.com/ja/blog/how-to-secure-a-lovable-app
