Come funziona questo controllo
Scanaris carica la tua pagina principale, raccoglie i file JavaScript che collega dal tuo stesso dominio e li legge cercando pattern di credenziali note: chiavi segrete e ristrette di Stripe (sk_live_, rk_live_), identificatori di accesso AWS (AKIA…), chiavi API di Google (AIza…), chiavi OpenAI, token GitHub (ghp_…) e chiavi private in formato PEM. Inoltre decodifica qualsiasi JWT che trova e, se il suo payload contiene role: service_role, lo segnala come chiave di servizio Supabase esposta, che è critico. Le chiavi pubbliche (pk_live_ di Stripe, la anon o publishable di Supabase, la configurazione di Firebase) sono escluse di proposito: sono progettate per stare nel browser.
Cosa fa un attaccante con questo
Esattamente quello che fa lo scanner, ma su scala: ci sono bot che scaricano il JavaScript dei domini nuovi appena compaiono nei registri dei certificati e provano ogni chiave che trovano. Una chiave segreta di Stripe permette di creare rimborsi o leggere i clienti; una di OpenAI, di consumare la tua quota; una service_role di Supabase, di leggere e cancellare tutto il tuo database saltando le policy. Non serve "hackerare" nulla: il file è pubblico per definizione, lo riceve ogni visitatore.
Come una chiave finisce nel bundle
Quasi sempre per una variabile d'ambiente con il prefisso pubblico: NEXT_PUBLIC_ in Next.js, VITE_ in Vite, NUXT_PUBLIC_ in Nuxt. Il framework incorpora quelle variabili nel JavaScript al momento della build, che è proprio ciò che vuoi per una chiave pubblica e proprio ciò che non vuoi per una segreta. L'altra via abituale è incollare la chiave nella chat di Lovable, Bolt o Cursor per "collegare" un servizio: il modello la scrive nel componente che ne ha bisogno. Più dettagli in segreti che la tua app fa trapelare nel browser.
Come sistemarlo
- Ruota prima la chiave. Considerala compromessa anche se la togli dal codice: i bundle precedenti restano nelle cache e nella cronologia dei deploy. Generane una nuova nel pannello del servizio e revoca quella vecchia.
- Sposta la chiamata sul server. In Next.js, in un Route Handler o una Server Action; in Nuxt, in una rotta di
server/api; con Supabase, in una Edge Function; su Netlify, in una funzione. Il browser chiama il tuo server e il server, con la chiave, chiama il servizio. - Togli il prefisso pubblico alla variabile e salvala nel pannello del tuo hosting come variabile di server. Se una variabile ha bisogno del prefisso, è pubblica: chiediti se dovrebbe esserlo.
- Controlla la cronologia di git. Se la chiave è stata caricata una volta in un repository, è nella cronologia anche se il file attuale è pulito.
Verifica che sia pulito
curl -s https://tuodominio.com | grep -oE 'src="[^"]+\.js[^"]*"' | cut -d'"' -f2 | head -5
curl -s https://tuodominio.com/assets/index-XXXX.js | grep -oE "sk_live_[A-Za-z0-9]+|rk_live_[A-Za-z0-9]+|AKIA[A-Z0-9]{16}|AIza[A-Za-z0-9_-]{35}|ghp_[A-Za-z0-9]{36}|BEGIN (RSA |EC )?PRIVATE KEY" | head
Il primo comando elenca i tuoi script; il secondo cerca i pattern in uno di essi. Se non esce nulla in nessuno, il bundle è pulito. Ripeti dopo ogni deploy importante, perché una rigenerazione con l'IA può rimettere la chiave.
Domande frequenti
La chiave anon di Supabase o la pk_live di Stripe sono un problema?
No. Sono pubbliche per design e lo scanner non le segnala. Ciò che protegge i tuoi dati con la anon è l'RLS; ciò che protegge Stripe con la pk_live è che serve solo a creare token di pagamento, non a incassare.
Mi segnala una chiave Google che uso per Maps. È grave?
Le chiavi AIza possono essere legittimamente pubbliche se sono limitate per dominio e per API nella console di Google Cloud. Senza restrizioni, chiunque può consumare la tua quota. Limitala e il rischio reale sparisce, anche se il pattern continua a comparire.
L'ho già tolta dal codice e continua a comparire.
Il bundle pubblicato può essere quello vecchio, oppure la chiave può stare in un altro file (un chunk condiviso, uno script di terzi ospitato sul tuo dominio). Fai di nuovo il deploy, riscansiona e, in ogni caso, ruota la chiave.