Wie diese Prüfung funktioniert
Scanaris liest das JavaScript deiner Startseite, einschließlich der Skripte, die sie von deiner Domain lädt, und sucht Aufrufe von localStorage.setItem oder sessionStorage.setItem, deren Schlüssel auf Zugangsdaten hindeutet: token, jwt, auth, secret, password, api_key, access, refresh oder bearer. Findet es einen, öffnet es einen Fund mit niedrigem Schweregrad. Niedrig, weil ein Token im Browser allein keine Sicherheitslücke ist; eine wird es in Kombination mit irgendeiner Skript-Injektion, und deshalb lohnt es sich, davon zu wissen.
Warum es wichtig ist
Alles im localStorage kann jeder Code lesen, der auf deiner Seite läuft: deine Skripte, die Drittskripte, die du lädst (Analytics, Chats, Widgets) und jedes Skript, das ein Angreifer einschleust. Eine einzige XSS-Schwachstelle, in deinem Code oder in einer Abhängigkeit, genügt, damit localStorage.getItem('token') an einen fremden Server wandert und jemand die Sitzung deines Nutzers von woanders nutzt, ohne Passwort und ohne dass er es merkt. Ein Cookie mit HttpOnly ist für JavaScript unsichtbar, dasselbe XSS kann es also nicht stehlen; es kann Anfragen stellen, solange die Seite offen ist, was schlimm genug ist, aber die Sitzung nicht mitnehmen.
Woher es meist kommt
Aus der Standardkonfiguration vieler Authentifizierungsbibliotheken in Single-Page-Anwendungen. Der Supabase-Client für den Browser speichert die Sitzung im localStorage, wenn man ihm nichts anderes sagt, genau wie viele „handgemachte" JWT-Beispiele mit Express. Lovable, Bolt und v0 erzeugen dieses Muster, weil es das aus der Schnellstart-Dokumentation ist.
Wie du es behebst
- Sitzung in HttpOnly-Cookies. Hast du einen Server (Next.js, Nuxt, Express, eine Edge Function), soll er das Token vom Anbieter entgegennehmen und in einem Cookie mit
HttpOnly,SecureundSameSite=Laxspeichern. Der Browser schickt es von selbst mit und sieht es nie. - Mit Supabase: in Next.js oder Nuxt
@supabase/ssrverwenden, das die Sitzung in Cookies ablegt; in einer reinen SPA ohne Server das Risiko akzeptieren und mit einer strikten CSP ausgleichen, denn es gibt keinen anderen Ort dafür. - Kurze Tokens und sichere Erneuerung. Muss das Zugriffstoken im Speicher oder im Storage leben, soll es nach Minuten ablaufen, und das Refresh-Token gehört in ein
HttpOnly-Cookie. - Arbeitsspeicher statt Storage. Das Token in einer JavaScript-Variable (nicht im Storage) zu halten, schützt vor Skripten, die den Storage auslesen, nicht aber vor einem aktiven XSS auf derselben Seite. Besser als localStorage, schlechter als ein Cookie.
- Verkleinere die XSS-Angriffsfläche. Eine CSP ohne
'unsafe-inline'inscript-src, jedes Nutzer-HTML bereinigen und die geladenen Drittskripte prüfen. Das ist die Verteidigung, die das Token im Storage unwichtig macht.
Prüf, ob es behoben ist
curl -s https://deinedomain.com | grep -oE 'src="[^"]+\.js[^"]*"' | cut -d'"' -f2 | head -3
curl -s https://deinedomain.com/assets/index-XXXX.js | grep -oE "(local|session)Storage\.setItem\([^)]{0,60}" | head
Liefert der zweite Befehl Zeilen mit token, auth oder Ähnlichem im Schlüssel, ist das Muster noch da. In den Browser-Tools, Tab Anwendung, siehst du, was deine App nach der Anmeldung im Storage ablegt.
Häufige Fragen
Supabase speichert die Sitzung standardmäßig im localStorage. Ist das falsch?
Es ist der Kompromiss, den eine SPA ohne Server eingeht. Hast du einen Server, wechsle zu @supabase/ssr. Wenn nicht, kümmere dich um CSP und Drittskripte: Dieses Token ist so sicher wie die Seite drumherum.
Ist sessionStorage sicherer als localStorage?
Nur in der Dauer: Es wird beim Schließen des Tabs gelöscht. Jedes Skript der Seite liest es genauso.
Warum niedriger Schweregrad, wenn es die Sitzung stehlen kann?
Weil es eine weitere Schwachstelle (ein XSS) braucht, um ausgenutzt zu werden. Die Prüfung warnt dich, damit du informiert entscheidest; hat deine Website schon eine strikte CSP und zeigt keinen unbereinigten Nutzerinhalt, ist das reale Risiko klein.