Vai al contenuto

Supabase, cambio dei grant del 30 ottobre: cosa controllare

Visualizza come MarkdownApri in ClaudeApri in ChatGPTApri in Perplexity

Anche la domanda viene copiata. Se la chat si apre vuota, la incolli.

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

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

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

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:

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:

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:

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.

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

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 la guida passo passo, e la nota sugli schemi esposti copre gli schemi diversi da public, che questo cambiamento lascia stare.

© 2026 Denys Zaichenko. Articolo originale: https://madsalt.tech/it/blog/supabase-october-30-grant-change/