Basi della sicurezza
Quali chiavi API sono sicure nel browser e quali no
La tua chiave anon di Supabase è fatta per essere pubblica. La chiave service_role no, e ignora ogni regola che imposti. Come distinguerle.
In breve
- Trovare una chiave API nel codice della tua app non è automaticamente un problema. Alcune chiavi sono fatte per stare lì.
- Le chiavi pubblicabili sono sicure nel browser. Quelle segrete no: una chiave segreta nel browser è una cassa aperta.
- Due controlli le separano in meno di un minuto: leggere il prefisso e, su una vecchia chiave Supabase, decodificare il ruolo.
Se hai costruito la tua app con Lovable, Bolt, v0, Cursor o Replit, prima o poi succede questo: qualcuno apre il sito, preme F12 e ti avvisa che la tua chiave API è «esposta». È una notizia inquietante, per un'app di cui non sai leggere il codice.
Ecco il punto che guida dopo guida racconta male: alcune di quelle chiavi devono stare lì. Trattare ogni chiave visibile come una falla porta o ad allarmarsi per nulla o, molto peggio, a imparare a ignorare l'avviso, e a ignorarlo anche il giorno in cui conta davvero.
È grave che la mia chiave API sia visibile?
Di solito no. Dipende interamente da quale chiave sia.
Ogni servizio che parla con un browser distribuisce due tipi diversi di credenziale. Uno è un indirizzo. L'altro è il mazzo di chiavi dell'edificio. Entrambi si chiamano «chiave API», ed è da lì che nasce quasi tutta la confusione.
Un indirizzo si può pubblicare senza rischi. Dice solo a quale progetto appartiene una richiesta; il controllo dei permessi avviene altrove. Un mazzo di chiavi no, perché è il controllo dei permessi: chi lo possiede può fare tutto ciò che consente, da qualunque posto.
La tua app ha bisogno dell'indirizzo nel browser per funzionare. Non dovrebbe mai avere bisogno lì del mazzo di chiavi.
Perché la tua app manda chiavi al browser
Perché la richiesta parte dal browser del tuo visitatore, non dal tuo server.
Quando la tua app carica l'elenco degli ordini dei tuoi utenti, quella richiesta parte direttamente dal browser del visitatore verso il tuo fornitore di database. Deve dire a quale progetto appartiene, e quell'identificativo deve stare nella pagina, perché è da lì che la richiesta viene fatta.
Non esiste una versione in cui quell'identificativo resti segreto. Arriva a ogni visitatore per progetto. Ed è esattamente per questo che i fornitori dividono le credenziali in due: sanno che una delle due diventerà pubblica, quindi l'hanno resa innocua.
La sicurezza non nasce dal nascondere l'indirizzo. Nasce dalle regole che imposti dall'altra parte: nel caso di Supabase la Row Level Security, che decide riga per riga chi può vedere cosa. Sono quelle regole che vale la pena controllare, e attivarle non è la stessa cosa che essere protetti.
Le due famiglie di chiavi
| Fornitore | Chiave | Sicura nel browser? | Cosa fa |
|---|---|---|---|
| Supabase | sb_publishable_…, o anon nei progetti più vecchi | Al suo posto | Dice di quale progetto si tratta. Ogni richiesta resta filtrata dalle tue regole di Row Level Security. |
| Supabase | sb_secret_…, o service_role nei progetti più vecchi | Mai | Aggira del tutto la Row Level Security. Legge e scrive ogni riga di ogni tabella, qualunque cosa dicano le tue regole. |
| Stripe | pk_live_… (pubblicabile) | Al suo posto | Crea i moduli di pagamento. Non può spostare denaro né leggere i clienti. |
| Stripe | sk_live_… (segreta) | Mai | Accesso completo all'account: addebiti, rimborsi, pagamenti, schede clienti. |
| OpenAI / Anthropic | qualsiasi chiave | Mai | Non esiste una variante pubblicabile. Ogni chiave addebita direttamente sul tuo account. |
Lo schema vale oltre questi tre. Se un fornitore offre un solo tipo di chiave, dai per scontato che sia segreta e che il suo posto sia un server.
Come distinguerle in 60 secondi
Su Stripe, e sulla maggior parte dei fornitori, basta il prefisso. pk_ è
pubblicabile e sicura. sk_ è segreta e non lo è. Alcuni fornitori mettono
_test_ o _live_ in mezzo: una chiave di test trapelata è un problema molto
più piccolo di una di produzione, ma rigenerale entrambe.
Su Supabase dipende da quanto è vecchio il tuo progetto. Supabase ha emesso due forme diverse di chiave, ed entrambe oggi girano in app funzionanti.
Progetti nuovi: leggi il prefisso, esattamente come su Stripe.sb_publishable_… è quella che sta bene nella tua app. sb_secret_… è quella da
rigenerare oggi. Non c'è nulla da decodificare: a cosa serve la chiave è scritto
davanti.
Cosa è cambiato e cosa significa per la tua app,
se non le hai ancora incontrate.
Progetti più vecchi: bisogna guardare dentro la chiave. La coppia originale,
anon e service_role, sono JWT: tre blocchi di caratteri incomprensibili
separati da punti, e quello centrale è informazione leggibile, non cifratura. Lì
c'è scritto il ruolo della chiave in chiaro.
In nessuno dei due casi serve uno strumento. Apri Settings → API Keys nel pannello di Supabase e ti dice quale è quale. Se preferisci controllare la chiave che hai davvero trovato nella tua app, la sezione centrale di una vecchia chiave si decodifica in qualcosa di simile.
{
"iss": "supabase",
"ref": "abcdefghij…",
"role": "anon", ← è questa la parola che conta
"iat": 1750000000
}
"role": "anon" è quella sicura. "role": "service_role" è quella da
rigenerare oggi. In una vecchia chiave quell'unica parola è tutta la differenza,
ed è il motivo per cui uno scanner che si limita a cercare stringhe a forma di
chiave restituisce rumore invece di una risposta: non può dirti quale di due
chiavi identiche hai effettivamente pubblicato.
Se preferisci non passare la tua app chiave per chiave, la nostra scansione gratuita legge il tuo sito dal vivo e ti dice quali di queste si vedono da fuori. Ci vogliono una ventina di secondi e non serve un account: scansiona la tua app.
Cosa succede davvero quando una chiave segreta trapela
Ogni riga del tuo database diventa leggibile e scrivibile da chi ha trovato la
chiave, comprese tabelle che non hai mai esposto alla tua app e i dati personali
dei tuoi utenti. La Row Level Security non si applica a una chiave segreta di
Supabase (sb_secret_…, o service_role in un progetto più vecchio). È lo scopo
di quella chiave.
Nemmeno il danno è teorico. Di solito lo si scopre come un messaggio all'assistenza su dati che sono cambiati da soli, o come una tabella improvvisamente vuota.
Una chiave sk_live_ di Stripe trapelata significa rimborsi, addebiti e schede
clienti. Una chiave trapelata di un fornitore di IA significa una fattura, a
volte molto salata, che arriva prima che qualcuno se ne accorga.
Niente di tutto questo richiede un aggressore sofisticato. Ci sono scanner automatici che setacciano i siti pubblici cercando esattamente queste stringhe, e non hanno bisogno di sapere chi sei per trovare la tua.
Se vuoi la versione specifica per piattaforma di cosa controllare, abbiamo una guida in linguaggio semplice per le app con Supabase.
Cosa fare adesso
Cosa fare
- Trova ogni chiave nella tua app e identificala. Il prefisso per Stripe e per le chiavi Supabase nuove; il campo
roledentro quelle Supabase più vecchie. - Se trovi una chiave segreta nel browser, rigenerala per prima cosa. Cancellarla dal codice non chiude la porta: il valore vecchio esiste ancora nella cronologia delle versioni e nelle copie in cache del tuo sito.
- Sposta su un server ciò che aveva bisogno di quella chiave: una edge function, una route serverless, qualsiasi cosa che non sia il browser.
- Attiva la Row Level Security su ogni tabella, poi verifica che funzioni davvero. Una chiave pubblicabile (
sb_publishable_…oanon) è sicura solo grazie a quelle regole; senza, legge tutto il tuo database. - Controlla fatturazione e log dopo qualsiasi esposizione di una chiave segreta. Rigenerarla ferma ciò che verrebbe dopo, non ciò che è già successo.
Se preferisci procedere per punti, la checklist di sicurezza in 10 minuti copre questo e le altre cose da disattivare in un'app appena lanciata.
E se una fuga arriva a svuotare una tabella, riaverla dipende interamente da che cosa stavi salvando.
FAQ
Mi hanno detto che la mia chiave API è esposta. Devo preoccuparmi?
Non prima di sapere di quale chiave si tratta. Se inizia con pk_ o è una chiave anon di Supabase, è fatta per essere pubblica e va tutto bene. Se inizia con sk_ o è una chiave service_role, rigenerala subito e poi guarda a cosa poteva arrivare.
Posso semplicemente nascondere la chiave così nessuno la trova?
No. Tutto ciò che il tuo browser può usare, un visitatore può leggerlo: minificare, rinominare o offuscare rallenta qualcuno solo di qualche secondo. La soluzione non è mai nascondere una chiave segreta nel browser, ma spostarla su un server, oppure usare una chiave pubblicabile che era sicuro esporre fin dall'inizio.
Perché Supabase mi dà una chiave che chiunque può leggere?
Perché non è la chiave anon a proteggere i tuoi dati. Dice soltanto con quale progetto stai parlando. La protezione vera è la Row Level Security, che decide riga per riga cosa ogni visitatore può vedere. Per questo una chiave anon con RLS attiva non è un problema, e la stessa chiave senza RLS è un database aperto.
La mia chiave Supabase inizia con sb_publishable_. È la stessa cosa della chiave anon?
Fa lo stesso lavoro. Supabase ha rinominato le sue chiavi: sb_publishable_ ha preso il posto della chiave anon e sb_secret_ quello di service_role. Quindi una chiave che inizia con sb_publishable_ è fatta per stare nella tua app, e una che inizia con sb_secret_ non lo è mai. I progetti più vecchi portano ancora le chiavi anon e service_role originali, e quelle funzionano ancora; potresti perfino vedere entrambe le coppie affiancate nel pannello.
Ho già incollato una chiave segreta nella mia app. E adesso?
Rigenerala per prima cosa: nel pannello del fornitore crea una nuova chiave e revoca quella vecchia. È questo che chiude davvero la porta; toglierla dal codice non basta, perché il valore vecchio resta nella tua cronologia delle versioni e nelle copie in cache. Poi sposta su un server ciò che aveva bisogno di quella chiave e controlla fatturazione e log per qualcosa che non hai fatto tu.