1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Supabase の自動 GRANT が 2026-10-30 に廃止:RLS を書いたのに `42501` になる理由と、migration に GRANT を書く型

1
Last updated at Posted at 2026-09-27

はじめに

Supabase は 2026-10-30 から、既存の全プロジェクトで public スキーマの新規テーブルへの権限(GRANT)の自動付与をやめます。
これからは、テーブルを作っただけでは Data API(PostgREST / supabase-js)から触れません。

筆者は期限前から、この状態を一度踏んでいます。
RLS ポリシーを正しく書いたつもりなのに、新しく作ったテーブルへの操作が全部 42501 で失敗しました。
エラーコードが RLS 違反と同じなので、ポリシーの書き間違いを疑って遠回りしました。

この記事は、Supabase を使っていて SQL Editor や migration でテーブルを作っている方に向けたものです。
読み終えると、次の2つができるようになります。

  • 自分のプロジェクトが影響を受けるかを SQL で確認する
  • migration に GRANT を書く型を、そのまま持ち帰る

TL;DR

  • 2026-10-30 以降、public の新規テーブルには anon / authenticated / service_role の権限が自動で付かない。既存テーブルの権限はそのまま
  • GRANT と RLS は別の層。GRANT が無いと、RLS を評価する前に 42501 permission denied で弾かれる
  • 同じ 42501 でも、メッセージを見れば GRANT 不足か RLS 違反かを見分けられる
  • 新しいテーブルの migration には GRANT を必ず書く。必要なロール・必要な操作だけに絞り、anon には安易に付けない
  • 本番が無事でも、migration から環境を作り直すと全テーブルが 42501 になることがある。スキーマの正本にも GRANT を書いておく

何が変わるのか

公式の告知(Changelog)から、要点をまとめます。

日付 内容
2026-04-28 プロジェクト作成時に新しい挙動を選べるようになった
2026-05-30 新規プロジェクトは新しい挙動がデフォルトになった
2026-10-30 既存の全プロジェクトに新しい挙動が適用される

変更の中身は次のとおりです。

  • これまでは、public スキーマに作ったテーブルに anon / authenticated / service_role の SELECT, INSERT, UPDATE, DELETE が既定の権限(default privileges)で自動的に付いていた
  • これからは、この既定の権限が外れる。Data API から使うテーブルには、明示的な GRANT が必要になる
  • 既存テーブルは影響を受けない。付いている権限はそのまま残る
  • 対象は Data API(PostgREST / GraphQL)経由のアクセス。Postgres への直接接続は関係ない

service_role も対象です。サーバー側で service_role キーを使っていると「RLS をバイパスするから大丈夫」と思いがちですが、RLS をバイパスできても GRANT はバイパスできません。サーバーからだけ使うテーブルでも、service_role への GRANT が必要です。

影響が出るのは、大きく次の2つです。

  1. これから作るテーブル
  2. migration やスキーマ定義から環境を作り直すとき(新しいプロジェクト、ローカルでの supabase db reset など)

2つ目は見落としやすいポイントです。
公式の Discussion でも、新しいデフォルトで supabase db reset をすると過去のテーブルに GRANT が付かない、という点が取り上げられています(Discussion #45329)。

GRANT と RLS は別の層

まず、なぜ「RLS を正しく書いたのに動かない」のかを整理します。
Postgres は、Data API から来たリクエストを2段階でチェックします。

層 決めること 例
テーブル権限(GRANT) そのロールが、そのテーブルをそもそも操作してよいか authenticated は notes に INSERT できる
RLS(行レベルセキュリティ) そのロールが、どの行を見てよいか・書いてよいか 自分の owner_id の行だけ

GRANT は外側の門で、RLS はその内側の門です。
外側の門が閉じていると、内側の RLS は評価すらされません。
だから、ポリシーをどれだけ正しく書いても 42501 のままです。

公式ドキュメントも「公開するオブジェクトには両方を使うこと」と書いています(Securing your API)。

実際にハマった例:期限前から起きていた

筆者の場合、既存のテーブルは問題なく動いていました。
ところが、後から SQL Editor で create table した新しいテーブルだけ、アプリからの操作がすべて失敗しました。

code: 42501
message: permission denied for table xxx

RLS を有効にしてポリシーも書いた直後だったので、まずポリシーを疑いました。
しかし、原因は新しいテーブルに authenticated への GRANT が付いていなかったことでした。
既存テーブルには以前から権限が付いていたため、それまで問題が表に出なかったのです。

振り返ると、今回の廃止は「条件によってはすでに起きていたこと」が、全プロジェクトの新規テーブルに広がる変更だと言えます。

42501 の見分け方:同じコードで原因が2つ

42501 は「権限不足」を表す Postgres のエラーコードです。
GRANT が無いときも、RLS に弾かれたときも同じコードになります。
見分けるにはメッセージを見ます。

メッセージ 原因 対処
permission denied for table xxx テーブル権限(GRANT)が無い GRANT を付ける
new row violates row-level security policy for table "xxx" RLS ポリシーに合わない ポリシーまたはクエリを見直す

新しい挙動では、GRANT 不足のときに hint に必要な GRANT 文が入って返ってきます(公式 Changelog より)。
エラーオブジェクトは message だけでなく hint まで見るのがおすすめです。

const { error } = await supabase
  .from('notes')
  .insert({ body: 'test', owner_id: userId })

if (error) {
  console.log(error.code, error.message, error.hint)
  // 42501 / permission denied for table notes          → GRANT が無い
  // 42501 / new row violates row-level security policy → RLS に合わない
}

補足:RLS 側でよくある落とし穴

GRANT とは別の原因ですが、見分けるときに一緒に知っておくと便利なものを1つ挙げます。

supabase-js の insert().select() は、SQL でいうと INSERT ... RETURNING です。
このとき Postgres は、挿入した行が SELECT ポリシーも満たすことを求めます(PostgreSQL: CREATE POLICY)。

たとえば SELECT ポリシーが「トリガーで後から作られる別テーブルの行」を前提にしていると、INSERT ポリシーは満たしていても失敗します。
INSERT ポリシーを何度見直しても直らないときは、SELECT ポリシーも確認してみてください。

自分のプロジェクトが影響を受けるか確かめる

既存テーブルの権限を見る

SQL Editor で次のどちらかを実行します。

-- テーブルごとの権限(ACL)をまとめて見る
select relname, relacl
from pg_class
where relnamespace = 'public'::regnamespace
  and relkind = 'r'
order by relname;

relacl に authenticated=arwd... のような表示があれば付与済みです。
文字の意味は、r=SELECT、a=INSERT、w=UPDATE、d=DELETE です(PostgreSQL: Privileges)。

ロールと操作を1行ずつ見たい場合は、こちらが読みやすいです。

select table_name, grantee, privilege_type
from information_schema.role_table_grants
where table_schema = 'public'
  and grantee in ('anon', 'authenticated', 'service_role')
order by table_name, grantee, privilege_type;

確認するポイント

確認すること 状態 判断
本番の既存テーブルに権限が付いているか 付いている 当面は影響なし
migration / スキーマ定義に GRANT が書かれているか 書かれていない 環境を作り直すと全テーブルで 42501
今後テーブルを追加する予定があるか ある migration に GRANT を書く運用に切り替える

本番が無事でも、2行目に当てはまるなら対応しておく価値があります。
「本番の権限は、過去の自動付与のおかげで付いているだけ」という状態だからです。

対策:migration に GRANT を明示する

新しいテーブルの migration の型

テーブル作成・RLS 有効化・ポリシー・GRANT を、同じ migration ファイルにセットで書きます。
別ファイルに分けると、GRANT だけ適用し忘れる事故が起きやすくなります。

create table if not exists public.notes (
  id uuid primary key default gen_random_uuid(),
  owner_id uuid not null references auth.users (id) on delete cascade,
  body text not null,
  created_at timestamptz not null default now()
);

alter table public.notes enable row level security;

create policy "notes: 自分の行だけ読める" on public.notes
  for select to authenticated
  using ((select auth.uid()) = owner_id);

create policy "notes: 自分の行だけ書ける" on public.notes
  for insert to authenticated
  with check ((select auth.uid()) = owner_id);

-- テーブル権限(RLS の前段)。2026-10-30 以降は自動で付かないので明示する。
-- ログインユーザーだけが使うテーブルなので anon には付けない。
grant select, insert, update, delete on table public.notes to authenticated;

ポリシー内の auth.uid() を (select auth.uid()) で包んでいるのは、行ごとの再評価を避けるためです(Row Level Security)。

付ける権限の決め方

自動付与は「3ロールに4操作すべて」でした。
明示するなら、必要なものだけに絞れます。

テーブルの使われ方 付けるもの
ログインユーザーだけが読み書きする authenticated に必要な操作だけ
未ログインでも読める公開データ anon に select だけ(+RLS で行を絞る)
読むだけのマスタ select だけ
サーバー(service_role)からも操作する service_role にも必要な操作を付ける

anon に付けると、未ログインでのアクセス経路が1つ増えます。
RLS で守られていても、使わない経路は最初から作らない方が安全側です。

テーブル以外にも GRANT が要るもの

公式ドキュメントと Discussion では、テーブル以外にも次のものが挙げられています。

  • 関数(RPC): execute 権限を明示する
  • ビュー: テーブルと同様に GRANT が要る。同じ migration で付ける
  • シーケンス: 既定の権限の取り消し対象に含まれる。serial 系の列を使う場合は確認する

関数は、呼べるロールを絞る書き方にしておくと安心です。
Postgres の関数は、既定で PUBLIC(全ロール)に execute が付くためです。

revoke execute on function public.my_rpc(uuid) from public, anon;
grant  execute on function public.my_rpc(uuid) to authenticated;

スキーマの正本にも既存テーブルぶんを書く

スキーマ全体を1ファイル(schema.sql など)で管理している場合は、既存の全テーブルぶんの GRANT も書いておきます。
本番にはすでに付いているので、実行しても何も変わりません。
効くのは、そのファイルから環境を作り直したときだけです。

小技:Supabase のロールが無い環境でも落ちない書き方

型生成や CI のために、素の Postgres コンテナへスキーマを流すことがあります。
その環境には authenticated などの Supabase 固有ロールが無いため、GRANT がエラーになります。
pg_roles で存在を確認してから付けると、両方の環境で同じファイルを使えます。

do $$
begin
  if exists (select 1 from pg_roles where rolname = 'authenticated') then
    grant select, insert, update, delete
      on table public.table_a, public.table_b to authenticated;
  end if;
end $$;

create policy ... to authenticated のようにポリシー側でもロールを参照している場合は、この書き方だけでは足りません。ポリシー作成時にもロールが必要になるため、その環境では先に create role authenticated; などでスタブのロールを作っておく必要があります。

期限前に新しい挙動で試しておく

10/30 を待たずに、既存プロジェクトを新しい挙動に切り替えることもできます。
公式 Changelog では、SQL Editor で次を実行する方法が案内されています。

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;

これは以後に作るテーブルの既定の権限を変えるもので、既存テーブルの権限は外しません。
いきなり本番で試すのが不安なら、開発用のプロジェクトで実行してから、テーブルを1つ作って 42501 と hint を確認してみるのがおすすめです。

ついでにやっておくとよいこと

  • 既定の権限に頼らない: 「勝手に付く」前提をやめる。migration を読めば権限が全部分かる状態にしておくと、レビューもしやすくなる
  • anon に何が付いているかを棚卸しする: 自動付与で付いていた不要な anon 権限は、外しておくと安全側になる。ただし、外す前に未ログインで使う機能が無いかを必ず確認する
  • Security Advisor を確認する: ダッシュボードの Advisors で、RLS 未設定のテーブルなどの警告も合わせて見ておく

まとめ

  • 2026-10-30 以降、public の新規テーブルには anon / authenticated / service_role の権限が自動で付かない
  • GRANT は外側の門、RLS は内側の門。GRANT が無いと RLS 以前に 42501 で弾かれる
  • 42501 は message と hint で原因を見分ける。permission denied for table なら GRANT 不足
  • 新しいテーブルの migration には、RLS・ポリシーと一緒に GRANT を書く。付けるのは必要なロール・操作だけ
  • 本番が無事でも、スキーマの正本に GRANT が無ければ作り直した環境で壊れる。今のうちに書いておく

参考

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?