// docs / baas security / supabase rls scanner
Scanner RLS Supabase: trova tabelle con sicurezza a livello di riga mancante o difettosa
La sicurezza a livello di riga (RLS) è l'unica cosa che sta tra i dati dei tuoi clienti e Internet quando metti in produzione un'applicazione supportata da Supabase. Gli strumenti di codifica IA generano codice in forma di RLS che compila, viene spedito e perde dati silenziosamente — tabelle create senza RLS abilitato, policy che leggono ma non restringono mai, predicati che confrontano una colonna con sé stessa. Questo articolo mostra cosa può dimostrare uno scanner RLS Supabase dall'esterno, le quattro forme di RLS difettosa che compaiono nelle app vibe-coded e come scansionare il tuo stesso deployment in meno di un minuto.
Cosa può dimostrare una scansione RLS esterna
Una scansione RLS passiva gira contro l'endpoint PostgREST che Supabase espone a https://[project].supabase.co/rest/v1/. Usa solo la chiave anon pubblicabile — la stessa che usa il tuo browser — e sonda metadati di lista tabelle, letture anonime e scritture anonime. Non si autentica mai come utente e non tocca mai privilegi di service-role. Qualsiasi cosa possa fare, un attaccante non autenticato su Internet può farla.
Da fuori del database, uno scanner può confermare quanto segue con alta confidenza:
- RLS è disabilitato su una tabella. PostgREST restituisce righe per un
SELECTanonimo quando RLS è spento o quando una policy lo permette. Entrambi i casi sono un risultato. - Il ruolo anonimo può elencare le tabelle. Un
GET /rest/v1/con la chiave anon restituisce lo schema OpenAPI per ogni tabella su cui il ruoloanonha un qualsiasi privilegio. Le app generate da IA spesso concedonoUSAGEsullo schema eSELECTsu ogni tabella, esponendo la mappa completa dello schema anche quando RLS nega le letture vere e proprie. - Il ruolo anonimo può inserire. Un
POSTdi sondaggio con una supposizione sulla forma delle colonne riesce se RLS non ha una policyINSERTche lo neghi — anche seSELECTè bloccato. - La chiave di service-role è nel bundle del browser. Adiacente a RLS: se uno scanner trova
SUPABASE_SERVICE_ROLE_KEYo qualsiasi JWT conrole: service_rolenel bundle JavaScript, RLS è irrilevante — chi possiede quella chiave bypassa ogni policy.
Cosa non può dimostrare una scansione esterna
Sii onesto sui limiti dello scanner. Una scansione RLS esterna non può leggere la tua tabella pg_policies, i file di migrazione o il predicato esatto di alcuna policy. Inferisce dal comportamento black-box, il che significa che a volte segnalerà un risultato che si rivela essere dato pubblico intenzionale (una tabella newsletter marketing, un catalogo prodotti pubblico). Il report FixVibe li contrassegna come confidenza media quando lo scanner non può disambiguare l'intento — rivedi il nome della tabella e decidi.
Le quattro forme di RLS difettosa che producono gli strumenti IA
Quando punti Cursor, Claude Code, Lovable o Bolt su Supabase, gli stessi quattro pattern di RLS difettosa emergono su migliaia di app. Ciascuno passa il type-check, compila e va in produzione:
Forma 1: RLS mai abilitato
La modalità di fallimento più comune. La migrazione crea la tabella ma lo sviluppatore (o lo strumento IA) dimentica ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST serve allegramente l'intera tabella a chiunque abbia la chiave anon. Correzione: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;. FORCE non è opzionale — senza di esso il proprietario della tabella (e ogni ruolo con proprietà della tabella) bypassa RLS.
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;Forma 2: RLS abilitato, nessuna policy
Un fallimento più sottile. RLS è abilitato ma nessuna policy è scritta. Il default in PostgreSQL è nega, quindi gli utenti autenticati non vedono nulla — e lo sviluppatore aggiunge USING (true) per far funzionare l'app, il che permette a tutti di leggere tutto. Correzione: scrivi una policy che limiti per auth.uid(): CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); e una policy INSERT/UPDATE/DELETE corrispondente.
CREATE POLICY "select_own"
ON public.[name]
FOR SELECT
USING (auth.uid() = user_id);Forma 3: policy confronta colonna con sé stessa
A copy-paste artefact. The developer writes <code>USING (user_id = user_id)</code> — which is always true — instead of <code>USING (auth.uid() = user_id)</code>. Type-checks pass; the policy permits every row. <strong>Fix:</strong> always compare a column to a function call (<code>auth.uid()</code>, <code>auth.jwt()->>'org_id'</code>, etc.), never to itself or to a constant.
Forma 4: policy su SELECT ma non su INSERT/UPDATE
Lo sviluppatore blocca le letture ma dimentica le scritture. Le policy RLS sono per-comando. FOR SELECT protegge solo le letture; un client anonimo può ancora INSERT se nessuna policy lo nega. Correzione: scrivi una policy per comando, o usa FOR ALL con clausole USING e WITH CHECK esplicite.
Come funziona lo scanner RLS Supabase di FixVibe
Il check baas.supabase-rls gira in tre fasi, ognuna con livelli di confidenza espliciti:
- Fase 1 — fingerprint. Lo scanner crawla l'app deployata, analizza il suo bundle JavaScript ed estrae l'URL del progetto Supabase e la chiave anon dalla configurazione runtime. Niente indovinare DNS, niente brute-force — legge ciò che legge il browser.
- Fase 2 — scoperta dello schema. Un singolo
GET /rest/v1/con la chiave anon restituisce lo schema OpenAPI per ogni tabella che il ruolo anon può vedere. Lo scanner registra i nomi delle tabelle ma non legge dati di righe in questa fase. - Fase 3 — sondaggi di lettura e scrittura. Per ogni tabella scoperta, lo scanner emette un
SELECTanonimo conlimit=1. Se restituiscono righe, RLS è permissivo. Lo scanner si ferma lì — non enumera righe, non pagina, non modifica dati. I sondaggi INSERT sono soggetti a verifica della proprietà del dominio e opt-in esplicito; non vengono mai eseguiti contro target non verificati.
Ogni risultato viene fornito con l'URL esatta della richiesta, lo stato della risposta, la forma della risposta (solo header) e il nome della tabella. Il prompt di correzione IA in fondo al risultato è un blocco SQL pronto da incollare che esegui nell'editor SQL di Supabase.
Cosa fare quando lo scanner trova qualcosa
Ogni risultato RLS è un'emergenza in produzione. Gli endpoint PostgREST pubblici vengono scansionati dagli attaccanti in minuti. La sequenza di rimedio è meccanica:
- Audita ogni tabella. Esegui
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';nell'editor SQL di Supabase. Qualsiasi riga conrowsecurity = falseè un problema. - Abilita RLS su ogni tabella pubblica. Di default, applica
ENABLE ROW LEVEL SECURITYeFORCE ROW LEVEL SECURITYsu ogni tabella creata — rendilo un template di migrazione. - Scrivi policy comando per comando. Non usare
FOR ALL USING (true). Scrivi policy esplicite per SELECT, INSERT, UPDATE, DELETE — ciascuna limitata aauth.uid()o a una colonna org-id daauth.jwt(). - Verifica con un secondo account. Registrati come utente diverso, prova a leggere i record di un altro utente via API REST direttamente. Se la risposta è
200, la policy è rotta. - Riscansiona. Dopo aver applicato la correzione, esegui di nuovo una scansione FixVibe contro la stessa URL. Il risultato
baas.supabase-rlsdovrebbe scomparire.
-- Audit every table for missing RLS. Run in the Supabase SQL editor.
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY rowsecurity, tablename;Come si confronta con altri scanner
La maggior parte degli strumenti DAST generici (Burp Suite, OWASP ZAP, Nessus) non sa cos'è PostgREST. Crawleranno la tua app, ignoreranno il percorso /rest/v1/ e riporteranno sulle pagine HTML che capiscono. Snyk e Semgrep sono strumenti di analisi statica — trovano file di migrazione nel tuo repo con chiamate RLS mancanti, ma non possono dimostrare che il database deployato è mal configurato. FixVibe sta in quel gap: passivo, consapevole del BaaS, focalizzato su ciò che un attaccante non autenticato può dimostrare dalla URL pubblica.
Domande frequenti
Lo scanner leggerà o modificherà i miei dati?
No. Le scansioni passive emettono al massimo un SELECT ... limit=1 per tabella scoperta per confermare se RLS permette letture anonime. Lo scanner registra la forma della risposta, non il contenuto delle righe. I sondaggi INSERT, UPDATE e DELETE sono soggetti a verifica della proprietà del dominio e non vengono mai eseguiti contro target non verificati.
Funziona se il mio progetto Supabase è in pausa o su un dominio personalizzato?
I progetti in pausa restituiscono 503 su ogni richiesta — lo scanner segnala il progetto come irraggiungibile. I domini personalizzati funzionano finché l'app deployata carica ancora l'SDK client Supabase nel browser; lo scanner estrae l'URL del progetto dal bundle in ogni caso.
Cosa succede se la mia chiave anon viene ruotata o la mia chiave pubblicabile cambia?
Riesegui la scansione. Lo scanner estrae di nuovo la chiave dal bundle corrente a ogni esecuzione. La rotazione invalida solo il report precedente, non lo stato delle policy del database.
Lo scanner controlla il nuovo modello di chiave pubblicabile Supabase (<code>sb_publishable_*</code>)?
Sì. Il rilevatore riconosce sia i JWT anon legacy che le più recenti chiavi sb_publishable_* e le tratta identicamente — entrambe sono destinate a essere pubbliche ed entrambe lasciano RLS come unica linea di difesa.
Prossimi passi
Esegui una scansione FixVibe gratuita contro la tua URL di produzione — il check baas.supabase-rls è abilitato su ogni piano incluso quello gratuito. Per una lettura più approfondita su cos'altro può trapelare da un progetto Supabase, vedi Chiave di service-role Supabase esposta in JavaScript e Checklist di sicurezza dei bucket Supabase Storage. Per la panoramica su tutti i provider BaaS, leggi Scanner di configurazioni errate BaaS.
