Come funziona questo controllo
Scanaris legge il JavaScript della tua pagina principale, compresi gli script che carica dal tuo dominio, e cerca chiamate a localStorage.setItem o sessionStorage.setItem la cui chiave suggerisca una credenziale: token, jwt, auth, secret, password, api_key, access, refresh o bearer. Se ne trova una, apre un riscontro di severità bassa. È bassa perché salvare un token nel browser non è una breccia di per sé; lo diventa quando si combina con qualsiasi iniezione di script, e per questo vale la pena saperlo.
Perché conta
Tutto ciò che sta in localStorage può essere letto da qualsiasi codice che gira sulla tua pagina: i tuoi script, quelli di terzi che carichi (analytics, chat, widget) e qualsiasi script che un attaccante riesca a iniettare. Una sola vulnerabilità XSS, nel tuo codice o in una dipendenza, basta perché localStorage.getItem('token') viaggi verso un server altrui e qualcuno usi la sessione del tuo utente da un altro posto, senza la sua password e senza che se ne accorga. Un cookie con HttpOnly è invisibile a JavaScript, quindi quello stesso XSS non può rubarlo; può fare richieste finché la pagina è aperta, che è male, ma non portarsi via la sessione.
Da dove viene di solito
Dalla configurazione di default di molte librerie di autenticazione nelle applicazioni a pagina singola. Il client Supabase per il browser salva la sessione in localStorage se non gli dici altrimenti, come molti esempi di JWT "fatto a mano" con Express. Lovable, Bolt e v0 generano quel pattern perché è quello che compare nella documentazione di avvio rapido.
Come sistemarlo
- Sessione in cookie HttpOnly. Se hai un server (Next.js, Nuxt, Express, una Edge Function), che sia lui a ricevere il token dal provider e a salvarlo in un cookie con
HttpOnly,SecureeSameSite=Lax. Il browser lo invia da solo e non lo vede mai. - Con Supabase: in Next.js o Nuxt usa
@supabase/ssr, che salva la sessione in cookie; in una SPA pura senza server, accetta il rischio e compensalo con una CSP rigorosa, perché non c'è altro posto dove tenerla. - Token brevi e refresh sicuro. Se il token di accesso deve vivere in memoria o nello storage, che scada in minuti e che quello di refresh vada in un cookie
HttpOnly. - Memoria invece di storage. Tenere il token in una variabile JavaScript (non nello storage) lo protegge dagli script che leggono lo storage, ma non da un XSS attivo sulla stessa pagina. Meglio di localStorage, peggio di un cookie.
- Riduci la superficie XSS. Una CSP senza
'unsafe-inline'inscript-src, sanificare qualsiasi HTML dell'utente e controllare gli script di terzi che carichi. È la difesa che rende irrilevante il token nello storage.
Verifica che sia sistemato
curl -s https://tuodominio.com | grep -oE 'src="[^"]+\.js[^"]*"' | cut -d'"' -f2 | head -3
curl -s https://tuodominio.com/assets/index-XXXX.js | grep -oE "(local|session)Storage\.setItem\([^)]{0,60}" | head
Se il secondo comando restituisce righe con token, auth o simili nella chiave, il pattern è ancora lì. Negli strumenti del browser, scheda Applicazione, puoi vedere cosa salva la tua app nello storage dopo il login.
Domande frequenti
Supabase salva la sessione in localStorage di default. È sbagliato?
È il compromesso che accetta una SPA senza server. Se hai un server, passa a @supabase/ssr. Altrimenti cura la CSP e gli script di terzi: quel token è sicuro quanto la pagina che lo circonda.
sessionStorage è più sicuro di localStorage?
Solo nella durata: si cancella alla chiusura della scheda. Qualsiasi script della pagina lo legge lo stesso.
Perché severità bassa se può rubare la sessione?
Perché richiede un'altra vulnerabilità (un XSS) per essere sfruttato. Il controllo ti avvisa perché tu decida con cognizione; se il tuo sito ha già una CSP rigorosa e non mostra contenuto utente senza sanificare, il rischio reale è piccolo.