TL;DR
anon keyは、現在publishable keyと呼ばれ、公開される前提のキーです。anonまたはauthenticatedロールに対応し、何が見えるかはRLSポリシーが決めます。service role keyは、現在secret keyと呼ばれ、service_roleに対応し、RLSを完全に迂回します。私のローカルのテストテーブルでは、RLSがオンでポリシーがないとき、公開キーは0行、secret keyは3行すべてを取りました。publishable keyはブラウザに置き、secret keyはサーバーに置き、一度でも公開されたならローテーションしてください。
Denysです。フルスタック・AIエンジニアです。Supabaseのアプリを引き継ぐとき、私が最初に探すのは漏えいしたsecret keyです。それがあれば、ほかの保護はすべて飾りになるからです。この記事では、各キーが何を開くのかを、確認のために実行したリクエストとともに説明します。
Supabaseのanon keyとは?
anon keyが識別するのは、ユーザーではなくあなたのアプリです。ブラウザやモバイルアプリからのリクエストはすべてこれを持つので、公開されている必要があります。ページのソースから誰でも読めます。このキーだけのリクエストは、Postgresのロールanonとして実行されます。ユーザーがSupabase Authでサインインすると、同じキーがユーザーのトークンと一緒に送られ、リクエストはauthenticatedとして実行されます。RLSポリシーは、この2つのロール向けに書きます。
Supabaseはキーの名前を変えています。API keysガイドでは、新しいキーをpublishable(sb_publishable_…)とsecret(sb_secret_…)と呼び、JWTベースの従来のanonとservice_roleは2026年末までに非推奨になります。その裏にあるロールは変わりません。
| キー | 従来の名前 | Postgresのロール | RLSは適用される? | 置き場所 |
|---|---|---|---|---|
Publishable sb_publishable_… |
anon key |
anon。ユーザートークンがあればauthenticated |
はい | ブラウザ、モバイルアプリ、配布するものすべて |
Secret sb_secret_… |
service_role key |
service_role |
いいえ。このロールはBYPASSRLSを持つ |
サーバー、Edge Function、cronジョブだけ |
Supabaseのanon keyを公開しても安全?
はい。ただし、そのキーが届くすべてのテーブル、ビュー、関数が保護されているならです。キー自体は特別なものを何も与えません。すべてはスキーマにあるgrantとポリシーで決まります。ローカルのSupabaseスタック(CLI 2.109.1、Postgres 17.6)に、偽の注文3行のテーブルを1つ置いて確認しました。
| テーブルの状態 | Publishable key | Secret key |
|---|---|---|
| RLSオフ | 3行すべて。INSERTは201 Createdを返した |
3行すべて |
| RLSオン、ポリシーなし | [] |
3行すべて |
RLSオン、ポリシーusing (true) |
3行すべて | 3行すべて |
anonへのgrantなし |
42501 permission denied。足りないGRANTを示すヒント付き |
3行すべて |
つまり、publishable keyの安全性は、いちばん弱いテーブルと同じです。RLSのないテーブルや、すべての行を許可するポリシーのあるテーブルは、DevToolsを開いた誰にでも読めます。これがCVE-2025-48757の背景にある穴で、Lovableのショーケースアプリ1,645件のうち170件が公開キーにデータを返していました。
service role keyとは何で、なぜ危険?
secret keyはマスターキーです。BYPASSRLS属性を持つservice_roleとして実行されるため、あなたが書いたポリシーは一切適用されません。上の表の最後の列では、どの状態でも3行すべてを読み取りました。Supabaseのガイドは、置いてよい場所を挙げています。自前で認可チェックをするサーバー、Edge Function、バックグラウンドジョブ、管理ツールです。ガイドにある2つの細部は、実務で重要です。
- 新しいsecret keyは、ブラウザからのリクエストを拒否します。Supabaseは
User-Agentを見て401を返します。従来のservice_roleJWTにはこの確認がないので、漏えいした従来のキーはどこからでも使えます。ゲートウェイでのこの確認がない私のローカルスタックは、secret keyを使ったスクリプトに3行すべてを返しました。 - secret keyがRLSを迂回するのは、リクエストにユーザーのトークンが付いていないときだけです。サインイン中のユーザーのトークンがあれば、リクエストはそのユーザーのポリシーのもとで実行されます。
secret keyを使うEdge Functionは、誰が呼んでいるかの確認と、所有者や会社での絞り込みを自分で行う必要があります。その下には、それをしてくれるものが何もないからです。
Supabaseのservice role keyはどこにある?
Dashboardでは、プロジェクトのConnectダイアログにURLとpublishable keyがあり、従来のキーを含むすべてのキーはSettings → API Keysにあります。ローカルでは、supabase startとsupabase statusがpublishable keyとsecret keyを表示します。キーは環境変数に置いてください。Next.jsでは、NEXT_PUBLIC_プレフィックスを付けてよいのはpublishable keyだけです。バンドラーは、そのプレフィックスが付いたものをすべてブラウザに送るからです。
service role keyが漏えいしたらどうする?
侵害されたものとして扱ってください。見つけた人は、すべてのテーブルのすべての行を読み書きできました。
- 新しいsecret keyを作成し、サーバーをそれに移してから、漏えいしたキーを削除します。漏えいしたのが従来の
service_roleキーなら、解決策は新しいキーへの移行です。publishable keyとsecret keyを作り、アプリを切り替えてから、Settings → API Keysで従来のキーを無効にします。そうしない限り、従来のキーは有効なままです。 - ビルド済みのJavaScriptとGit履歴から
sb_secret_とservice_roleを検索します。Escapeがvibe codingアプリ5,600件をスキャンしたところ、400件超のシークレットの露出が見つかり、その大半はフロントエンドのバンドルにありました。 - 漏えいしていた間に何が変わったかを確認します。新しいユーザー、ロールの変更、削除された行です。
無料のSupabase セキュリティチェックはフロントエンドは読みませんが、同じ問題のデータベース側を読みます。RLSを迂回するロール、RLSがオフのテーブル、すべての行を許可するポリシー、機密に見えて匿名の訪問者が読める列です。公開キーが実際に何に届くかを確認する手順はvibe codingのセキュリティリスクにあり、アプリに対して私が実行する全リストはLovableアプリのセキュリティチェックリストにあります。