TL;DR
2026年10月30日、Supabaseはすべての既存プロジェクトで、publicの新しいテーブルにanon、authenticated、service_roleへの自動grantを付けなくなります。すでにあるテーブルは動き続けます。それ以降に作ったテーブルは、migrationでgrantするまで、supabase-jsにもsecret keyにも42501 permission deniedを返します。この切り替えをローカルで再現しました。新しいテーブル、新しいview、migrationでdropして作り直した古いテーブルはすべて失敗し、既存のテーブル、RPC関数、serialのinsertは動き続けました。下の2つのSQLと1つのgrepで確認し、修正は同じmigrationの中でgrantとRLSとpolicyを書いて行います。grant all on all tablesは使わないでください。
私は、公開されている全テーブルのgrantを読む無料のSupabaseセキュリティチェッカーを運営しています。そのため、多くの人が引っかかる前に、この変更が実際に何をするのかを確かめたいと思いました。ローカルのSupabase環境に従来のデフォルトを設定し、Supabaseの説明どおりに切り替えを適用し、思いつく限りの種類のオブジェクトにリクエストを送りました。この記事は、その結果と、30日までに自分のプロジェクトを確認する方法です。
2026年10月30日に何が変わるのか?
これまで、publicスキーマに作ったテーブルには、Postgresのdefault privilegesによってanon、authenticated、service_roleへのselect、insert、update、deleteが付いていました。そのため新しいテーブルは、RLSで守られているかどうかにかかわらず、作った瞬間からブラウザに届いていました。Supabaseのbreaking changeのお知らせは、このデフォルトを3段階で外します。
| 日付 | 起きること |
|---|---|
| 2026年4月28日 | 新規プロジェクトは、作成時に「Automatically expose new tables」をオフにできる |
| 2026年5月30日 | 新しい動作が新規プロジェクトのデフォルトになる。数週間かけて展開 |
| 2026年10月30日 | 新しい動作がすべての既存プロジェクトに適用される |
あなたのプロジェクトでの切り替えは2つの文です。Supabaseが早期に切り替えるために示しているものと同じです。
alter default privileges for role postgres in schema public
revoke select, insert, update, delete on tables from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke usage, select on sequences from anon, authenticated, service_role;
default privilegesは、あとから作られるオブジェクトにだけ効きます。すでにあるgrantには触れないので、今あるテーブルは動き続けます。お知らせによると、影響を受けないのは、ほかのスキーマ(auth、storage、realtime、自分で作ったもの)、Postgresに直接接続するアプリ(psql、ORM、接続文字列を使うサーバー)、そしてRLSそのものです。変更の対象はData APIです。REST、GraphQL、その上に載るすべてのクライアントライブラリが含まれます。
何が壊れ、何が動き続けるのか?
私の環境は、Supabase CLI 2.109.1、Postgres 17.6、PostgREST 14.14で、2026年10月26日に実施しました。config.tomlにauto_expose_new_tables = trueを設定して従来のデフォルトを再現し、2行のテーブルを1つ作ってRLSをオンにし、読み取りpolicyを付けてから、上の2つの文を実行しました。そのあと新しいオブジェクトを作り、ローカルの公開キー(anon)とsecret key(service_role)で、それぞれ1リクエストずつ呼び出しました。
| オブジェクト(特記なければ切り替え後に作成) | キー | 結果 |
|---|---|---|
| 切り替え前に作ったテーブル | 公開 | HTTP 200、2行 |
RLSとto anon, authenticatedのpolicyを付けた新しいテーブル |
公開 | HTTP 401、42501、ヒント GRANT SELECT ON public.new_orders TO anon; |
| 同じ新しいテーブル | secret | HTTP 403、42501、ヒント … TO service_role; |
古いテーブルの上に作った新しいview(security_invoker) |
公開 | HTTP 401、42501、permission denied for view、ヒント null |
create table … (like old_orders including all)で作ったコピー |
公開 | HTTP 401、42501 |
drop tableしてもう一度create tableした古いテーブル |
公開 | HTTP 401、42501 |
/rest/v1/rpc経由の新しい関数public.ping() |
公開 | HTTP 200 |
id serialを持つ新しいテーブル。anonにはselect, insertだけをgrant |
公開 | insert HTTP 201 |
この表には、切り替え前に知っておくべき点が5つあります。
「既存のテーブルはgrantを保つ」が成り立つのは、そのテーブルが存在し続ける間だけです。 テーブルをdropして作り直すmigration、create table … (like …)によるコピー、dumpからのリストアは、新しいテーブルを作ります。新しいテーブルには何も付きません。policyは私のcreate policyで戻りましたが、grantは戻りませんでした。パイプラインの中でテーブルをalterせず作り直している箇所は、切り替え後の最初の実行で壊れます。
secret keyも壊れます。 service_roleはRLSを飛ばしますが、それでもgrantは必要です。新しいテーブルに書き込むEdge Function、cronジョブ、webhookのハンドラーは、ブラウザだけでなくこれらも403で失敗します。ヒントにservice_roleと書かれているので、見分けられます。
viewにはヒントが出ません。 テーブルの場合、PostgRESTは必要なGRANT文をそのまま返します。新しいviewではヒントがnullで、メッセージはpermission denied for view v_old_ordersでした。42501をヒントから直すAIツールには、ここでコピーできるものがありません。必要なgrantはgrant select on public.v_old_orders to authenticated;で、テーブルのRLSが引き続き効くよう、viewはsecurity_invoker = trueのままにします。
関数はこの変更の対象外です。 2つの文が触るのはテーブルとシーケンスです。私の新しい関数はanon、authenticated、service_roleから呼び出せたままでした。Postgresは新しい関数すべてにPUBLICへのexecuteを付けるため、スキーマ単位のrevokeでは消せないからです。Supabaseのセキュリティガイドにも説明があります。RPC呼び出しは10月30日以降も動きます。同時に、新しいsecurity definer関数は、作った瞬間から公開キーで呼び出せるということでもあります。
serialのinsertは、シーケンスのgrantなしでも動き続けました。 切り替え後はserialのidにgrant usage on sequence … to authenticatedが必要だという情報をよく見ます。私の実行では、それなしでinsertが通りました。revokeが外すのは新しいシーケンスのusageとselectで、updateは残り、nextvalにはupdateで足ります。セキュリティガイドの完全なrevokeのように、シーケンスのdefault privilegesをすべて取り消した場合は、usageのgrantが必要です。identity列(generated by default as identity)は、どちらの場合も何も要りません。
もう1つ、revokeが残すものがあります。新しいテーブルには、3つのロールすべてにTRUNCATE、REFERENCES、TRIGGERが付いたままで、Postgres 17ではデフォルトにMAINTAINも含まれます。TRUNCATEにはRLSのpolicyが効きません。これは42501のノートで扱っています。
10月30日より前にもう出会う場面
ホスティング版のプロジェクトの切り替えは30日ですが、すでに切り替え後と同じ挙動をする場所が3つあります。
新しいCLIでのsupabase db reset。 CLI v2.102.0で、config.tomlに[api] auto_expose_new_tablesが追加されました。2.109.1でキーを書かずに作り直したところ、私の新しい環境にはすでに新しいデフォルトが入っていました。テーブルにはMAINTAIN、REFERENCES、TRIGGER、TRUNCATEだけ、シーケンスにはUPDATEだけが付いていました。trueにすると従来のgrantが戻り、api.auto_expose_new_tables is deprecated and will be removed on 2026-10-30と表示されます。resetのあともローカルのテーブルが動くなら、まずこのキーを確認してください。試すのにも最も手軽な場所です。キーを消してresetし、アプリを動かします。
プレビューブランチと、5月30日以降に作ったプロジェクト。 migrationを新しいデフォルトで再生します。migrationにgrantがないテーブルは、そこではすでに届きません。GitHubのディスカッションでは、顧客ごとに1プロジェクトを持つ人たちがまさにこれを報告しています。稼働中のプロジェクトは無事で、同じmigrationから新しくデプロイするたびに壊れる、という内容です。
従来のデフォルトを戻してしまうベースラインmigration。 同じディスカッションのコメントによると、supabase db pullで作った最初のmigrationに、alter default privileges … grant all on tables to anon, authenticated, service_roleが入っていることがあります。切り替えのあとでこの文を実行したところ、次に作ったテーブルは再び届くようになり、HTTP 200でした。ローカルでは問題が隠れます。ホスティング版に適用すると、変更を静かに打ち消し、今後のテーブルはRLSの有無にかかわらず、また公開キーに開いてしまいます。migrationの中でalter default privilegesを検索し、ヒットしたものはすべて読んでください。
10月30日までにプロジェクトを確認する方法
確認は4つあり、速いものから徹底的なものの順です。最初の2つは、ホスティング版プロジェクトのSQL Editorで実行します。
1. プロジェクトはもう切り替わっているか?
Supabaseのセキュリティガイドにあるクエリです。publicでpostgresが作る新しいオブジェクトに何が付くかを一覧します。
select
case d.defaclobjtype
when 'r' then 'tables'
when 'S' then 'sequences'
when 'f' then 'functions'
end as object_type,
a.grantee::regrole as role,
string_agg(a.privilege_type, ', ' order by a.privilege_type) as privileges
from pg_default_acl d
cross join lateral aclexplode(d.defaclacl) a
where d.defaclrole = 'postgres'::regrole
and d.defaclnamespace = 'public'::regnamespace
and a.grantee in ('anon'::regrole, 'authenticated'::regrole, 'service_role'::regrole)
group by 1, 2
order by 1, 2;
tablesの行にSELECT, INSERT, UPDATE, DELETEが含まれていれば、プロジェクトはまだ従来のデフォルトで、30日に切り替わります。MAINTAIN, REFERENCES, TRIGGER, TRUNCATEだけ(または行なし)なら、新しいテーブルはすでに閉じています。Dashboardの Data API settings にある「Automatically expose new tables」でも確認できます。
2. policyはあるのにgrantがないテーブルはどれか?
そのコマンドのgrantを持たないロールに対するpolicyは、実行されません。リクエストはRLSより前に失敗します。このクエリは、そうした組み合わせを一覧します。私の検証環境では、新しいテーブルと作り直した古いテーブルだけが見つかりました。
select p.schemaname || '.' || p.tablename as "table", r.role, p.cmd
from pg_policies p
cross join lateral unnest(case when 'public' = any(p.roles) then array['anon', 'authenticated']::name[] else p.roles end) as r(role)
join pg_class c on c.relname = p.tablename
join pg_namespace n on n.oid = c.relnamespace and n.nspname = p.schemaname
where p.schemaname = 'public'
and r.role in ('anon', 'authenticated')
and not has_table_privilege(r.role::text, c.oid,
case p.cmd when 'ALL' then 'SELECT' else p.cmd end)
order by 1, 2, 3;
migrationのたびに、CIでまっさらなdb resetに対して、またはstagingで実行してください。健全なプロジェクトでは何も返りません。30日以降は、1行が1つの「アプリから届かないテーブル」です。見ているのはテーブル単位のgrantなので、列単位のgrantだけで開いているテーブル(grant select (id, name) on …)は、その列が読めていてもここに出ます。
3. grantなしでテーブルを作っているmigrationはどれか?
データベースに見えるのは、存在するテーブルだけです。まだ適用していないmigrationは、リポジトリの中にしか見えません。この簡易grepは、supabase/migrationsにあるcreate tableのうち、同じ名前へのgrant … onがないものを一覧します。
for t in $(grep -hioE 'create table( if not exists)? +[a-z0-9_."]+' supabase/migrations/*.sql | awk '{print $NF}' | sort -u); do
grep -qiF "on $t " supabase/migrations/*.sql || grep -qiF "on table $t " supabase/migrations/*.sql || echo "no grant: $t"
done
これはSQLの構文解析ではなく文字列の照合です。1つの文で複数のテーブルをgrantしている場合や、ある箇所ではスキーマなし、別の箇所ではスキーマ付きで書いている場合は、誤検出になります。出力された行は1つずつ読んでください。直接接続のサーバーコードからしか触らないテーブルには、grantは一切要りません。
4. 新しいデフォルトですべてを再生する
config.tomlからauto_expose_new_tablesを消し、supabase db resetを実行し、そこに対してアプリやテストを動かします。これが30日以降のあなたのプロジェクトで、しかもすべてのテーブルの新しいコピーを含むので、より厳しいケースです。Supabaseは、影響を受けるテーブルをSecurity Advisorに一覧し、Table EditorではData APIへの公開を表示します。
すべてのテーブルを開けずに直す方法
テーブルを作るmigrationの中で、grant、RLS、policyを書き、各ロールにはアプリが使うものだけを与えます。
create table public.invoices (
id bigint generated by default as identity primary key,
user_id uuid not null default auth.uid(),
total numeric not null
);
revoke all on table public.invoices from anon, authenticated, service_role;
grant select, insert on table public.invoices to authenticated;
grant select, insert, update, delete on table public.invoices to service_role;
alter table public.invoices enable row level security;
create policy "Owners read their invoices"
on public.invoices for select
to authenticated
using ((select auth.uid()) = user_id);
create policy "Owners add their invoices"
on public.invoices for insert
to authenticated
with check ((select auth.uid()) = user_id);
このブロックを同じローカル環境で実行しました。サインイン済みのテストユーザーが行をinsertして読み返せ(HTTP 201、続いて200)、セッションなしの公開キーは42501になり、上の確認2はこのテーブルを一覧しなくなりました。最初のrevoke allは、新しいテーブルに付いてくるTRUNCATE、TRIGGER、REFERENCESを消します。まだ切り替わっていないプロジェクトでも同じ結果になります。anonには、ログインしていない訪問者向けのデータにだけgrantし、service_roleは、サーバーコードがこのテーブルにsecret keyを使うときだけgrantします。アプリ全体でgrantとpolicyがどう組み合わさるかは、Lovableアプリのセキュリティチェックリストにまとめています。
最初の42501が出たときにやってはいけないこと:
grant all on all tables in schema public to anon, authenticatedを実行しない。 従来のデフォルトを全テーブルに一度に戻してしまいます。RLSを一度もオンにしていないテーブルも含まれ、公開キーを持つ誰からでも読めるようになります。42501のノートで、grantが1つ目の鍵でRLSが2つ目の鍵である理由を説明しています。alter default privileges … grantを足し直さない。 それは変更全体の巻き戻しです。- 「テスト」のためにRLSを無効にしない。secret keyをフロントエンドに置かない。 secret keyは、バンドルを開いた全員に対してRLSを飛ばします。キーの違いはanon keyとservice role keyの違いにあります。
AIツールにmigrationを書かせているなら、その指示に1行足してください。「every new table in public gets explicit grants per role, enable row level security and policies in the same migration; never grant on all tables in schema public」。エラーのヒントが教えるのはgrantだけで、セットで必要なRLSは教えてくれません。
この変更についてよくある質問
既存のテーブルは10月30日に動かなくなりますか? いいえ。default privilegesはあとから作られるオブジェクトにだけ効き、私の実行でも、切り替え後に古いテーブルはHTTP 200を返しました。例外は、migrationでdropして作り直すテーブルです。
テーブルを作らないなら、何かする必要はありますか? 30日には不要です。自分、チームメンバー、AIツールがテーブルを追加するときに確認してください。migrationからプロジェクトやブランチを作るパイプラインも確認してください。
Edge Functionに影響しますか? はい。新しいテーブルに対してsupabase-jsとsecret keyを使う場合は、service_roleにもgrantが必要です。Postgresに直接接続する関数は影響を受けません。
RPC関数、Storage、Authに影響しますか? この変更ではありません。新しい関数はPUBLIC経由で呼び出せたままで、storageとauthはpublicスキーマではありません。
前倒しで切り替えられますか? はい。冒頭の2つの文が切り替えです。先にstagingで、次に本番で実行してください。既存のテーブルはgrantを保つので稼働中のアプリは気づきませんし、次に新しいテーブルを作れば、migrationの準備ができているかがすぐ分かります。
チェッカーはこれをどう見るか
チェッカーは読み取り専用で接続し、公開されているスキーマの全テーブルのgrantを読みます。公開キーがpolicyなしで書き込めるテーブル、RLSがオフのテーブル、新しいテーブルすべてにTRUNCATE、REFERENCES、TRIGGERを与え続けているdefault privilegesを調べます。また、そのコマンドのgrantを持たないロールを対象にしたpolicyもすべて一覧にします(RLSがオフのテーブルと、列単位のgrantがあるテーブルは除きます)。上の確認2で見つかるのと同じテーブルです。まだ適用していないmigrationファイルは見えないので、上の確認3はご自身で実行してください。RLSに不安があるなら、SupabaseのRLSをテストする方法で手順を追えます。この変更が対象にしないpublic以外のスキーマは、公開スキーマのノートで扱っています。