Torna al Blog
13 maggio 2026

Supabase RLS: Come una policy mancante ha esposto i profili di tutti gli utenti

Viktor Bulanek
Founder & CTO, Penetrify
MSc IT Security · 20+ years in security · 4x Ex-CTO

L'applicazione sembrava pronta per la produzione. Interfaccia utente pulita, integrazione Stripe funzionante, un flusso di onboarding raffinato. Era stata presentata su Product Hunt, aveva acquisito oltre 200 utenti nella prima settimana e stava già generando MRR. Il fondatore l'aveva creata in 48 ore durante un weekend di hackathon utilizzando Next.js e Supabase, lo stack preferito da chiunque voglia rilasciare velocemente.

Otto minuti dopo l'inizio della scansione Penetrify, abbiamo segnalato una vulnerabilità Critica: il record del profilo di ogni utente — nome, email, metadati dell'account — era leggibile da qualsiasi altro utente autenticato tramite l'API REST auto-generata di Supabase. Nessuna sofisticazione dell'attaccante richiesta. Basta scambiare il proprio ID utente con un altro nell'URL.

Questa è la storia di come sia successo, perché è più comune di quanto si voglia ammettere e come si presentava esattamente la soluzione.


Cos'è la Supabase Row Level Security — E cosa succede quando è disattivata

Supabase è basato su PostgreSQL ed espone le tabelle del tuo database direttamente tramite un'API REST (via PostgREST) e una libreria client JavaScript. Questo è davvero potente per lo sviluppo rapido: puoi interrogare il tuo database dal frontend senza scrivere una singola route API.

Il meccanismo che impedisce agli utenti di leggere i dati degli altri si chiama Row Level Security (RLS). RLS è una funzionalità di PostgreSQL che ti permette di definire policy che controllano quali righe un dato utente del database può SELECT, INSERT, UPDATE o DELETE. In un'applicazione Supabase, scriveresti tipicamente una policy come:

-- Consenti agli utenti di leggere solo il proprio profilo
CREATE POLICY "Users can view own profile"
  ON profiles
  FOR SELECT
  USING (auth.uid() = user_id);

Quando RLS è abilitata e una policy come questa è in vigore, una query per il profilo di un altro utente restituisce zero righe — il database applica il controllo degli accessi a livello di riga, prima che qualsiasi dato raggiunga l'applicazione.

Quando RLS è disabilitata o non esiste alcuna policy, la tabella si comporta come se ogni riga fosse pubblica per qualsiasi richiesta autenticata. L'API REST di Supabase, accessibile all'indirizzo https://[project].supabase.co/rest/v1/profiles, restituirà tutte le righe nella tabella — i dati di ogni utente — a chiunque abbia un JWT valido da quel progetto.


Cosa abbiamo trovato: Cinque vulnerabilità, due delle quali Critiche

La scansione è stata eseguita per otto minuti in modalità rapida sull'applicazione live. Ecco le vulnerabilità in ordine di gravità:

CRITICO — RLS di Supabase non abilitata sulla tabella dei profili

La tabella dei profili aveva RLS disabilitata. Qualsiasi utente autenticato poteva interrogarla tramite l'endpoint REST di Supabase e recuperare tutti i record utente. La richiesta che lo ha dimostrato:

GET /rest/v1/profiles?select=*
Authorization: Bearer [any valid user JWT]

HTTP/1.1 200 OK
[
  {"id": "uuid-1", "email": "user1@example.com", "full_name": "...", ...},
  {"id": "uuid-2", "email": "user2@example.com", "full_name": "...", ...},
  ... (tutti i 200+ record utente)
]

Ai sensi del GDPR, questa è un'esposizione di dati personali che colpisce ogni utente registrato. La chiave anonima di Supabase è incorporata nel bundle JavaScript del frontend per design — è sicuro esporla pubblicamente quando RLS è configurata correttamente, perché la chiave da sola non fornisce accesso elevato. Senza RLS, diventa una chiave master per tutti i dati utente.

CRITICO — Verifica email non applicata sugli endpoint protetti

Supabase consente le registrazioni con email e password per impostazione predefinita e invia un'email di conferma per verificare l'indirizzo. Tuttavia, le route API del backend dell'applicazione non stavano verificando se l'utente richiedente avesse confermato la propria email prima di concedere l'accesso a funzionalità protette.

Un attaccante potrebbe registrarsi con victim@example.com (l'indirizzo email di un utente reale), saltare completamente la verifica dell'email e accedere immediatamente alle rotte API protette dell'applicazione come se fosse il proprietario di quell'email. In combinazione con il problema RLS, ciò significava che un attaccante non autenticato avrebbe potuto esfiltrare l'intero database degli utenti creando un account usa e getta con qualsiasi indirizzo email.

MEDIO — IDOR su /api/export

L'applicazione disponeva di un endpoint di esportazione che accettava un ID utente come parametro di query:

GET /api/export?userId=abc123

Nessun controllo di proprietà veniva eseguito lato server. Qualsiasi utente autenticato poteva esportare i dati di qualsiasi altro utente sostituendo il proprio ID con quello di un obiettivo. Gli ID utente erano esposti nelle risposte API in tutta l'applicazione, rendendo l'enumerazione banale.

MEDIO — Nessun rate limiting sull'endpoint di login

L'endpoint di login accettava richieste di autenticazione a circa 500 richieste al secondo senza throttling o blocco. Un attacco di credential stuffing contro la base utenti dell'applicazione non avrebbe incontrato alcuna resistenza.

MEDIO — Token JWT memorizzati in localStorage senza rotazione

I JWT di Supabase erano memorizzati in localStorage, accessibili a qualsiasi JavaScript in esecuzione sulla pagina. Nessuna rotazione del token si verificava al cambio dei privilegi. Un attacco XSS riuscito su qualsiasi pagina avrebbe fornito a un attaccante una sessione persistente e valida.


Perché succede: La trappola dei default di Supabase

Questa non è una storia su uno sviluppatore negligente. È una storia sui default.

Quando si crea una nuova tabella in Supabase, RLS è disabilitato per impostazione predefinita. La documentazione di Supabase lo descrive chiaramente e ne raccomanda l'abilitazione, ma gli esempi di avvio rapido — quelli che gli sviluppatori seguono effettivamente quando costruiscono alle 2 del mattino durante un hackathon — spesso omettono RLS per brevità. Si vede una query funzionante nell'esempio, si copia il pattern, lo si rilascia.

La dashboard di Supabase mostra un'icona di avviso gialla sulle tabelle senza RLS. È facile non notarlo quando si è concentrati sull'interfaccia utente, sull'integrazione dei pagamenti, sul flusso di onboarding. Non c'è errore, nessun fallimento in fase di esecuzione, nessun sintomo evidente. Tutto funziona perfettamente. Gli utenti possono registrarsi, accedere e utilizzare l'applicazione. La vulnerabilità è completamente silenziosa.

I bug di sicurezza più pericolosi sono quelli che sembrano esattamente un comportamento corretto.

Questo pattern si presenta in quasi ogni applicazione supportata da Supabase che analizziamo, costruita per la velocità. Non perché gli sviluppatori non si preoccupino della sicurezza, ma perché la pressione temporale di un hackathon o di uno sprint di lancio non lascia spazio per leggere ogni sezione della documentazione.


La Soluzione: Due Ore, Nessuna Riscrizione del Codice

Il fondatore ha risolto tutti e cinque i problemi in meno di due ore la stessa sera in cui è arrivato il rapporto. Ecco esattamente cosa è stato fatto:

RLS — 15 minuti nella dashboard di Supabase

L'abilitazione di RLS e l'aggiunta delle politiche appropriate non hanno richiesto modifiche al codice. Nella dashboard di Supabase, sotto Editor Tabelle → profili → Politiche RLS:

-- Abilita RLS sulla tabella (un interruttore nella dashboard)
-- Quindi aggiungi le politiche:

CREATE POLICY "Gli utenti possono visualizzare il proprio profilo"
  ON profiles FOR SELECT
  USING (auth.uid() = user_id);

CREATE POLICY "Gli utenti possono aggiornare il proprio profilo"
  ON profiles FOR UPDATE
  USING (auth.uid() = user_id);

-- Per l'accesso amministrativo (se necessario):
CREATE POLICY "Il ruolo di servizio ha pieno accesso"
  ON profiles
  USING (auth.role() = 'service_role');

Dopo aver abilitato RLS e aggiunto queste politiche, la query di massa ha restituito zero righe per qualsiasi utente autenticato che effettuava una richiesta di dati non propri.

Verifica email — un controllo middleware

Le rotte API di Next.js stavano già leggendo l'oggetto utente di Supabase dal JWT ad ogni richiesta. L'aggiunta dell'applicazione della verifica email è stata un controllo di una sola riga nel middleware di autenticazione:

const { data: { user } } = await supabase.auth.getUser()
if (!user?.email_confirmed_at) {
  return res.status(403).json({ error: 'Email verification required' })
}

IDOR su /api/export — correzione middleware a una riga

La correzione consisteva nel sostituire il parametro userId fornito dall'utente con l'ID dell'utente autenticato, estratto dal JWT verificato:

// Before (vulnerable)
const userId = req.query.userId

// After (fixed)
const { data: { user } } = await supabase.auth.getUser()
const userId = user.id  // always the authenticated user — can't be spoofed

Limitazione di frequenza — la limitazione di frequenza integrata di Vercel

L'applicazione era ospitata su Vercel. L'aggiunta della limitazione di frequenza all'endpoint di login ha richiesto l'aggiunta del pacchetto @upstash/ratelimit e l'incapsulamento della rotta — circa 20 righe di codice. Il fondatore ha implementato questa correzione la mattina seguente.


L'esposizione al GDPR

Il problema RLS ha interessato tutti gli oltre 200 utenti registrati. I loro record di profilo completi — indirizzi email, nomi visualizzati e qualsiasi altro campo memorizzato nella tabella dei profili — erano leggibili da qualsiasi altro utente autenticato per tutto il tempo in cui l'applicazione era stata attiva.

Ai sensi dell'Articolo 33 del GDPR, una violazione dei dati personali che è "suscettibile di presentare un rischio per i diritti e le libertà delle persone fisiche" deve essere segnalata all'autorità di controllo competente entro 72 ore dal momento in cui se ne è venuti a conoscenza. Se questa particolare violazione abbia superato tale soglia dipenderebbe dalla sensibilità dei dati memorizzati e da quanti dati degli utenti siano stati effettivamente consultati — ma la finestra di esposizione era aperta.

Il fondatore ha ruotato tutti i valori di configurazione sensibili, abilitato RLS e risolto i problemi rimanenti la stessa sera. Nessuna prova di accesso non autorizzato è stata trovata nei log di Supabase. Il problema è stato contenuto prima che diventasse un incidente.


Come Verificare la Tua Applicazione Supabase

Se stai eseguendo un'applicazione basata su Supabase, ecco un rapido auto-audit che puoi fare in cinque minuti:

  1. Apri la dashboard di Supabase → Editor Tabelle. Qualsiasi tabella senza un'icona a scudo verde ha RLS disabilitato. Se quella tabella contiene dati utente, è probabilmente esposta.
  2. Apri gli strumenti per sviluppatori del tuo browser sulla tua app di produzione → scheda Rete → filtra per l'URL del tuo progetto Supabase. Cerca richieste a /rest/v1/[tablename]?select=*. Se vedi richieste che restituiscono molti record, verifica se dovrebbero essere limitate all'utente autenticato.
  3. Verifica il tuo flusso di verifica email. Registra un nuovo account, salta l'email di conferma e tenta di accedere direttamente alle parti protette della tua applicazione. Se riesci a raggiungere funzionalità protette senza confermare la tua email, il tuo backend non sta applicando la verifica.
  4. Verifica i tuoi endpoint di esportazione/download. Qualsiasi endpoint che accetta un ID utente come parametro dovrebbe confrontarlo con il JWT dell'utente autenticato. Se il parametro può essere liberamente sostituito per accedere ai dati di un altro utente, si tratta di un IDOR.

Questi quattro controlli richiedono meno di dieci minuti e coprono le lacune di sicurezza Supabase più comuni che riscontriamo nelle applicazioni di produzione.


Il Modello Più Ampio

Questo caso di studio non è insolito. È rappresentativo di ciò che troviamo nella maggior parte delle applicazioni basate su Supabase che sono state create durante un hackathon, uno sprint di lancio rapido o da un fondatore singolo senza un background di sicurezza.

La combinazione di strumenti veloci, un'eccellente esperienza per gli sviluppatori e impostazioni predefinite pubbliche che privilegiano la facilità d'uso rispetto alla sicurezza significa che molte applicazioni vengono rilasciate con queste lacune. Le lacune sono risolvibili — spesso in pochi minuti, raramente richiedendo qualcosa di più di una policy SQL o di un controllo middleware a una riga. La parte difficile è sapere che esistono in primo luogo.

Il team di Supabase ha svolto un lavoro significativo per rendere RLS più evidente nella dashboard e nella documentazione. Ma il divario tra "la documentazione dice di abilitare questo" e "ogni applicazione in produzione lo ha effettivamente abilitato" rimane ampio. Fino a quando i test di sicurezza automatizzati non diventeranno parte della checklist di lancio standard, questo schema continuerà ad apparire.

Frequently Asked Questions

Quali tipi di vulnerabilità rileva Penetrify?

Penetrify rileva tutte le categorie di vulnerabilità OWASP Top 10, inclusi SQL injection, XSS, CSRF, IDOR, autenticazione compromessa, configurazioni di sicurezza errate ed esposizione di dati sensibili. Testa anche la sicurezza delle API, la gestione delle sessioni e le comuni configurazioni errate in Supabase, Firebase e Bubble.

Quanto dura un test di penetrazione con IA?

Una scansione rapida si completa in 15–30 minuti. Una scansione standard dura 1–2 ore con una copertura più ampia. Una scansione approfondita può durare diverse ore per applicazioni complesse.

Cosa include un report di Penetrify?

Ogni report include un sommario esecutivo, un punteggio di sicurezza complessivo, i risultati classificati per gravità (Critico, Alto, Medio, Basso), procedure di riproduzione dettagliate e indicazioni concrete di rimediazione scritte per gli sviluppatori, non per i responsabili della conformità.

Related articles

CI/CD Penetration Testing: Come integrare la sicurezza in ogni distribuzione
Scopri come integrare il Penetration Testing nella tua pipeline CI/CD. Copre SAST, DAST, i quality gates e il testing potenziato dall'IA senza rallentare la delivery.
Scansione Autonoma delle Vulnerabilità OWASP: Come l'IA sta sostituendo i test di sicurezza basati su regole
Scopri come la scansione autonoma delle vulnerabilità OWASP utilizza l'IA per andare oltre il confronto delle firme. Tratta l'OWASP Top 10 2025, il test agentico e perché gli scanner basati su regole non sono sufficienti.
Simulazione di catene di attacco a più fasi: Perché la scansione di singole vulnerabilità non è sufficiente
Scopri come la simulazione di catene di attacco a più fasi individua gli exploit concatenati che sfuggono agli scanner di vulnerabilità. Esempi reali, mappatura MITRE ATT&CK e guida all'implementazione.

Explore more

Compare alternatives →Security glossary →CI/CD integration →Security statistics →
Torna al Blog