---
title: "Supabase, changement des grants du 30 octobre : que vérifier"
description: "Le 30 octobre 2026, Supabase n'ouvre plus à l'API les nouvelles tables public des projets existants. Ce qui casse, ce qui tient, 4 contrôles testés en local."
url: "https://madsalt.tech/fr/blog/supabase-october-30-grant-change/"
author: "Denys Zaichenko"
language: "fr"
published: 2026-10-26
---

# Supabase, changement des grants du 30 octobre : que vérifier

En bref

Le 30 octobre 2026, Supabase cesse d'accorder automatiquement à `anon`, `authenticated` et `service_role` les droits sur les nouvelles tables de `public`, sur tous les projets existants. Les tables que vous avez déjà continuent de fonctionner. Toute table créée après cette date répond `42501 permission denied` à `supabase-js` comme à votre secret key, tant qu'une migration ne l'a pas accordée. J'ai reproduit le basculement en local : les nouvelles tables, les nouvelles vues et les anciennes tables qu'une migration supprime puis recrée échouent toutes ; les tables existantes, les fonctions RPC et les insertions `serial` continuent de fonctionner. Vérifiez avec deux requêtes SQL et un grep ci-dessous, et corrigez avec un grant, la RLS et une policy dans la même migration, jamais avec `grant all on all tables`.

J'ai créé un [vérificateur de sécurité Supabase](https://madsalt.tech/fr/tools/supabase-security-check/) gratuit, qui lit les grants de chaque table exposée. Je voulais donc voir précisément ce que fait ce changement avant que les gens ne tombent dessus. J'ai pris une stack Supabase locale, je lui ai donné les anciens réglages par défaut, j'ai appliqué le basculement comme Supabase le décrit, puis j'ai envoyé une requête à chaque type d'objet auquel je pouvais penser. Cet article rapporte ce qui est revenu, et comment contrôler votre propre projet avant le 30.

## Que change le 30 octobre 2026 ?

Jusqu'ici, chaque table créée dans le schéma `public` recevait `select`, `insert`, `update` et `delete` pour `anon`, `authenticated` et `service_role` via les default privileges de Postgres. C'est pourquoi une nouvelle table était accessible depuis le navigateur dès sa création, protégée par la RLS ou non. L'[avis de breaking change](https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically) de Supabase supprime ces défauts en trois étapes :

| Date            | Ce qui se passe                                                                                          |
| --------------- | -------------------------------------------------------------------------------------------------------- |
| 28 avril 2026   | Les nouveaux projets peuvent désactiver « Automatically expose new tables » à la création                |
| 30 mai 2026     | Le nouveau comportement devient la valeur par défaut des nouveaux projets, déployé sur quelques semaines |
| 30 octobre 2026 | Le nouveau comportement s'applique à tous les projets existants                                          |

Sur votre projet, le basculement tient en deux instructions, les mêmes que Supabase donne pour l'activer en avance :

```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;
```

Les default privileges ne s'appliquent qu'aux objets créés ensuite. Les grants existants ne sont pas touchés, ce qui explique que vos tables actuelles continuent de fonctionner. Selon l'avis, ne sont pas concernés : les autres schémas (`auth`, `storage`, `realtime`, les vôtres), les applications qui parlent à Postgres en connexion directe (psql, un ORM, un serveur avec une chaîne de connexion) et la RLS elle-même. Le changement porte sur la Data API : REST, GraphQL et toutes les bibliothèques clientes construites dessus.

## Qu'est-ce qui casse et qu'est-ce qui continue de fonctionner ?

Mon installation : Supabase CLI 2.109.1, Postgres 17.6, PostgREST 14.14, le 26 octobre 2026. J'ai mis `auto_expose_new_tables = true` dans `config.toml` pour retrouver les anciens défauts, créé une table de deux lignes avec la RLS activée et une policy de lecture, puis exécuté les deux instructions ci-dessus. Ensuite j'ai créé de nouveaux objets et appelé chacun avec la clé publique locale (`anon`) et la secret key (`service_role`), une requête chacun.

![Ce qui ne répond plus après le basculement du 30 octobre. Supabase local, objets créés après le revoke du changelog, clé publique sauf mention. Nouvelle table avec RLS et policy: create table → policy, sans grant → 42501, HTTP 401; La même table avec la secret key: create table → policy, sans grant → 42501, HTTP 403; Nouvelle vue sur une table qui marche: create view → 42501, HTTP 401, sans indice; Ancienne table recréée par une migration: drop + create table → grants perdus → 42501, HTTP 401.](https://madsalt.tech/charts/fr/supabase-october-30-grant-change.svg)

*Ma reproduction sur une stack Supabase locale (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26 octobre 2026.*

![Ce qui continue de marcher après le basculement du 30 octobre. Même stack locale, même revoke, clé publique. Table créée avant le basculement: table existante → grants gardés → 2 lignes, HTTP 200; Nouvelle fonction appelée en RPC: create function → execute via PUBLIC → HTTP 200; Nouvelle table serial, select et insert seulement: id serial → UPDATE reste → insert, HTTP 201.](https://madsalt.tech/charts/fr/supabase-october-30-grant-change-works.svg)

*Ma reproduction sur une stack Supabase locale (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26 octobre 2026.*

| Objet, créé après le basculement sauf mention contraire                       | Clé      | Résultat                                                             |
| ----------------------------------------------------------------------------- | -------- | -------------------------------------------------------------------- |
| Table créée avant le basculement                                              | publique | HTTP 200, 2 lignes                                                   |
| Nouvelle table avec RLS et une policy `to anon, authenticated`                | publique | HTTP 401, 42501, indice `GRANT SELECT ON public.new_orders TO anon;` |
| La même nouvelle table                                                        | secret   | HTTP 403, 42501, indice `… TO service_role;`                         |
| Nouvelle vue (`security_invoker`) sur l'ancienne table                        | publique | HTTP 401, 42501, `permission denied for view`, indice `null`         |
| Copie faite avec `create table … (like old_orders including all)`             | publique | HTTP 401, 42501                                                      |
| L'ancienne table après `drop table` puis un nouveau `create table`            | publique | HTTP 401, 42501                                                      |
| Nouvelle fonction `public.ping()` via `/rest/v1/rpc`                          | publique | HTTP 200                                                             |
| Nouvelle table avec `id serial`, seulement `select, insert` accordés à `anon` | publique | insertion HTTP 201                                                   |

Ce tableau contient cinq choses à savoir avant le basculement.

**« Les tables existantes gardent leurs grants » n'est vrai que tant que la table existe.** Une migration qui supprime et recrée une table, une copie `create table … (like …)` ou une restauration depuis un dump produit une nouvelle table, et une nouvelle table ne reçoit rien. La policy est revenue avec mon `create policy`, pas les grants. Tout ce qui, dans votre pipeline, reconstruit des tables au lieu de les modifier casse dès la première exécution après le basculement.

**La secret key casse aussi.** `service_role` ignore la RLS, mais il lui faut quand même un grant. Une Edge Function, une tâche cron ou un handler de webhook qui écrit dans une nouvelle table échoue donc en 403, pas seulement le navigateur. L'indice nomme `service_role`, ce qui permet de la distinguer.

**Les vues ne donnent aucun indice.** Pour les tables, PostgREST renvoie le `GRANT` exact dont vous avez besoin. Pour ma nouvelle vue, l'indice était `null` et le message disait `permission denied for view v_old_orders`. Un outil d'IA qui corrige le 42501 à partir de l'indice n'a rien à copier ici ; le grant est `grant select on public.v_old_orders to authenticated;`, et la vue doit garder `security_invoker = true` pour que la RLS de la table s'applique toujours.

**Les fonctions ne font pas partie de ce changement.** Les deux instructions touchent les tables et les séquences. Ma nouvelle fonction est restée appelable par `anon`, `authenticated` et `service_role`, car Postgres accorde `execute` à `PUBLIC` sur toute nouvelle fonction et un revoke au niveau du schéma ne peut pas l'enlever, comme l'explique le [guide de sécurité de l'API Supabase](https://supabase.com/docs/guides/api/securing-your-api). Les appels RPC continuent de fonctionner après le 30 octobre ; cela veut aussi dire qu'une nouvelle fonction `security definer` est appelable avec votre clé publique dès qu'elle existe.

**Les insertions `serial` ont continué de fonctionner sans grant sur la séquence.** Beaucoup de conseils disent qu'un id `serial` demande `grant usage on sequence … to authenticated` après le basculement. Dans mon test, l'insertion est passée sans : le revoke retire `usage` et `select` sur les nouvelles séquences, mais `update` reste, et `update` suffit pour `nextval`. Si vous révoquez tous les default privileges sur les séquences, comme le fait le revoke complet du guide de sécurité de l'API, il faut bien le grant `usage`. Une colonne d'identité (`generated by default as identity`) n'a besoin de rien dans les deux cas.

Une dernière chose que le revoke laisse en place : la nouvelle table est quand même arrivée avec `TRUNCATE`, `REFERENCES` et `TRIGGER` pour les trois rôles, et sur Postgres 17 les défauts incluent aussi `MAINTAIN`. Aucune policy RLS ne s'applique à `TRUNCATE`. J'en parle dans la [note sur le 42501](https://madsalt.tech/fr/notes/supabase-42501-permission-denied/).

## Où vous le rencontrez avant le 30 octobre

Le basculement de votre projet hébergé a lieu le 30, mais trois endroits se comportent déjà comme après.

**`supabase db reset` avec une CLI récente.** La CLI v2.102.0 a ajouté `[api] auto_expose_new_tables` à `config.toml`. Sur la 2.109.1, clé absente, ma stack toute neuve avait déjà les nouveaux défauts : les tables n'avaient que `MAINTAIN`, `REFERENCES`, `TRIGGER` et `TRUNCATE`, les séquences seulement `UPDATE`. La mettre à `true` ramène les anciens grants et affiche `api.auto_expose_new_tables is deprecated and will be removed on 2026-10-30`. Si vos tables locales fonctionnent encore après un reset, regardez d'abord cette clé. C'est aussi l'endroit le moins cher pour tester : retirez-la, faites un reset, lancez votre application.

**Les preview branches et les projets créés depuis le 30 mai.** Ils rejouent vos migrations avec les nouveaux défauts. Toute table dont la migration n'a pas de `grant` y est déjà inaccessible. Dans la [discussion GitHub](https://github.com/orgs/supabase/discussions/45329), des gens qui ont un projet par client rapportent exactement cela : les projets en production vont bien, chaque nouveau déploiement depuis les mêmes migrations est cassé.

**Une migration de base qui ramène l'ancien défaut.** Un commentateur de la même discussion signale qu'une première migration produite par `supabase db pull` peut contenir `alter default privileges … grant all on tables to anon, authenticated, service_role`. J'ai exécuté cette instruction après le basculement : la table créée ensuite était de nouveau accessible, HTTP 200. En local, cela masque le problème. Appliquée à votre projet hébergé, elle annule discrètement le changement, et chaque future table est de nouveau ouverte à la clé publique, avec ou sans RLS. Cherchez `alter default privileges` dans vos migrations et lisez chaque résultat.

## Comment vérifier votre projet avant le 30 octobre

Quatre contrôles, du plus rapide au plus complet. Les deux premiers s'exécutent dans le SQL Editor du projet hébergé.

### 1. Votre projet a-t-il déjà basculé ?

C'est la requête du guide de sécurité de l'API Supabase. Elle liste ce que recevront les nouveaux objets créés par `postgres` dans `public` :

```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;
```

Si les lignes `tables` contiennent `SELECT, INSERT, UPDATE, DELETE`, le projet est encore sur l'ancien défaut et bascule le 30. Si elles n'affichent que `MAINTAIN, REFERENCES, TRIGGER, TRUNCATE` (ou aucune ligne), les nouvelles tables sont déjà fermées. Vous le voyez aussi dans le Dashboard, dans les réglages de la Data API, « Automatically expose new tables ».

### 2. Quelles tables ont une policy mais pas de grant ?

Une policy pour un rôle qui n'a aucun grant pour cette commande ne s'exécute jamais : la requête échoue avant la RLS. Cette requête liste ces paires. Dans mon labo, elle a trouvé la nouvelle table et l'ancienne recréée, et rien d'autre :

```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;
```

Lancez-la après chaque migration, en CI sur un `db reset` neuf ou sur le staging. Sur un projet sain, elle ne renvoie rien ; après le 30, chaque ligne est une table que votre application ne peut pas atteindre. Elle vérifie les grants au niveau de la table : une table ouverte uniquement par des grants de colonnes (`grant select (id, name) on …`) apparaît donc ici alors que ces colonnes sont lisibles.

### 3. Quelles migrations créent une table sans grant ?

La base ne montre que les tables qui existent. Une migration que vous n'avez pas encore appliquée n'est visible que dans votre dépôt. Ce grep approximatif liste chaque `create table` de `supabase/migrations` sans `grant … on` pour le même nom :

```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
```

C'est de la correspondance de texte, pas une analyse SQL : un grant qui liste plusieurs tables dans une seule instruction, ou une table nommée sans schéma à un endroit et avec schéma à un autre, donne un faux positif. Lisez chaque ligne affichée. Les tables que seul du code serveur atteint en connexion directe n'ont besoin d'aucun grant.

### 4. Tout rejouer avec les nouveaux défauts

Retirez `auto_expose_new_tables` de `config.toml`, lancez `supabase db reset`, puis exécutez votre application ou ses tests dessus. C'est votre projet après le 30, avec en plus une copie neuve de chaque table, ce qui est le cas le plus dur. Supabase liste aussi les tables concernées dans le Security Advisor et marque l'exposition à la Data API dans le Table Editor.

## Comment corriger sans ouvrir toutes les tables

Écrivez le grant, la RLS et les policies dans la migration qui crée la table, et donnez à chaque rôle uniquement ce que l'application utilise :

```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);
```

J'ai exécuté ce bloc sur la même stack locale : un utilisateur de test connecté a inséré une ligne et l'a relue (HTTP 201, puis 200), la clé publique sans session a obtenu 42501, et le contrôle 2 ci-dessus ne listait plus la table. Le `revoke all` du début supprime les `TRUNCATE`, `TRIGGER` et `REFERENCES` qui accompagnent une nouvelle table, et donne le même résultat sur un projet qui n'a pas encore basculé. N'accordez `anon` que sur des données destinées aux visiteurs non connectés, et `service_role` seulement si du code serveur utilise la secret key sur cette table. La façon dont grants et policies s'articulent sur toute une application est dans ma [checklist de sécurité d'une application Lovable](https://madsalt.tech/fr/blog/lovable-app-security-checklist/).

Ce qu'il ne faut pas faire quand le premier 42501 arrive :

- **Ne lancez pas `grant all on all tables in schema public to anon, authenticated`.** Cela remet l'ancien défaut sur toutes les tables d'un coup, y compris celles où la RLS n'a jamais été activée, qui deviennent lisibles par quiconque détient votre clé publique. La [note sur le 42501](https://madsalt.tech/fr/notes/supabase-42501-permission-denied/) montre pourquoi le grant est le premier verrou et la RLS le second.
- **Ne rajoutez pas `alter default privileges … grant`.** C'est le retour en arrière de tout le changement.
- **Ne désactivez pas la RLS pour « tester » et ne mettez pas la secret key dans le frontend.** La secret key ignore la RLS pour quiconque ouvre votre bundle ; la différence entre les clés est expliquée dans [anon key vs service role key](https://madsalt.tech/fr/blog/supabase-anon-key-vs-service-role-key/).

Si un outil d'IA écrit vos migrations, ajoutez une ligne à ses instructions : « 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 ». L'indice de l'erreur lui apprend le grant, jamais la RLS qui doit l'accompagner.

## Les questions qu'on pose sur ce changement

**Mes tables existantes vont-elles cesser de fonctionner le 30 octobre ?** Non. Les default privileges ne s'appliquent qu'aux objets créés ensuite, et dans mon test l'ancienne table a répondu HTTP 200 après le basculement. L'exception est une table que vos migrations suppriment puis recréent.

**Dois-je faire quelque chose si je ne crée jamais de tables ?** Pas le 30. Contrôlez la prochaine fois que vous, un collègue ou un outil d'IA en ajoutez une, et contrôlez tout pipeline qui crée des projets ou des branches à partir de vos migrations.

**Cela touche-t-il les Edge Functions ?** Oui, si elles utilisent `supabase-js` avec la secret key sur une nouvelle table : `service_role` a aussi besoin d'un grant. Les fonctions qui se connectent directement à Postgres ne sont pas concernées.

**Cela touche-t-il les fonctions RPC, Storage ou Auth ?** Pas dans ce changement. Les nouvelles fonctions restent appelables via `PUBLIC`, et `storage` et `auth` ne sont pas le schéma `public`.

**Puis-je basculer en avance ?** Oui. Les deux instructions du début sont le basculement ; lancez-les d'abord sur le staging, puis en production. Les tables existantes gardent leurs grants, donc une application en production ne le remarque pas, et la prochaine nouvelle table vous dit tout de suite si vos migrations sont prêtes.

## Comment le vérificateur voit cela

Le vérificateur se connecte en lecture seule et lit les grants de chaque table de vos schémas exposés : quelles tables la clé publique peut modifier sans policy, quelles tables ont la RLS désactivée, et quels default privileges donnent encore à chaque nouvelle table `TRUNCATE`, `REFERENCES` ou `TRIGGER`. Il liste aussi chaque policy dont le rôle n'a aucun grant pour cette commande : ce sont les tables que trouve le contrôle 2 ci-dessus, sauf celles où RLS est désactivé ou qui ont des grants par colonne. Il ne voit pas les fichiers de migration que vous n'avez pas appliqués : le contrôle 3 ci-dessus reste donc à vous. Si c'est la RLS qui vous inquiète, [comment tester la RLS de Supabase](https://madsalt.tech/fr/blog/how-to-test-supabase-rls/) vous guide pas à pas, et la [note sur les schémas exposés](https://madsalt.tech/fr/notes/supabase-exposed-schemas/) couvre les schémas autres que `public`, que ce changement laisse tranquilles.
