TL;DR
SupabaseのExposed schemasリストは、公開キーを持つ誰に対してData APIがどのスキーマを提供するかを決めます。デフォルトはpublicとgraphql_publicです。リストにあるスキーマのテーブル、ビュー、関数は、それぞれ独自のgrantとRLSを持つ必要があるため、スキーマを1つ足すごとに守る面が増えます。リストは短く保ち、最初に確認してください。
私は無料のSupabaseセキュリティチェッカーを作りましたが、Exposed schemasリストはそれが最初に読む項目の1つです。Supabaseプロジェクトの安全性を聞かれたとき、私が最初に見るのがこの設定です。
Supabaseで「公開(exposed)」とは何か?
SupabaseはPostgRESTを使って、データベースをRESTで提供します。PostgRESTはdb_schemaリストにあるスキーマにしか応答しません。このリストはダッシュボードのProject Settings → Data API → Exposed schemasで編集します。私が見たプロジェクト設定では値はpublic,graphql_publicで、Supabaseのカスタムスキーマのガイドには、publicはデフォルトで公開されると書かれています。リストにないスキーマへのリクエストは拒否され、PostgRESTはpg_catalogとinformation_schemaをリストに入れることを決して許可しません。
このリストはSupabaseのセキュリティでなぜ重要なのか?
公開されたスキーマの中身は、テーブル、ビュー、関数のすべてが公開キーで到達できます。SupabaseのRLSガイドははっきり書いています。公開スキーマにあるRLSなしのテーブルは、grantを持つどのロールでも読み書きできます。リストにないスキーマには、APIからまったく到達できません。
つまり、スキーマを1つ追加するのは中立的な変更ではありません。リストにstorageやvaultを加えれば、内部テーブルをData APIの届く範囲に置くことになります。自分のスキーマを加えれば、その中のすべてのテーブルにpublicと同じ注意が必要です。
チェッカーはこのリストをどう評価するのか?
チェック名はextra_exposed_schemasです。publicとgraphql_public以外はすべて追加扱いで、そのうえで次のように判定します。
- リストにSupabaseの内部スキーマ(
auth、storage、realtime、vault、extensionsなど)がある場合は、失敗、重大度は高です。 - たとえば
apiのようなカスタムスキーマだけなら、警告、重大度は中です。 - 追加がなければ合格です。
同じリストは他のチェックにも使われます。RLSが無効なテーブル、anonへの書き込みgrant、常に真になるポリシー、security_invokerのないビュー、anonが呼び出せるSECURITY DEFINER関数は、公開スキーマについてだけ報告されます。公開キーが届くのはそこだからです。
カスタムスキーマを安全に使うには?
カスタムスキーマは正当なパターンです。SupabaseのSecuring your APIガイドは、apiのような専用スキーマを、公開される面を見分けやすく監査しやすくする方法として説明しています。内部テーブルは公開しないスキーマに置きます。成立させる手順は2つで、カスタムスキーマのガイドに両方が載っています。スキーマをExposed schemasに追加し、そのスキーマのusageをanon、authenticated、service_roleに付与し、さらにそのオブジェクトへの権限を付与します。
ガイドの例はすべてのテーブルにALLを付与しています。私ならその行をそのままコピーしません。Securing your APIガイドは、各ロールに必要最小限だけを与え、公開するすべてのテーブルでRLSを有効にするよう述べています。grantはロールがテーブルに触れるかどうかを決め、ポリシーはどの行かを決めます。
別の変更もあります。Supabaseはpublicの新しいテーブルをData APIに自動で付与しなくなります。新規プロジェクトでは2026年5月30日からデフォルトになり、既存プロジェクトには2026年10月30日に適用されます。既存のテーブルのgrantは維持されます。これはgrantの層の話で、リストにどのスキーマがあるかは変わりません。
自分のプロジェクトをどう確認する?
Project Settings → Data APIを開き、Exposed schemasを読んでください。publicとgraphql_public以外があれば、追加スキーマそれぞれが何のためのものかを確認します。
次に、それらのスキーマでRLSが無効なテーブルと、anonがすでに読めるかどうかを一覧にします。
select n.nspname as schema,
c.relname as "table",
has_table_privilege('anon', c.oid, 'select') as anon_can_select
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname in ('public', 'api') -- your Exposed schemas
and c.relkind in ('r', 'p')
and not c.relrowsecurity
order by 1, 2;
私は、使い捨てのPostgres 17でこれを実行しました。apiスキーマにはRLSありのテーブルとなしのテーブルを1つずつ、publicにはRLSなしのテーブルを1つ置きました。結果は、RLSなしの2つだけでした。
schema | table | anon_can_select
--------+--------+-----------------
api | orders | t
public | logs | t
anon_can_selectが真の行はすべて、公開キーを持つ誰でも読めるテーブルです。RLSを有効にするか、grantを取り消すまでそのままです。このクエリが対象にするのはテーブルだけです。ビューと関数は別に確認する必要があり、それは私のLovableアプリのセキュリティチェックリストで順に見ています。また、Supabase RLSのテスト方法では、ポリシーが機能することをどう証明するかを示しています。