---
title: "Supabase 10月30日のgrant変更：壊れる点と確認方法"
description: "2026年10月30日、既存プロジェクトでも新しいpublicテーブルがAPIに自動公開されなくなります。何が壊れ何が動き続けるか、4つの確認方法をローカルで検証しました。"
url: "https://madsalt.tech/ja/blog/supabase-october-30-grant-change/"
author: "Denys Zaichenko"
language: "ja"
published: 2026-10-26
---

# Supabase 10月30日のgrant変更：壊れる点と確認方法

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セキュリティチェッカー](https://madsalt.tech/ja/tools/supabase-security-check/)を運営しています。そのため、多くの人が引っかかる前に、この変更が実際に何をするのかを確かめたいと思いました。ローカルのSupabase環境に従来のデフォルトを設定し、Supabaseの説明どおりに切り替えを適用し、思いつく限りの種類のオブジェクトにリクエストを送りました。この記事は、その結果と、30日までに自分のプロジェクトを確認する方法です。

## 2026年10月30日に何が変わるのか？

これまで、`public`スキーマに作ったテーブルには、Postgresのdefault privilegesによって`anon`、`authenticated`、`service_role`への`select`、`insert`、`update`、`delete`が付いていました。そのため新しいテーブルは、RLSで守られているかどうかにかかわらず、作った瞬間からブラウザに届いていました。Supabaseの[breaking changeのお知らせ](https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically)は、このデフォルトを3段階で外します。

| 日付           | 起きること                                                                    |
| -------------- | ----------------------------------------------------------------------------- |
| 2026年4月28日  | 新規プロジェクトは、作成時に「Automatically expose new tables」をオフにできる |
| 2026年5月30日  | 新しい動作が新規プロジェクトのデフォルトになる。数週間かけて展開              |
| 2026年10月30日 | 新しい動作がすべての既存プロジェクトに適用される                              |

あなたのプロジェクトでの切り替えは2つの文です。Supabaseが早期に切り替えるために示しているものと同じです。

```sql
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リクエストずつ呼び出しました。

![10月30日の切り替え後に応答しなくなるもの。ローカルのSupabase。changelogのrevoke後に作ったオブジェクト。特記なければ公開キー。RLSとpolicyを付けた新しいテーブル：テーブル作成 → policyのみ → 42501、401、同じテーブルをsecret keyで：テーブル作成 → policyのみ → 42501、403、動くテーブル上の新しいview：view作成 → 42501、ヒントなし、migrationで作り直した古いテーブル：drop + 作成 → grant消失 → 42501、401。](https://madsalt.tech/charts/ja/supabase-october-30-grant-change.svg)

*ローカルのSupabase環境での再現（CLI 2.109.1、Postgres 17.6、PostgREST 14.14）、2026年10月26日。*

![10月30日の切り替え後も動き続けるもの。同じローカル環境、同じrevoke、公開キー。切り替え前に作ったテーブル：既存テーブル → grant維持 → 2行、200、RPCで呼ぶ新しい関数：関数作成 → PUBLIC経由 → HTTP 200、新しいserialテーブル（select, insertのみ）：id serial → UPDATE残る → insert、201。](https://madsalt.tech/charts/ja/supabase-october-30-grant-change-works.svg)

*ローカルのSupabase環境での再現（CLI 2.109.1、Postgres 17.6、PostgREST 14.14）、2026年10月26日。*

| オブジェクト（特記なければ切り替え後に作成）                           | キー   | 結果                                                                 |
| ---------------------------------------------------------------------- | ------ | -------------------------------------------------------------------- |
| 切り替え前に作ったテーブル                                             | 公開   | 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のセキュリティガイド](https://supabase.com/docs/guides/api/securing-your-api)にも説明があります。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のノート](https://madsalt.tech/ja/notes/supabase-42501-permission-denied/)で扱っています。

## 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のディスカッション](https://github.com/orgs/supabase/discussions/45329)では、顧客ごとに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`が作る新しいオブジェクトに何が付くかを一覧します。

```sql
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より前に失敗します。このクエリは、そうした組み合わせを一覧します。私の検証環境では、新しいテーブルと作り直した古いテーブルだけが見つかりました。

```sql
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`がないものを一覧します。

```bash
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を書き、各ロールにはアプリが使うものだけを与えます。

```sql
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アプリのセキュリティチェックリスト](https://madsalt.tech/ja/blog/lovable-app-security-checklist/)にまとめています。

最初の42501が出たときにやってはいけないこと：

- **`grant all on all tables in schema public to anon, authenticated`を実行しない。** 従来のデフォルトを全テーブルに一度に戻してしまいます。RLSを一度もオンにしていないテーブルも含まれ、公開キーを持つ誰からでも読めるようになります。[42501のノート](https://madsalt.tech/ja/notes/supabase-42501-permission-denied/)で、grantが1つ目の鍵でRLSが2つ目の鍵である理由を説明しています。
- **`alter default privileges … grant`を足し直さない。** それは変更全体の巻き戻しです。
- **「テスト」のためにRLSを無効にしない。secret keyをフロントエンドに置かない。** secret keyは、バンドルを開いた全員に対してRLSを飛ばします。キーの違いは[anon keyとservice role keyの違い](https://madsalt.tech/ja/blog/supabase-anon-key-vs-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をテストする方法](https://madsalt.tech/ja/blog/how-to-test-supabase-rls/)で手順を追えます。この変更が対象にしない`public`以外のスキーマは、[公開スキーマのノート](https://madsalt.tech/ja/notes/supabase-exposed-schemas/)で扱っています。
