---
title: "Supabase, cambio dei grant del 30 ottobre: cosa controllare"
description: "Il 30 ottobre 2026 Supabase non apre più all'API le nuove tabelle public dei progetti esistenti. Cosa si rompe, cosa regge, 4 controlli testati in locale."
url: "https://madsalt.tech/it/blog/supabase-october-30-grant-change/"
author: "Denys Zaichenko"
language: "it"
published: 2026-10-26
---

# Supabase, cambio dei grant del 30 ottobre: cosa controllare

In breve

Il 30 ottobre 2026 Supabase smette di assegnare automaticamente a `anon`, `authenticated` e `service_role` i permessi sulle nuove tabelle di `public`, su ogni progetto esistente. Le tabelle che ha già continuano a funzionare. Ogni tabella creata dopo risponde `42501 permission denied` a `supabase-js` e alla sua secret key finché una migration non la concede. Ho riprodotto il passaggio in locale: le nuove tabelle, le nuove view e le vecchie tabelle che una migration elimina e ricrea falliscono tutte; le tabelle esistenti, le funzioni RPC e gli insert `serial` continuano a funzionare. Controlli con due query SQL e un grep qui sotto e corregga con un grant, la RLS e una policy nella stessa migration, mai con `grant all on all tables`.

Gestisco un [controllo di sicurezza Supabase](https://madsalt.tech/it/tools/supabase-security-check/) gratuito che legge i grant di ogni tabella esposta, quindi volevo vedere esattamente cosa fa questo cambiamento prima che la gente ci inciampi. Ho preso uno stack Supabase locale, gli ho dato i vecchi default, ho applicato il passaggio come lo descrive Supabase e ho mandato una richiesta a ogni tipo di oggetto che mi veniva in mente. Questo articolo racconta cosa è tornato indietro e come controllare il suo progetto prima del 30.

## Cosa cambia il 30 ottobre 2026?

Finora ogni tabella creata nello schema `public` riceveva `select`, `insert`, `update` e `delete` per `anon`, `authenticated` e `service_role` tramite i default privileges di Postgres. Ecco perché una nuova tabella era raggiungibile dal browser appena esisteva, protetta dalla RLS o no. L'[avviso di breaking change](https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically) di Supabase toglie quei default in tre passaggi:

| Data            | Cosa succede                                                                                  |
| --------------- | --------------------------------------------------------------------------------------------- |
| 28 aprile 2026  | I nuovi progetti possono disattivare «Automatically expose new tables» alla creazione         |
| 30 maggio 2026  | Il nuovo comportamento diventa il default dei nuovi progetti, distribuito in alcune settimane |
| 30 ottobre 2026 | Il nuovo comportamento viene applicato a tutti i progetti esistenti                           |

Sul suo progetto il passaggio sono due istruzioni, le stesse che Supabase indica per attivarlo in anticipo:

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

I default privileges valgono solo per gli oggetti creati dopo. I grant esistenti non vengono toccati, ed è per questo che le sue tabelle attuali continuano a funzionare. Secondo l'avviso non sono interessati: gli altri schemi (`auth`, `storage`, `realtime`, i suoi), le app che parlano a Postgres con una connessione diretta (psql, un ORM, un server con una stringa di connessione) e la RLS stessa. Il cambiamento riguarda la Data API: REST, GraphQL e ogni libreria client costruita sopra.

## Cosa si rompe e cosa continua a funzionare?

Il mio setup: Supabase CLI 2.109.1, Postgres 17.6, PostgREST 14.14, il 26 ottobre 2026. Ho impostato `auto_expose_new_tables = true` in `config.toml` per riavere i vecchi default, ho creato una tabella con due righe, RLS attiva e una policy di lettura, poi ho eseguito le due istruzioni qui sopra. Dopo ho creato nuovi oggetti e ho chiamato ciascuno con la chiave pubblica locale (`anon`) e con la secret key (`service_role`), una richiesta per volta.

![Cosa smette di rispondere dopo il passaggio del 30 ottobre. Supabase locale, oggetti creati dopo il revoke del changelog, chiave pubblica salvo indicazione. Nuova tabella con RLS e policy: create table → policy, senza grant → 42501, HTTP 401; La stessa tabella con la secret key: create table → policy, senza grant → 42501, HTTP 403; Nuova view su una tabella che funziona: create view → 42501, HTTP 401, senza hint; Vecchia tabella ricreata da una migration: drop + create table → grant persi → 42501, HTTP 401.](https://madsalt.tech/charts/it/supabase-october-30-grant-change.svg)

*La mia riproduzione su uno stack Supabase locale (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26 ottobre 2026.*

![Cosa continua a funzionare dopo il passaggio del 30 ottobre. Stesso stack locale, stesso revoke, chiave pubblica. Tabella creata prima del passaggio: tabella esistente → grant mantenuti → 2 righe, HTTP 200; Nuova funzione chiamata via RPC: create function → execute via PUBLIC → HTTP 200; Nuova tabella serial, solo select e insert: id serial → resta UPDATE → insert, HTTP 201.](https://madsalt.tech/charts/it/supabase-october-30-grant-change-works.svg)

*La mia riproduzione su uno stack Supabase locale (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26 ottobre 2026.*

| Oggetto, creato dopo il passaggio salvo diversa indicazione            | Chiave   | Risultato                                                                  |
| ---------------------------------------------------------------------- | -------- | -------------------------------------------------------------------------- |
| Tabella creata prima del passaggio                                     | pubblica | HTTP 200, 2 righe                                                          |
| Nuova tabella con RLS e una policy `to anon, authenticated`            | pubblica | HTTP 401, 42501, suggerimento `GRANT SELECT ON public.new_orders TO anon;` |
| La stessa nuova tabella                                                | secret   | HTTP 403, 42501, suggerimento `… TO service_role;`                         |
| Nuova view (`security_invoker`) sulla vecchia tabella                  | pubblica | HTTP 401, 42501, `permission denied for view`, suggerimento `null`         |
| Copia fatta con `create table … (like old_orders including all)`       | pubblica | HTTP 401, 42501                                                            |
| La vecchia tabella dopo `drop table` e un nuovo `create table`         | pubblica | HTTP 401, 42501                                                            |
| Nuova funzione `public.ping()` su `/rest/v1/rpc`                       | pubblica | HTTP 200                                                                   |
| Nuova tabella con `id serial`, solo `select, insert` concessi a `anon` | pubblica | insert HTTP 201                                                            |

In quella tabella ci sono cinque cose da sapere prima del passaggio.

**«Le tabelle esistenti mantengono i grant» vale solo finché la tabella esiste.** Una migration che elimina e ricrea una tabella, una copia con `create table … (like …)` o un ripristino da dump produce una tabella nuova, e una tabella nuova non riceve nulla. La policy è tornata con il mio `create policy`, i grant no. Tutto ciò che nella sua pipeline ricostruisce le tabelle invece di modificarle si rompe alla prima esecuzione dopo il passaggio.

**Si rompe anche la secret key.** `service_role` salta la RLS, ma ha comunque bisogno di un grant. Quindi una Edge Function, un job cron o un handler di webhook che scrive su una nuova tabella fallisce con 403, non solo il browser. Il suggerimento nomina `service_role`, ed è così che si distingue.

**Le view non danno alcun suggerimento.** Per le tabelle PostgREST restituisce il `GRANT` esatto che serve. Per la mia nuova view il suggerimento era `null` e il messaggio diceva `permission denied for view v_old_orders`. Uno strumento di IA che corregge il 42501 dal suggerimento qui non ha nulla da copiare; il grant è `grant select on public.v_old_orders to authenticated;`, e la view deve mantenere `security_invoker = true` perché la RLS della tabella continui ad applicarsi.

**Le funzioni non fanno parte di questo cambiamento.** Le due istruzioni toccano tabelle e sequenze. La mia nuova funzione è rimasta chiamabile da `anon`, `authenticated` e `service_role`, perché Postgres concede `execute` a `PUBLIC` su ogni nuova funzione e un revoke a livello di schema non può toglierlo, come spiega la [guida alla sicurezza dell'API di Supabase](https://supabase.com/docs/guides/api/securing-your-api). Le chiamate RPC continuano a funzionare dopo il 30 ottobre; significa anche che una nuova funzione `security definer` è chiamabile con la sua chiave pubblica nel momento in cui esiste.

**Gli insert `serial` hanno continuato a funzionare senza grant sulla sequenza.** Molti consigli dicono che un id `serial` richiede `grant usage on sequence … to authenticated` dopo il passaggio. Nel mio test l'insert è passato senza: il revoke toglie `usage` e `select` sulle nuove sequenze, ma `update` resta, e `update` basta per `nextval`. Se revoca tutti i default privileges sulle sequenze, come fa il revoke completo della guida alla sicurezza dell'API, il grant `usage` serve davvero. Una colonna identity (`generated by default as identity`) non ha bisogno di nulla in entrambi i casi.

Un'ultima cosa che il revoke lascia: la nuova tabella è comunque arrivata con `TRUNCATE`, `REFERENCES` e `TRIGGER` per tutti e tre i ruoli, e su Postgres 17 i default includono anche `MAINTAIN`. Nessuna policy RLS si applica a `TRUNCATE`. Ne parlo nella [nota sul 42501](https://madsalt.tech/it/notes/supabase-42501-permission-denied/).

## Dove lo incontra prima del 30 ottobre

Il passaggio sul suo progetto ospitato avviene il 30, ma tre posti si comportano già come dopo.

**`supabase db reset` con una CLI recente.** La CLI v2.102.0 ha aggiunto `[api] auto_expose_new_tables` a `config.toml`. Sulla 2.109.1, con la chiave omessa, il mio stack nuovo aveva già i nuovi default: le tabelle avevano solo `MAINTAIN`, `REFERENCES`, `TRIGGER` e `TRUNCATE`, le sequenze solo `UPDATE`. Impostarla a `true` riporta i vecchi grant e stampa `api.auto_expose_new_tables is deprecated and will be removed on 2026-10-30`. Se le sue tabelle locali funzionano ancora dopo un reset, controlli prima questa chiave. È anche il posto più economico per provare: la tolga, faccia il reset, avvii la sua app.

**Preview branch e progetti creati dal 30 maggio.** Rieseguono le sue migration con i nuovi default. Ogni tabella la cui migration non ha un `grant` lì è già irraggiungibile. Nella [discussione su GitHub](https://github.com/orgs/supabase/discussions/45329) chi ha un progetto per cliente riporta esattamente questo: i progetti in esecuzione vanno bene, ogni nuovo deploy dalle stesse migration è rotto.

**Una migration di base che riporta il vecchio default.** Un commentatore della stessa discussione fa notare che una prima migration creata con `supabase db pull` può contenere `alter default privileges … grant all on tables to anon, authenticated, service_role`. Ho eseguito quell'istruzione dopo il passaggio: la tabella creata subito dopo era di nuovo raggiungibile, HTTP 200. In locale questo nasconde il problema. Applicata al progetto ospitato, annulla in silenzio il cambiamento e ogni tabella futura è di nuovo aperta alla chiave pubblica, con o senza RLS. Cerchi `alter default privileges` nelle sue migration e legga ogni risultato.

## Come controllare il suo progetto prima del 30 ottobre

Quattro controlli, dal più rapido al più completo. I primi due si eseguono nello SQL Editor del progetto ospitato.

### 1. Il suo progetto è già passato?

È la query della guida alla sicurezza dell'API di Supabase. Elenca cosa riceveranno i nuovi oggetti creati da `postgres` in `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;
```

Se le righe `tables` contengono `SELECT, INSERT, UPDATE, DELETE`, il progetto è ancora sul vecchio default e passa il 30. Se mostrano solo `MAINTAIN, REFERENCES, TRIGGER, TRUNCATE` (o nessuna riga), le nuove tabelle sono già chiuse. Lo vede anche nella Dashboard, nelle impostazioni della Data API, «Automatically expose new tables».

### 2. Quali tabelle hanno una policy ma nessun grant?

Una policy per un ruolo che non ha alcun grant per quel comando non viene mai eseguita: la richiesta fallisce prima della RLS. Questa query elenca quelle coppie. Nel mio laboratorio ha trovato la nuova tabella e la vecchia ricreata, e nient'altro:

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

La esegua dopo ogni migration, in CI su un `db reset` pulito o in staging. Su un progetto sano non restituisce nulla; dopo il 30 ogni riga è una tabella che la sua app non raggiunge. Controlla i grant a livello di tabella, quindi una tabella aperta solo con grant sulle colonne (`grant select (id, name) on …`) compare qui anche se quelle colonne sono leggibili.

### 3. Quali migration creano una tabella senza grant?

Il database mostra solo le tabelle che esistono. Una migration che non ha ancora applicato è visibile solo nel suo repository. Questo grep approssimativo elenca ogni `create table` in `supabase/migrations` senza un `grant … on` per lo stesso nome:

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

È confronto di testo, non parsing SQL: un grant che elenca più tabelle in una sola istruzione, o una tabella nominata senza schema in un punto e con schema in un altro, dà un falso positivo. Legga ogni riga stampata. Le tabelle raggiunte solo da codice server con una connessione diretta non hanno bisogno di alcun grant.

### 4. Rieseguire tutto con i nuovi default

Tolga `auto_expose_new_tables` da `config.toml`, esegua `supabase db reset` e lanci la sua app o i suoi test. Quello è il suo progetto dopo il 30, più una copia nuova di ogni tabella, che è il caso più difficile. Supabase elenca inoltre le tabelle interessate nel Security Advisor e segna l'esposizione alla Data API nel Table Editor.

## Come correggere senza aprire ogni tabella

Scriva il grant, la RLS e le policy nella migration che crea la tabella, e dia a ogni ruolo solo ciò che l'app usa:

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

Ho eseguito questo blocco sullo stesso stack locale: un utente di test autenticato ha inserito una riga e l'ha riletta (HTTP 201, poi 200), la chiave pubblica senza sessione ha ottenuto 42501 e il controllo 2 qui sopra non elencava più la tabella. Il `revoke all` iniziale toglie `TRUNCATE`, `TRIGGER` e `REFERENCES` che arrivano con una nuova tabella, e dà lo stesso risultato su un progetto che non è ancora passato. Conceda `anon` solo sui dati pensati per i visitatori non autenticati, e `service_role` solo se codice server usa la secret key su questa tabella. Come grant e policy si incastrano in un'intera app è nella mia [checklist di sicurezza per le app Lovable](https://madsalt.tech/it/blog/lovable-app-security-checklist/).

Cosa non fare quando arriva il primo 42501:

- **Non esegua `grant all on all tables in schema public to anon, authenticated`.** Rimette il vecchio default su tutte le tabelle insieme, comprese quelle in cui la RLS non è mai stata attivata, che diventano leggibili da chiunque abbia la sua chiave pubblica. La [nota sul 42501](https://madsalt.tech/it/notes/supabase-42501-permission-denied/) mostra perché il grant è la prima serratura e la RLS la seconda.
- **Non riaggiunga `alter default privileges … grant`.** È il rollback dell'intero cambiamento.
- **Non disattivi la RLS per «provare» e non metta la secret key nel frontend.** La secret key salta la RLS per chiunque apra il suo bundle; la differenza tra le chiavi è in [anon key vs service role key](https://madsalt.tech/it/blog/supabase-anon-key-vs-service-role-key/).

Se uno strumento di IA scrive le sue migration, aggiunga una riga alle sue istruzioni: «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». Il suggerimento nell'errore gli insegna il grant, mai la RLS che deve accompagnarlo.

## Le domande che si fanno su questo cambiamento

**Le mie tabelle esistenti smetteranno di funzionare il 30 ottobre?** No. I default privileges valgono per gli oggetti creati dopo, e nel mio test la vecchia tabella ha risposto HTTP 200 dopo il passaggio. L'eccezione è una tabella che le sue migration eliminano e ricreano.

**Devo fare qualcosa se non creo mai tabelle?** Non il 30. Controlli la prossima volta che lei, un collega o uno strumento di IA ne aggiunge una, e controlli ogni pipeline che crea progetti o branch dalle sue migration.

**Riguarda le Edge Function?** Sì, se usano `supabase-js` con la secret key su una nuova tabella: anche `service_role` ha bisogno di un grant. Le funzioni che si collegano direttamente a Postgres non sono interessate.

**Riguarda le funzioni RPC, Storage o Auth?** Non in questo cambiamento. Le nuove funzioni restano chiamabili tramite `PUBLIC`, e `storage` e `auth` non sono lo schema `public`.

**Posso fare il passaggio in anticipo?** Sì. Le due istruzioni all'inizio sono il passaggio; le esegua prima in staging, poi in produzione. Le tabelle esistenti mantengono i grant, quindi un'app in esecuzione non se ne accorge, e la prossima nuova tabella le dice subito se le sue migration sono pronte.

## Come lo vede il controllo

Il controllo si collega in sola lettura e legge i grant di ogni tabella nei suoi schemi esposti: su quali tabelle la chiave pubblica può scrivere senza policy, quali tabelle hanno la RLS disattivata e quali default privileges danno ancora a ogni nuova tabella `TRUNCATE`, `REFERENCES` o `TRIGGER`. Elenca anche ogni policy il cui ruolo non ha il grant per quel comando: sono le tabelle che trova il controllo 2 qui sopra, tranne quelle con RLS disattivato o con grant per colonna. Non vede i file di migration che non ha applicato, quindi il controllo 3 qui sopra resta suo da eseguire. Se è la RLS la parte di cui non è sicuro, [come testare la RLS di Supabase](https://madsalt.tech/it/blog/how-to-test-supabase-rls/) la guida passo passo, e la [nota sugli schemi esposti](https://madsalt.tech/it/notes/supabase-exposed-schemas/) copre gli schemi diversi da `public`, che questo cambiamento lascia stare.
