---
title: "Attivare la RLS in Supabase: cosa restituisce ogni passo"
description: "Come attivare la RLS in Supabase da dashboard o in SQL, testato in locale: cosa ricevono la chiave pubblica, due utenti e la chiave segreta a ogni passo."
url: "https://madsalt.tech/it/blog/how-to-enable-rls-in-supabase/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/it/about/"
language: "it"
published: 2026-11-04
---

# Attivare la RLS in Supabase: cosa restituisce ogni passo

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](https://madsalt.tech/it/tools/supabase-security-check/) 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](https://madsalt.tech/it/blog/supabase-anon-key-vs-service-role-key/).

## 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](https://github.com/supabase/supabase/commit/33bd798d142183e162c25debcc50c8c711e68f66) è 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](https://supabase.com/docs/guides/api/securing-your-api) di Supabase dice: vada in Database, poi Policies, e scelga "Enable RLS" sulla tabella.
- **In SQL**, nell'editor SQL o in una migrazione:

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

```sql
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](https://madsalt.tech/it/blog/supabase-rls-policy-examples/).

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.](https://madsalt.tech/charts/it/how-to-enable-rls-in-supabase.svg)

*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](https://madsalt.tech/it/blog/supabase-october-30-grant-change/).

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:

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

```sql
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](https://madsalt.tech/it/notes/supabase-security-definer-view/).

## Come si attiva la RLS automaticamente su ogni nuova tabella?

Con un event trigger. La [guida agli event trigger](https://supabase.com/docs/guides/database/postgres/event-triggers) 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:

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

- L'Advisor nella dashboard segnala lo stesso come `rls_disabled_in_public`; cosa significa quell'errore è in [Supabase "RLS disabled in public": cosa significa](https://madsalt.tech/it/notes/supabase-rls-disabled-in-public/).
- Il [controllo di sicurezza Supabase](https://madsalt.tech/it/tools/supabase-security-check/) gratuito legge il suo progetto in sola lettura ed elenca le tabelle senza RLS che l'API può raggiungere, le viste che aggirano la RLS e le policy che consentono tutto a tutti, con l'SQL di correzione per ciascuna.
- Uno scanner non può sapere a chi appartengono le righe. Per dimostrarlo, invii richieste come due utenti, come sopra. Il metodo completo per un'app con ruoli è in [Come testare la Supabase RLS: 30 permessi su 87 inutili](https://madsalt.tech/it/blog/how-to-test-supabase-rls/).

## 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](https://madsalt.tech/it/#contact)
