Supabase、10/30から新規テーブルにGRANT必須化 — 既存プロジェクトで壊れる3つのケースと対処法

はじめに

Supabase は 2026 年 10 月 30 日から、既存プロジェクトも含めて public スキーマの新しいテーブルを Data API(PostgREST / supabase-js)と GraphQL API に自動で公開しなくなります。新規プロジェクトでは 5 月末から既定になっていた変更が、明日から全プロジェクトに広がります。

公式は「既存のテーブルは影響を受けない」と説明していますが、これを読んで安心するのは早いです。マイグレーションを最初から流し直す場面では、過去に作ったテーブルもすべて「新しいテーブル」として作られるからです。この記事では、何が変わるのか、どこで壊れるのか、今日のうちに何を確認すればよいかを整理します。

何が変わるのか

これまで Supabase は、public にテーブルを作ると anon・authenticated・service_role の 3 ロールへ select・insert・update・delete を自動で付与していました。連番(sequence)にも usage と select が付いていました。仕組みは PostgreSQL の default privileges(ALTER DEFAULT PRIVILEGES)で、postgres ロールが作るオブジェクトに既定の権限を付ける設定です。

この自動付与がなくなります。公式 changelog のタイムラインは次のとおりです。

日付内容
2026/4/28新規プロジェクト作成時に「Automatically expose new tables」を外せるようになる
2026/5/18pg_graphql が既定で有効化されなくなる
2026/5/30新規プロジェクトでは自動公開しないのが既定に
2026/10/30既存プロジェクトにも適用

変更後に GRANT を書かずに作ったテーブルへ supabase-js からアクセスすると、PostgREST はコード 42501、メッセージ permission denied for table your_table を返します。GRANT SELECT ON public.your_table TO anon; のようなヒントも付くため、黙って空配列が返るような分かりにくい壊れ方ではありません。

対象は Data API 経由のアクセスだけです。psql や ORM、接続文字列でアプリサーバーから直接 Postgres に繋いでいる場合は影響を受けません。auth・storage・realtime や独自スキーマも現状の権限のままです。

GRANT と RLS は別の層

この変更を理解するうえで大事なのは、GRANT と RLS が別々の検査だという点です。

PostgreSQL はクエリを受けると、まず「このロールはこのテーブルに触れてよいか」をテーブル単位の権限(GRANT)で調べます。通ったあとで「どの行を見せるか」を RLS のポリシーで絞ります。これまで Supabase では前者が自動で全開になっていたので、開発者は RLS のポリシーだけ書けば動く状態でした。

その結果、「RLS さえ書けば安全」「RLS を書き忘れたテーブルも全ロールから丸見え」という二つの状態が同居していました。RLS の設定漏れでテーブルが anon キーだけで読めてしまう事故は実際に起きています。2025 年には、Lovable で作られたアプリ 170 件超で RLS の不備によりデータを読み書きできた問題が CVE-2025-48757 として登録されました。今回の変更は、入口の権限を既定で閉じ、公開するテーブルを開発者に一つずつ選ばせる方向への転換です。

公式が示す新しいテーブルの書き方は次のとおりです。

grant select on public.your_table to anon;
grant select, insert, update, delete on public.your_table to authenticated;
grant select, insert, update, delete on public.your_table to service_role;
alter table public.your_table enable row level security;

GRANT で入口を開け、RLS で行を絞る。2 行セットで書くのが新しい標準になります。

既存プロジェクトで壊れる 3 つのケース

Zenn の記事(べるべる氏)は、実際に手元で試して次の 3 つを挙げています。

  1. 10/30 以降に追加するテーブル: 当然 GRANT が要ります。マイグレーションのテンプレートや、AI に書かせているコードが旧来の書き方のままだと、リリース後に 42501 が出ます
  2. マイグレーションの流し直し: 検証環境を新しく作る、障害で本番を作り直す、といった場面です。過去のマイグレーションには GRANT が書かれていないため、全テーブルが API から使えなくなります。普段は起きず、困ったときにだけ起きるので最も危険なケースです
  3. 一般の実行権限を外した関数: revoke execute ... from public, anon で絞った関数は、これまで既定権限によって authenticated から呼べていました。自動付与がなくなると誰からも呼べなくなります

3 つ目は公式 changelog の本文に手順がありません。本文の SQL はテーブルと連番だけで、関数は設定名に「and functions」と出てくる程度です(GitHub Discussion のコメントでは、メンテナーが grant execute を明示するよう回答しています)。Zenn の筆者も「一番気づきにくいのがこれでした」と書いており、AI に修正を頼む場合も、先に新しい挙動を再現させないとテーブルの GRANT だけで終わって関数を見落とす、と注意しています。

今日やっておくこと

ローカルで先に新しい挙動を再現できます。Supabase CLI v2.102.0 以降で、supabase/config.toml に次を書きます。

[api]
auto_expose_new_tables = false

この状態で supabase db reset してから supabase test db を流すと、ケース 2 を手元で起こせます(db reset はローカルのデータを消すので注意)。Discussion でメンテナーが引用した CLI のコードには「この項目は 2026-10-30 に削除される」とあるので、この設定で試せるのは今日のうちと考えておくのが安全です。

修正は 1 本のマイグレーションにまとめるのが分かりやすいです。

  • 既存テーブルに今と同じ権限を明示する。テーブル名を列挙し、grant ... on all tables in schema public は使わない。RLS を有効にしていないテーブルまで公開してしまうからです
  • 実行権限を絞った関数には、呼ぶロールへ grant execute を書く
  • default privileges を revoke all で取り消す。select などを個別に外すと TRUNCATE などが残るため all を使う

さらに pgTAP で「authenticated に select が付いていない public のテーブル」「service_role が実行できない関数」を pg_class / pg_proc から洗い出すテストを置けば、今後の書き忘れも CI で止められます。

Claude Code や Codex にマイグレーションを書かせているなら、CLAUDE.md / AGENTS.md に「テーブル作成時は同じファイルで GRANT と RLS を書く」「all tables in schema public は使わない」と書いておくのが効きます。Zenn の筆者も、AI はしばらく GRANT のない昔の書き方をする可能性が大きいと指摘しています。RLS だけのマイグレーションは長く Supabase の標準的な書き方だったので、明示しないと元に戻りやすい部分です。

なお、公式によればダッシュボードの Security Advisor が影響するテーブルと修正 SQL を表示し、Table Editor には Data API に公開されているかのバッジが出ます。SQL を書く前に一度確認しておくと全体像がつかめます。

まとめ

  • 10/30 から、既存プロジェクトでも public の新しいテーブルは GRANT を書かないと Data API から 42501 になる
  • 既存テーブルは動き続けるが、マイグレーションの流し直しでは全テーブルが新規扱いになり全滅する
  • 関数の実行権限は公式の告知に明記がなく、見落としやすい
  • 対処は「ローカルで auto_expose_new_tables = false にして再現 → 権限を明示するマイグレーション → 書き忘れを検出するテスト」の順

RLS だけで守る設計から、GRANT で入口を閉じたうえで RLS を重ねる設計へ移る変更です。手間は増えますが、公開範囲がマイグレーションに明示されることで、レビューで追える状態になります。

ソース