Basi della sicurezza
Supabase security checker: i cinque controlli da fare da soli
Un Supabase security checker legge la tua app pubblicata invece delle impostazioni del progetto. Ecco i cinque controlli che esegue e come farli da solo.

In breve
- Un Supabase security checker legge la tua app dal vivo come la legge uno sconosciuto: il bundle che consegna, le risposte delle tue tabelle, i tuoi bucket di storage, le tue intestazioni.
- Il Security Advisor di Supabase legge l'altra metà, cioè la configurazione del tuo progetto. Nessuno dei due vede quello che vede l'altro.
- Cinque controlli coprono la vista da fuori. Ognuno si lancia da un terminale, non chiede alcun account e insieme prendono una decina di minuti.
Hai cercato un Supabase security checker e ne sono usciti nove. Uno vuole il tuo repository. Uno vuole un'estensione del browser. Uno vuole accesso in lettura a tutto il tuo account Supabase, che assomiglia abbastanza alla cosa che ti preoccupava da farti esitare.
Ecco quello che nessuna di quelle pagine prodotto dice ad alta voce. I controlli sono cinque, e puoi farli tutti da solo, senza account, senza installare niente e senza nessuna delle credenziali che sarebbe pericoloso consegnare. Dieci minuti in un terminale coprono lo stesso terreno degli strumenti.
I cinque qui sotto sono quelli che il nostro scanner esegue su un progetto Supabase, scritti come comandi. Se hai costruito con Lovable, Bolt, v0, Cursor, Replit, Windsurf o Base44 e la tua app salva qualcosa, il database dietro è molto probabilmente Supabase, e queste sono le domande che gli farebbe uno sconosciuto.
Che cosa controlla davvero un Supabase security checker?
La tua app pubblicata. Le impostazioni dentro il progetto sono il lavoro di un altro strumento, e Supabase quello strumento lo fornisce già.
Il Security Advisor nella tua dashboard legge il catalogo del progetto stesso: quali tabelle hanno la Row Level Security disattivata, quali funzioni hanno un search path troppo largo, quali estensioni stanno nello schema public. Legge quello che hai configurato.
Un checker legge quello che la tua app consegna a uno sconosciuto. Il bundle JavaScript che scaricano i visitatori, la risposta che una tabella dà a una richiesta senza login, il contenuto di un bucket di storage, le intestazioni delle tue pagine. Della tua configurazione non vede nulla, e non gli serve, perché sta guardando la conseguenza.
Le due metà si separano più spesso di quanto i nomi lascino intendere. Una tabella può passare l'Advisor con l'interruttore acceso e una policy scritta, e consegnare comunque le sue righe a chiunque, perché la policy che è stata scritta fa entrare tutti. Vale una lettura a parte se è nuovo per te: l'interruttore non è la protezione.
Controllo 1: quale chiave Supabase consegna la tua app?
Apri la tua app in un browser, premi F12 e guarda una sola richiesta.
Vai sulla scheda Rete, ricarica la pagina e scrivi supabase nel filtro. La tua
app farà almeno una richiesta a un indirizzo che finisce con .supabase.co.
Cliccaci sopra. Lì dentro ci sono le due cose che ti servono per il resto
dell'articolo:
- L'indirizzo della richiesta inizia con l'URL del tuo progetto, qualcosa
come
https://abcdefghij.supabase.co. I controlli 2 e 3 puntano lì. - L'intestazione di richiesta
apikeyporta la chiave che la tua app consegna a ogni visitatore.
Copia entrambe da qualche parte, poi leggi la chiave. Una che inizia con
sb_publishable_ sta bene nella tua app e non c'è niente da sistemare. Una che
inizia con sb_secret_ non ci sta mai, e trovarla chiude la lista in anticipo:
ruotala oggi, prima di ogni altra cosa in questa pagina. I progetti più vecchi
portano una coppia senza prefisso leggibile, anon e service_role, e
distinguerle significa
leggere il ruolo dentro la chiave.
Quest'ultimo caso è raro. Sulle 30.998 app dal vivo che abbiamo scansionato ad agosto 2026, una chiave segreta Supabase nel browser è comparsa tre volte.
Finché la scheda Rete è aperta, annota i nomi di tabella che vedi in quegli indirizzi. Il controllo successivo ne ha bisogno.
Controllo 2: le tue tabelle rispondono a uno sconosciuto?
Chiedi al database quante righe darebbe a qualcuno senza login, e leggi il numero nella risposta.
curl -s -i \
-H "apikey: LA_TUA_CHIAVE_PUBBLICABILE" \
-H "Prefer: count=exact" \
-H "Range: 0-0" \
"https://IL_TUO_PROGETTO.supabase.co/rest/v1/profiles?select=count" \
| grep -i content-range
Metti la chiave e l'indirizzo del progetto del controllo 1, e il tuo nome di
tabella al posto di profiles. curl è già installato su macOS, su Linux e su
Windows recente, quindi non c'è nulla da scaricare prima.
Quella richiesta non preleva nessuna riga. select=count chiede un conteggio,
Range: 0-0 non chiede nessuna delle righe, e l'intera risposta arriva in una
sola intestazione. È la stessa richiesta che fa il nostro scanner, ed è per
questo che può girare sull'app dal vivo di qualcuno senza toccare i suoi dati.
Possono tornare quattro cose, e una sola è un riscontro:
| Cosa viene stampato | Che cosa significa |
|---|---|
content-range: */0 | Niente di leggibile senza login. Quella tabella sta facendo il suo lavoro. |
content-range: 0-0/128 | 128 righe raggiungibili da chiunque abbia la chiave che viaggia nella tua pagina. |
401 o 403 | La chiave è stata respinta prima di arrivare alla tabella. Senza risposta, non pulito. |
404 | Nessuna tabella con quel nome. O hai tirato a indovinare, o si scrive altrimenti. |
Il rifiuto è la riga con cui stare attenti. Un 401 dice che la richiesta si è
fermata prima di arrivare alla tabella, il che non ti dice niente su quanto la
tabella sia protetta, ed è la più facile delle quattro da arrotondare in un
segno di spunta.
Esegui il comando per ogni tabella che la tua app ha nominato al controllo 1,
poi per quelle ordinarie che un'app di solito ha: users, profiles,
customers, orders, messages, invoices. In quelle sei stanno i dati di
altre persone.
È il controllo che trova di più. Sulle 3.680 app con Supabase dove abbiamo potuto completarlo, 2.096 avevano almeno una tabella che rispondeva a una richiesta senza login, e in 394 di esse la tabella aperta portava un nome di persone. La misurazione completa e di che cosa è una quota è un articolo a parte.
Controllo 3: un bucket di storage elenca i propri file?
Chiedi a un bucket il suo contenuto e guarda se torna qualcosa.
curl -s -X POST \
-H "apikey: LA_TUA_CHIAVE_PUBBLICABILE" \
-H "Content-Type: application/json" \
-d '{"prefix":"","limit":1}' \
"https://IL_TUO_PROGETTO.supabase.co/storage/v1/object/list/avatars"
Manda il body. Un POST a quell'indirizzo senza niente dentro risponde "Body cannot be empty" per ogni bucket di ogni progetto, comunque sia configurato. Lo sappiamo perché il nostro controllo sui bucket è uscito così e ha passato mesi a segnalare allegramente che niente era elencabile.
Due forme di risposta:
[]significa che il bucket è chiuso, oppure che non esiste un bucket con quel nome. Da fuori non puoi sapere quale delle due, quindi un elenco vuoto non prova nulla.- Un array con un file dentro significa che uno sconosciuto può chiedere al tuo progetto un indice di quel bucket e ottenerlo.
Prova i nomi di bucket che ha usato la tua app, poi quelli comuni: avatars,
public, uploads, files, images, documents.
Elencabile e pubblico sono due impostazioni diverse tenute in due posti diversi, e la differenza decide quanto conta un bucket pubblico. L'elenco è quello da chiudere, perché risparmia a uno sconosciuto la fatica di indovinare un nome di file. Sulle 27.269 app dove abbiamo potuto chiedere, 792 hanno risposto con un elenco.
Controllo 4: il tuo codice sorgente è pubblicato accanto alla tua app?
Leggi l'ultima riga del tuo bundle JavaScript.
Torna nella scheda Rete del controllo 1, trova il file .js più grande che la
tua app ha caricato e copia il suo indirizzo. Poi:
curl -s "https://la-tua-app.example/assets/index-abc123.js" | tail -c 120
Un'ultima riga che dice //# sourceMappingURL=index-abc123.js.map significa che
la tua build ha scritto una source map e ha detto ai browser dove si trova. Se
poi l'abbia davvero pubblicata lo chiarisce una richiesta in più:
curl -s -o /dev/null -w "%{http_code}\n" \
"https://la-tua-app.example/assets/index-abc123.js.map"
200 significa che chiunque può scaricarla. Una source map riporta il
JavaScript compresso ai tuoi file originali, con la tua struttura di cartelle, i
tuoi commenti e la tua logica intatti. Quello che espone è il tuo codice, e
qualunque chiave riveli stava già accanto nel bundle, che è di cosa parlava il
controllo 1. 3.885 delle 30.987 app che abbiamo potuto esaminare pubblicano la
propria, e su alcuni builder è un'impostazione predefinita della piattaforma e
non una decisione di qualcuno:
cosa mostra una source map pubblicata.
Controllo 5: cosa manda il tuo hosting con ogni pagina
Una richiesta all'indirizzo della tua app, leggendo cosa è tornato insieme.
curl -s -i "https://la-tua-app.example" | grep -i -E \
"content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy"
Cinque intestazioni, e quelle che non vengono stampate sono quelle che ti mancano. Dicono a un browser di rifiutare la pagina se viene caricata dentro il sito di qualcun altro, di pretendere una connessione cifrata la volta successiva, e di smettere di indovinare che tipo di file ha appena ricevuto.
Quasi tutte le app falliscono questo e quasi nessuno può farci qualcosa. A 30.756 delle 30.981 app che abbiamo potuto esaminare ne mancava almeno una, perché le manda la piattaforma di hosting e chi ha l'app su un sottodominio di un builder non ha dove cambiarle. Lo contiamo e lo teniamo al massimo a medio: cosa prevedono e cosa no le intestazioni mancanti.
Come leggere quello che hai trovato
Per gravità, che non è l'ordine in cui li hai eseguiti.
Una chiave segreta nel bundle viene per prima. Salta ogni regola che hai scritto su ogni tabella, quindi ruotala prima di guardare qualsiasi altra cosa qui.
Una tabella piena di persone che risponde al controllo 2 viene dopo. In
users, profiles, orders e messages stanno i dati di altra gente, e in
questo momento sono raggiungibili. Una tabella products che risponde allo
stesso modo può essere del tutto corretta, e tu sei l'unica persona in grado di
distinguere le due.
Poi un bucket elencabile, poi le map e le intestazioni, che di solito sono i valori predefiniti della tua piattaforma e non qualcosa che hai scelto.
Una cosa da portarsi via da tutti e cinque. Un controllo che non ha ottenuto
risposta non è passato. Un 401, una richiesta andata in timeout, una tabella
di cui hai sbagliato a indovinare il nome: ognuna di queste è una domanda ancora
aperta. Il nostro scanner scrive "Non è stato possibile controllare" su quelle
righe e lascia stare il voto, e farlo a mano significa tenere quella lista da
solo.
Tutto questo descrive inoltre l'app che era dal vivo nel momento in cui hai chiesto. La Row Level Security viene spenta per far caricare una pagina, un bucket viene aperto per un caricamento, una chiave viene incollata perché una funzione esca stasera.
Cosa fare questa settimana
Cosa fare
- Esegui il controllo 2 su ogni tabella che la tua app nomina, poi su
users,profiles,customers,ordersemessages. È lì che stanno i riscontri. - Se il controllo 1 ha tirato fuori una chiave segreta, ruotala per prima. Cancellarla dal codice lascia il vecchio valore funzionante per chi lo ha già.
- Annota ogni sonda che ha ricevuto un rifiuto o nessuna risposta. Quelle restano senza risposta, e una domanda senza risposta non è un risultato pulito.
- Lascia stare le tabelle che devono essere pubbliche. Un elenco di prodotti che risponde a uno sconosciuto è la tua app che funziona bene.
- Riesegui tutti e cinque dopo un deploy e dopo che qualcuno ha cambiato una regola del database. Nessuno dei due si annuncia da solo.
Il nostro scanner di sicurezza gratuito esegue questi cinque e altri quattro su qualsiasi URL dal vivo in una ventina di secondi, senza account e senza credenziali da consegnare. Legge, non scrive mai, e dove non ottiene risposta lo scrive sulla riga.
Tutti e cinque puoi farli oggi da solo. Quello che nessun passaggio singolo può dirti è se le risposte saranno le stesse il mese prossimo, ed è per questo che abbiamo costruito Reeve Care: riesegue questi controlli secondo un calendario, ti scrive quando una risposta peggiora, e conserva backup verificati del tuo database Supabase, più i file caricati dai tuoi utenti appena colleghi una credenziale di storage. Cosa sorveglia e quanto costa.
Se un terminale non è dove vuoi stare, la guida in linguaggio semplice per le app con Supabase spiega a cosa serve ognuna di queste impostazioni e dove trovarla nella dashboard.
FAQ
Il Security Advisor di Supabase trova tutto?
Trova tutto nella sua metà. L'Advisor legge la configurazione del tuo progetto e segnala le tabelle con la Row Level Security disattivata, le funzioni con un search path troppo largo e impostazioni simili che governi dalla dashboard. Quello che non può vedere è la tua app pubblicata: quale chiave è finita nel JavaScript che scaricano i tuoi visitatori, se una policy che hai scritto ferma davvero una richiesta anonima, o quali intestazioni manda il tuo hosting. Esegui l'Advisor e i cinque controlli di questo articolo, perché guardano cose diverse.
È sicuro eseguire questi controlli sul mio progetto?
Sì. Ogni comando qui legge e nessuno scrive. Il controllo sulle tabelle chiede al database un conteggio di righe e legge il numero da un'intestazione della risposta, quindi non viene mai prelevata una riga. Quello sui bucket chiede un elenco e si ferma lì, senza scaricare alcun file. Gli ultimi due sono una normale richiesta di pagina, del tipo che i tuoi visitatori fanno tutto il giorno. Eseguirli su un progetto che non è tuo è un'altra questione, e la risposta è chiedere prima a chi lo possiede.
La mia chiave anon è nel bundle. È un problema?
No. La chiave anon nei progetti più vecchi, e sb_publishable_ in quelli nuovi, è fatta per stare nel codice che scaricano i tuoi visitatori. Nomina il tuo progetto e da sola non concede nulla; è la Row Level Security a decidere quali righe torna indietro una richiesta. La chiave che non deve mai stare lì è quella segreta, chiamata service_role nei progetti vecchi e sb_secret_ in quelli nuovi, perché salta ogni regola che hai scritto.
Che cosa significa content-range: */0?
È il tuo database che dice che a questo chiamante darà zero righe da quella tabella. L'asterisco significa che la risposta non contiene alcuna riga, e il numero dopo la barra è il conteggio che il chiamante può vedere. Quindi */0 è la risposta che vuoi da una tabella con dati personali, e 0-0/128 significa che 128 righe sono raggiungibili da chiunque abbia la chiave che viaggia nella tua app.
Mi serve la chiave service_role per provare tutto questo?
No, e un checker che te la chiede sta chiedendo la cosa sbagliata. Ogni controllo qui usa la chiave pubblicabile che è già dentro la tua app, perché è quella che avrebbe uno sconosciuto. Provare con una chiave segreta ti dice cosa raggiunge un amministratore, cosa che non è mai stata in dubbio. Non incollare una chiave service_role o sb_secret_ in nessuno scanner, incluso il nostro: niente di quello che facciamo ne ha bisogno.