Vai al contenuto

Attivare la RLS in Supabase: cosa restituisce ogni passo

Visualizza come MarkdownApri in ClaudeApri in ChatGPTApri in Perplexity

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

In breve

In Supabase la RLS si attiva con un’istruzione per tabella, alter table public.todos enable row level security;, oppure con l’opzione “Enable RLS” nella dashboard. Le tabelle create nell’editor SQL o da una migrazione non l’hanno di default. Subito dopo l’attivazione, la tabella diventa silenziosa invece che rumorosa. Nel mio test locale la chiave pubblica e due utenti autenticati hanno ottenuto 200 [] su select, update e delete, e solo insert è fallito con 42501. La chiave segreta vedeva ancora tutte e 3 le righe. Una semplice vista sulla tabella dava ancora tutte e 3 le righe alla chiave pubblica. Dal 30 ottobre 2026 una nuova tabella ha inoltre bisogno di un grant prima che qualsiasi cosa la raggiunga: attivi quindi la RLS e scriva le policy nella stessa migrazione del grant.

Gestisco un controllo di sicurezza Supabase gratuito, e “RLS disabled” è l’esito che vedo spiegato peggio. La maggior parte delle guide su come attivare la RLS in Supabase dice che poi “la tabella diventa inaccessibile”. È vero per le letture e fuorviante per le scritture. Così ho costruito una tabella su uno stack Supabase locale e, dopo ciascuno dei passi qui sotto, le ho inviato ogni tipo di richiesta: con la chiave pubblica, con due utenti autenticati e con la chiave segreta.

Cos’è la RLS in Supabase?

La RLS (row-level security) è una funzionalità di Postgres che filtra le righe per ogni richiesta. Supabase trasforma ogni richiesta di supabase-js in una query al database eseguita con un ruolo: anon per la chiave pubblica (anon, publishable), authenticated per un utente autenticato. Con la RLS attiva, Postgres controlla le sue policy per ogni riga e restituisce, modifica o elimina solo le righe che una policy consente. Con la RLS disattivata, qualsiasi ruolo con un grant sulla tabella raggiunge ogni riga.

La chiave segreta (service role) è diversa. Il suo ruolo ha BYPASSRLS, quindi la RLS non si applica mai a essa. Per questo deve stare solo su un server. La differenza tra le due chiavi è spiegata in Supabase anon key vs service role key: cosa apre ciascuna.

Come si attiva la RLS in Supabase?

In tre modi, a seconda di come è stata creata la tabella:

  • Quando crea una tabella nel Table Editor, lasci spuntata l’opzione “Enable Row Level Security (RLS)”. Nel codice sorgente della dashboard è contrassegnata come “Recommended”, e disattivarla mostra un avviso: la tabella sarà leggibile e scrivibile pubblicamente.
  • Per una tabella esistente, la guida alla sicurezza dell’API di Supabase dice: vada in Database, poi Policies, e scelga “Enable RLS” sulla tabella.
  • In SQL, nell’editor SQL o in una migrazione:
alter table public.todos enable row level security;

La guida di Supabase è esplicita: le tabelle create nella dashboard hanno la RLS attiva di default, quelle create nell’editor SQL o con un altro strumento no. Se costruisce lo schema con migrazioni o con uno strumento di IA, controlli ogni tabella.

Cosa succede subito dopo l’attivazione della RLS?

Nulla dà errore tranne insert. Ho creato public.todos con una colonna proprietario (user_id uuid default auth.uid()), l’ho concessa ai ruoli dell’API come nei progetti più vecchi e ho aggiunto tre righe: due dell’utente A e una dell’utente B. Poi ho attivato la RLS senza alcuna policy:

Richiesta Chiave pubblica Utente A Utente B Chiave segreta
Select 200, 0 righe 200, 0 righe 200, 0 righe 200, 3 righe
Insert di una riga propria 401, errore 42501 403, errore 42501 403, errore 42501 201, riga inserita
Update di una riga 200, 0 righe 200, 0 righe 200, 0 righe 200, 1 riga
Delete di una riga 200, 0 righe 200, 0 righe 200, 0 righe 200, 1 riga

L’errore di insert è new row violates row-level security policy. Update e delete rispondono 200 con un array vuoto, quindi un’app che non controlla le righe restituite segnala “salvato” quando non è stato salvato nulla. Rilegga le sue scritture dopo aver attivato la RLS, oppure chieda un conteggio con Prefer: count=exact.

Prima della RLS, con gli stessi grant, la chiave pubblica poteva leggere tutte e 3 le righe, inserire, e modificare o eliminare qualsiasi riga. L’utente A poteva modificare ed eliminare la riga dell’utente B e passargli le proprie righe.

Come si rende di nuovo utilizzabile la tabella?

Scriva una policy per operazione. Il solito insieme per le righe proprie:

create policy "own select" on public.todos for select to authenticated
  using ((select auth.uid()) = user_id);
create policy "own insert" on public.todos for insert to authenticated
  with check ((select auth.uid()) = user_id);
create policy "own update" on public.todos for update to authenticated
  using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
create policy "own delete" on public.todos for delete to authenticated
  using ((select auth.uid()) = user_id);

Con queste, l’utente A vedeva le sue 2 righe e l’utente B la sua 1. La chiave pubblica ne vedeva ancora 0. Quando A ha provato a inserire una riga di proprietà di B, o a spostare una propria riga a B, la risposta è stata 403, 42501. Quel rifiuto viene da with check sull’update. Quando A ha provato a modificare o eliminare la riga di B, la risposta è stata 200, 0 rows. Altri schemi (profili pubblici, team, MFA) sono in Esempi di Supabase RLS policy, testati tramite API.

Un dettaglio sulla chiave segreta: una riga inserita da essa ha ottenuto user_id = null, perché dietro quella chiave non c’è alcun utente per auth.uid(). La RLS è stata saltata, ma il valore predefinito non ha fatto ciò che il nome della colonna lascia pensare.

Cosa leggeva la chiave pubblica a ogni passo?

Righe lette dalla chiave pubblica, su 3. Una tabella Supabase di 3 righe, dopo ogni passo. Nuova tabella, senza grant, RLS off (42501): 0; Grant aggiunto, RLS off: 3; RLS on, nessuna policy: 0; RLS on, policy sulle righe proprie: 0; Vista semplice su quella tabella: 3; Vista con security_invoker: 0.
Figura 1. Stack Supabase locale (CLI 2.109.1, Postgres 17.6), 28 ottobre 2026.

Due passi fanno trapelare tutto: un grant senza RLS e una semplice vista su una tabella con policy corrette. Le due sezioni seguenti spiegano entrambi i casi.

Serve ancora la RLS dopo il cambio dei grant del 30 ottobre?

Sì. Dal 30 ottobre 2026 Supabase non concede più automaticamente ai ruoli dell’API le nuove tabelle in public, nei progetti nuovi ed esistenti. Il mio stack locale si comporta già così. Una tabella appena creata con la RLS disattivata ha risposto 42501 a ogni ruolo, chiave segreta compresa, quindi nulla è trapelato. I dettagli sono in Supabase, cambio dei grant del 30 ottobre: cosa controllare.

Ma il grant torna non appena l’app ha bisogno della tabella, e da lì in poi la RLS è l’unica cosa tra la chiave pubblica e ogni riga. Metta quindi tutto in una sola migrazione, in quest’ordine:

create table public.todos (...);
alter table public.todos enable row level security;
create policy "own select" on public.todos ...;
grant select, insert, update, delete on table public.todos to authenticated;

Conceda solo ai ruoli che hanno bisogno della tabella. Quando ho revocato tutto ad anon, il select della chiave pubblica è passato da 200 [] a 401, 42501. Il grant è la prima serratura, la RLS la seconda.

Perché una vista fa comunque trapelare righe?

Perché una vista viene eseguita con i diritti del suo proprietario, e in Supabase è postgres, che aggira la RLS. Con le policy per le righe proprie attive su todos, un semplice create view public.todos_view as select * from public.todos ha restituito tutte e 3 le righe alla chiave pubblica e all’utente B.

Ricreata con with (security_invoker = true), la stessa vista ha restituito 0 righe alla chiave pubblica, 2 ad A e 1 a B, esattamente come la tabella:

create view public.todos_view with (security_invoker = true) as
  select * from public.todos;

L’Advisor di Supabase segnala la versione semplice come security_definer_view; cosa significa e come correggerla è in Supabase security definer view: perché aggira la RLS.

Come si attiva la RLS automaticamente su ogni nuova tabella?

Con un event trigger. La guida agli event trigger di Supabase fornisce una funzione rls_auto_enable() e un trigger ensure_rls che scatta dopo create table, create table as e select into in public. L’ho installato come pubblicato e ho ottenuto:

  • Una tabella creata dopo con create table aveva la RLS attiva senza alcun alter. La chiave pubblica ha ottenuto 200 [] su select e 401 su insert.
  • Anche una tabella creata con create table as select aveva la RLS attiva.
  • Una tabella creata prima del trigger è rimasta senza. Il trigger non è retroattivo.

Senza il trigger, una seconda tabella creata accanto a quella protetta aveva la RLS disattivata, e una volta concessa, la chiave pubblica la leggeva e vi scriveva liberamente. Il trigger copre inoltre solo public; gli altri schemi esposti richiedono una riga propria nella funzione.

FORCE ROW LEVEL SECURITY aiuta?

Non per i ruoli che la preoccupano. alter table ... force row level security fa valere la RLS anche per il proprietario della tabella. In Supabase sia postgres sia service_role hanno BYPASSRLS, quindi dopo force il proprietario contava ancora 3 righe, la chiave segreta vedeva ancora tutto, e una vista di proprietà di postgres continuava a far trapelare. Ha cambiato qualcosa solo per un ruolo proprietario creato senza BYPASSRLS: 2 righe prima, 0 dopo.

Come si controlla il proprio progetto?

  • Nell’editor SQL, elenchi le tabelle in public con la RLS disattivata:
select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
  and c.relkind in ('r', 'p')
  and not c.relrowsecurity
order by 1;

Come ho testato

  • Supabase CLI 2.109.1 su un portatile: Postgres 17.6, PostgREST 14.14, Auth 2.192.0. Due utenti autenticati con password, più le chiavi locali publishable e segreta.
  • Una tabella, public.todos, riempita di nuovo con le stesse 3 righe prima di ogni richiesta. Ogni passo è stato applicato come SQL, e ogni richiesta è passata dall’API REST, con codice di stato e corpo registrati.
  • I grant dei passi da 2 a 5 sono stati aggiunti a mano, per corrispondere a un progetto creato prima del 30 ottobre 2026.

Limiti. Un solo stack locale, dati inventati, 28 ottobre 2026. I passi della dashboard sono citati dalla documentazione di Supabase, non eseguiti cliccando. Le tabelle create dal ruolo proprio della dashboard possono ricevere altri grant di default. Non l’ho testato, né Storage, Realtime o GraphQL.

Non è sicuro che le sue tabelle siano bloccate?

Guardo volentieri uno schema insieme a lei. Nessuna proposta commerciale.

Mi contatti

© 2026 Denys Zaichenko. Articolo originale: https://madsalt.tech/it/blog/how-to-enable-rls-in-supabase/