Basi della sicurezza
Variabili d’ambiente Vite esposte: il prefisso vuol dire pubblica
Variabili d’ambiente Vite esposte: la tua app ha fatto ciò che il prefisso chiedeva. VITE_ e NEXT_PUBLIC_ vogliono dire pubblica, e l’IA non sapeva il costo.

In breve
- Le variabili d’ambiente Vite esposte nella tua app sono lì perché il prefisso lo ha chiesto. Una variabile chiamata VITE_ o NEXT_PUBLIC_ viene copiata nel JavaScript che ogni visitatore scarica, e tenerla in un file .env non lo impedisce.
- Il prefisso è l’etichetta giusta per un indirizzo o una chiave pubblicabile, e quella sbagliata per qualsiasi cosa spenda soldi o ignori le regole del tuo database. La build non distingue le due cose, e nemmeno l’IA che ha scritto la riga.
- Su 30.998 app vibe-coded online che abbiamo scansionato, 1.332 spedivano qualcosa a forma di chiave. 1.142 di queste erano chiavi API di Google, che di solito hanno bisogno di una restrizione e non di una rotazione. 52 spedivano una chiave che spende soldi o legge tutto.
Qualcuno ha aperto la tua app Lovable, ha premuto F12 e ha trovato nel codice
un valore che sei certo di aver messo in un file .env. Oppure hai chiesto al
builder una funzione, lui ha scritto una riga che comincia con VITE_, e
qualcosa che hai letto da allora dice che quel prefisso è il punto da cui le
chiavi escono. Il file ha .env nel nome e ogni tutorial dice di non
condividerlo mai. Variabili d’ambiente Vite esposte a ogni visitatore suonano
come un bug di Vite.
Ecco la parte che una guida dopo l’altra sbaglia: niente ha fallito nel
nascondere quel valore. VITE_ e NEXT_PUBLIC_ sono un’istruzione al tuo
strumento di build, e l’istruzione è mettere il valore nell’app che ogni
visitatore scarica. Lo strumento ha letto l’etichetta e ha fatto quel che
diceva.
Una build impacchetta la tua app in una scatola, e ogni visitatore riceve una copia della scatola. Il prefisso è un’etichetta su un valore che dice: metti dentro anche questo. Il resto di questo articolo riguarda per quali valori quell’etichetta è giusta, perché l’IA la mette su quelli sbagliati, e come vedere cosa c’è nella tua scatola.
Le variabili d’ambiente Vite sono esposte ai visitatori?
Quelle che cominciano con VITE_ sì, ed è a questo che serve il prefisso.
Vite, lo strumento di build dietro la maggior parte delle app Lovable e Bolt,
legge il tuo file .env a ogni build. Una variabile chiamata
VITE_SUPABASE_URL viene copiata nel bundle, il file JavaScript compresso che
ogni visitatore scarica, dove il tuo codice la legge come
import.meta.env.VITE_SUPABASE_URL. Una variabile chiamata DB_PASSWORD,
senza prefisso, arriva vuota nel browser. La documentazione stessa di Vite
dice che i valori con prefisso vengono inclusi nel tuo codice sorgente al
momento della build e non dovrebbero contenere chiavi API.
Next.js, con cui costruisce v0, ha la stessa regola sotto un altro nome:
NEXT_PUBLIC_. La sua documentazione descrive il valore come inlined, una
stringa fissa scritta nel bundle del browser quando fai la build. Expo usa
EXPO_PUBLIC_ e avverte con le stesse parole. I vecchi progetti Create React
App usano REACT_APP_. Ogni prefisso dice al suo strumento di build la stessa
cosa: questo va nella scatola.
Perché un file .env sembra privato e non lo è
Perché l’abitudine di tenere una chiave in un file .env viene dai server,
dove funziona.
Su un server, il file e il codice che lo legge stanno su una macchina che
controlli tu. Un visitatore riceve una risposta da quella macchina e non vede
mai il file. Tenere la chiave fuori dal codice e dentro un file che il server
legge all’avvio è buona pratica lì, ed è lì che è stato scritto ogni tutorial
che dice «mettila in .env».
Il tuo file .env ha due lettori, e i tutorial parlano di uno solo. Il primo
è chiunque possa vedere il tuo progetto: un collaboratore, un repository
GitHub pubblico, la vista file del builder stesso. Una voce in .gitignore,
che è una lista di file che git lascia fuori, tiene .env lontano da quel
lettore. Il secondo lettore è la build, che apre il file ogni volta che
pubblichi e copia fuori tutto ciò che porta il prefisso. .gitignore non le
dice niente.
Quindi «il mio .env è in gitignore» è vero, e risponde a un’altra domanda.
Il file è rimasto fuori dal tuo repository. I valori con prefisso sono entrati
nell’app lo stesso, perché quella è la strada che il prefisso apre, e in un
progetto Lovable, Bolt o v0 la maggior parte del codice che hai modificato
gira nel browser, dove non c’è nessun server dietro cui il file possa
restare.
Perché l’IA ha scelto il prefisso
Perché è così che si fa funzionare un valore nel codice del browser, e il modello non ha idea di quanto costi quel valore.
Hai chiesto una mappa, o una chat che risponde alle domande sul tuo prodotto.
Il codice che il builder ha scritto per questo gira nel browser del
visitatore, e il codice del browser che legge process.env.OPENAI_API_KEY
non riceve proprio niente. Il modo per far arrivare il valore è il prefisso.
Il builder rinomina la variabile in VITE_OPENAI_API_KEY, la funzione
funziona nell’anteprima, e nessun errore viene sollevato da nessuna parte,
perché dal lato dello strumento di build niente è andato storto.
Il prefisso non giudica. È la stessa istruzione per una chiave Google Maps, che deve essere pubblica una volta ristretta, e per una chiave segreta Stripe, che può rimborsare ogni cliente che hai. Una persona che sapesse che la seconda addebita la tua carta si fermerebbe. Il modello sa che la funzione non andava finché la riga non c’era, e poi è andata.
Per questo salta fuori su app i cui proprietari hanno fatto tutto ciò che è
stato detto loro. Il valore è stato tolto dal codice, tenuto in .env, il
file era in gitignore, e l’app lo spedisce comunque, perché l’unico passaggio
che lo pubblica somiglia al passaggio che lo fa funzionare.
Quali valori vanno dietro il prefisso
Un indirizzo e una chiave pubblicabile. Qualsiasi cosa spenda soldi o ignori le regole del tuo database, no.
| Valore | Dietro VITE_ o NEXT_PUBLIC_? | Perché |
|---|---|---|
VITE_SUPABASE_URL | Sta qui | Un indirizzo. Dice con quale progetto parla la tua app e nient’altro. |
VITE_SUPABASE_PUBLISHABLE_KEY, o VITE_SUPABASE_ANON_KEY su un progetto più vecchio | Sta qui | Progettata per il browser. Ogni richiesta che fa resta filtrata da Row Level Security, le regole su ogni tabella che decidono riga per riga chi può leggere cosa. |
Una chiave Stripe pk_live_ | Sta qui | Costruisce moduli di pagamento. Non può addebitare, rimborsare né leggere i clienti. |
| Una chiave Google Maps | Sta qui, una volta ristretta | Pubblica per progetto. Una restrizione per referrer in Google Cloud è ciò che impedisce a uno sconosciuto di farti pagare con quella. |
| Una chiave OpenAI o Anthropic | Mai | Non esiste una variante pubblicabile. Chiunque la tenga spende i tuoi soldi. |
Una chiave Supabase service_role o sb_secret_ | Mai | Aggira Row Level Security e legge ogni riga di ogni tabella. |
Una chiave Stripe sk_live_ | Mai | Addebiti, rimborsi, pagamenti e ogni scheda cliente. |
| Una chiave di accesso AWS | Mai | Qualsiasi cosa possa fare quell’account, da ovunque. |
Se la tua app ha VITE_SUPABASE_URL e accanto una chiave pubblicabile, quella
è la coppia che Supabase ha pensato per un browser, e la nostra scansione la
segna come al suo posto. L’indirizzo e la chiave pubblicabile sono il motivo
per cui il prefisso esiste.
Il test per tutto il resto è se ti darebbe fastidio vedere il valore stampato sulla tua home page. Il prefisso lo mette un clic più lontano di così, in un file invece che sulla pagina, e chiunque voglia il file ce l’ha. Distinguere le due famiglie, dal prefisso e, su una vecchia chiave Supabase, dal ruolo che contiene, è un articolo a sé.
Cosa spedivano 30.998 app dietro il prefisso
Soprattutto chiavi API di Google. 52 app spedivano una chiave che spende soldi o legge tutto.
Nell’agosto 2026 abbiamo eseguito
gli stessi nove controlli su 30.998
app vibe-coded online. 1.332 di queste, il 4%, spedivano qualcosa a forma di
chiave nel codice che ogni visitatore scarica. 1.142 di quelle erano chiavi
API di Google, che di solito hanno bisogno di una
restrizione impostata in Google Cloud
e non di una rotazione. 204 spedivano un valore dall’aria casuale accanto a un
nome come secret o password, che può essere una credenziale vera oppure
no.
Quelle costose erano rare. 33 app spedivano una chiave OpenAI, 9 una chiave di
accesso AWS, 5 una chiave Anthropic, 3 una chiave segreta Stripe e 3 una
service_role Supabase: 52 app in tutto, visto che una di loro ne portava
due. Quindi il valore dietro il prefisso è di solito una chiave Google, e la
soluzione per quello è un’impostazione. Il caso raro è quello in cui sta il
danno, e
quanto costa una chiave OpenAI trapelata
è l’articolo per quello.
Come controllare cosa spedisce la tua app
Apri l’app online, premi F12 e cerca il valore in ogni file caricato.
- Apri la tua app pubblicata in un browser, al suo indirizzo vero. L’anteprima del builder è un’altra build e può essere una versione indietro.
- Premi F12 per aprire gli strumenti per sviluppatori, poi scegli la scheda Sources.
- Premi Ctrl+Maiusc+F, o Cmd+Opzione+F su un Mac. Si apre una ricerca in ogni file che la pagina ha caricato.
- Incolla i primi dieci caratteri circa del valore che ti preoccupa, e non incollarlo da nessun’altra parte.
Un risultato vuol dire che il valore è nella scatola che ogni visitatore
riceve. Cerca il valore e non il nome: la build di solito sostituisce
import.meta.env.VITE_OPENAI_API_KEY con il valore stesso, quindi una ricerca
di VITE_ può tornare vuota mentre ogni valore che c’era dietro è lì.
Se preferisci non passare la tua app valore per valore, la nostra scansione gratuita legge il tuo sito online dall’esterno, tutti e nove i controlli, in circa 20 secondi e senza account. Nomina ogni chiave che trova per tipo, dice quali stanno bene in un browser, e scrive «Impossibile verificare» per tutto ciò a cui non ha potuto rispondere invece di una spunta: scansiona la tua app.
Dove va un vero segreto, invece
Su una macchina da cui i tuoi visitatori non scaricano mai niente. In un progetto Supabase è una Edge Function, un piccolo pezzo di codice server che Supabase esegue per te; in un’app Next.js è una route server; in un’app Replit è la metà server.
La forma è la stessa ovunque. Il tuo codice del browser chiede alla tua funzione di fare il lavoro. La funzione tiene la chiave, fa la chiamata a OpenAI o Stripe e rimanda la risposta. La chiave resta sulla macchina e il visitatore riceve un risultato. L’articolo su OpenAI lo disegna come un oggetto che si sposta di una casella a destra, ed è tutto qui il cambiamento.
Due cose ti dicono che il builder ha fatto quel che hai chiesto. La variabile
ha perso il prefisso, quindi è OPENAI_API_KEY, e vive nei secret della
funzione stessa, impostati nella dashboard di Supabase sotto Edge Functions,
senza niente nel .env dell’app. E il file che la legge sta sotto
supabase/functions/ o app/api/, da qualche parte che la build non
impacchetta mai, invece che sotto src/.
Un ordine conta, ed è facile invertirlo. Se una chiave segreta è già stata spedita dietro un prefisso, spostarla non fa niente per le copie già scaricate. Ruotala prima presso il fornitore, poi sposta il lavoro. Se ruotare prima o chiudere prima la falla dipende da se la copia è già pubblica, e una volta che è stata in un bundle pubblicato, lo è.
Per un progetto Replit la stessa separazione ha un nome suo, Secrets, e cosa copre lo strumento Secrets e cosa no è un articolo a sé.
Cosa fare adesso
Cosa fare
- Cerca nel tuo progetto
VITE_,NEXT_PUBLIC_,EXPO_PUBLIC_eREACT_APP_. Ogni corrispondenza è un valore che la tua build pubblica di proposito. Decidi per ciascuno se può essere pubblico. - Tieni l’indirizzo e la chiave pubblicabile.
VITE_SUPABASE_URLeVITE_SUPABASE_PUBLISHABLE_KEY, o la chiaveanonsu un progetto più vecchio, sono la coppia per cui il prefisso esiste. - Una chiave segreta dietro un prefisso si ruota prima presso il fornitore, poi si sposta. Cancellare la riga non richiama una copia già scaricata.
- Sposta il lavoro che aveva bisogno della chiave in una Edge Function o in una route server, con la chiave nei secret di quella funzione e senza prefisso nel nome.
- Restringi una chiave Google per referrer in Google Cloud. Quella ha bisogno di un’impostazione e tiene il suo valore.
- Dopo la prossima pubblicazione, cerca nell’app online ogni valore che hai spostato.
Ogni pubblicazione impacchetta una scatola nuova. La prossima funzione che chiedi è un’altra occasione perché un valore riceva il prefisso, e niente tra il builder e internet legge il bundle in uscita. Una scansione del mese scorso ha letto il bundle del mese scorso.
Reeve Monitor legge il bundle per te. Rilancia tutti e nove i controlli ogni ora su fino a tre app, ti avvisa quando un risultato cambia, controlla la disponibilità ogni 60 secondi e manda un report mensile. Una chiave che arriva nel bundle con la pubblicazione di martedì sta nella nuova scansione di quell’ora, che tu ti sia ricordato di guardare o no. Costa €12 al mese a listino, con sette giorni gratis prima di addebitarti qualcosa; la pagina dei prezzi a volte è sotto la cifra indicata qui e mai sopra.
Se preferisci affrontarlo come una lista, la checklist di sicurezza da 10 minuti copre questo e le altre cose che vale la pena spegnere in un’app appena lanciata.
FAQ
I file .env sono segreti?
Per il tuo repository sì, se il file è elencato in .gitignore. Per i tuoi visitatori no. La build legge .env ogni volta che pubblichi e copia ogni valore con il prefisso VITE_ o NEXT_PUBLIC_ nel JavaScript che la tua app spedisce. Il file in sé non lascia mai la tua macchina; i valori che hai etichettato per il browser sì.
È sicuro esporre VITE_SUPABASE_ANON_KEY?
È fatta per stare lì. La chiave anon, chiamata chiave pubblicabile su un progetto recente, è progettata per vivere in un browser. Dice solo a quale progetto appartiene una richiesta, e ogni richiesta che fa viene filtrata dalle tue regole di Row Level Security. Questo vale esattamente finché quelle regole sono attive e corrette, che è una verifica a parte.
NEXT_PUBLIC_ è diverso da VITE_?
Stessa regola, altro strumento di build. Next.js scrive il valore di ogni variabile NEXT_PUBLIC_ nel bundle del browser come stringa fissa al momento della build, e una variabile senza prefisso arriva vuota nel codice del browser. Expo fa lo stesso con EXPO_PUBLIC_, e i vecchi progetti Create React App con REACT_APP_. Qualunque strumento abbia costruito la tua app, il prefisso vuol dire pubblica.
Come controllo cosa c’è nel mio bundle?
Apri l’app online, premi F12, scegli Sources e premi Ctrl+Maiusc+F (Cmd+Opzione+F su un Mac) per cercare in ogni file che la pagina ha caricato. Incolla i primi caratteri del valore. Cerca il valore e non il nome della variabile, perché la build di solito sostituisce il nome con il valore, quindi VITE_ può mancare mentre la chiave c’è. La nostra scansione gratuita fa la stessa lettura dall’esterno in circa 20 secondi.
Dove dovrebbe vivere una chiave segreta in un’app Lovable o Bolt?
In una Supabase Edge Function, con la chiave impostata nei secret di quella funzione nella dashboard di Supabase e senza prefisso VITE_ nel nome. Il tuo codice del browser chiama la funzione, la funzione chiama il fornitore con la chiave, e la chiave non raggiunge mai un visitatore. Se la chiave è già stata spedita, ruotala presso il fornitore prima di spostarla.
.gitignore protegge le mie chiavi?
Tiene il file .env fuori da git, quindi nessuno che legge il tuo repository lo vede. Non ha alcun effetto sulla build, che legge il file direttamente e pubblica ogni valore con prefisso. Un .env ignorato da git con dentro VITE_OPENAI_API_KEY spedisce comunque quella chiave a ogni visitatore.