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