← Alle Checks
SicherheitTypische Schwere: Kritisch

Scanner für Secrets in JavaScript

Durchsucht das JavaScript deines Browsers nach geleakten API-Schlüsseln und Zugangsdaten wie Stripe, AWS oder Supabase.

Was es prüft

Dieser Scanner durchsucht das JavaScript, das deine Website an den Browser sendet, nach Mustern von API-Schlüsseln und Zugangsdaten wie denen von Stripe, AWS oder Supabase. Er meldet Geheimnisse, die versehentlich in den Client-Code gepackt wurden. Er prüft nur, was für jeden Besucher ohnehin öffentlich ist.

Warum es wichtig ist

Alles, was im Browser-JavaScript landet, ist für jeden sichtbar, der die Entwicklertools öffnet. Ein geheimer Schlüssel dort kann genutzt werden, um auf deine Dienste zuzugreifen, dein Geld auszugeben oder die Daten deiner Nutzer zu lesen. Das ist ein sehr häufiger Fehler, wenn Server- und Client-Variablen durcheinandergeraten.

Wie man es behebt

Verschiebe alle geheimen Schlüssel auf den Server und greife nur vom Backend aus darauf zu, niemals aus dem Browser-Code. Verwende auf dem Client nur öffentliche oder eingeschränkte Schlüssel und wechsle jedes bereits offengelegte Geheimnis. In vielen Frameworks bleiben Variablen ohne das öffentliche Präfix nur auf dem Server.

Wie diese Prüfung funktioniert

Scanaris lädt deine Startseite, sammelt die JavaScript-Dateien, die sie von deiner eigenen Domain einbindet, und durchsucht sie nach Mustern bekannter Zugangsdaten: geheime und eingeschränkte Stripe-Schlüssel (sk_live_, rk_live_), AWS-Zugangsschlüssel-IDs (AKIA…), Google-API-Schlüssel (AIza…), OpenAI-Schlüssel, GitHub-Tokens (ghp_…) und private Schlüssel im PEM-Format. Außerdem dekodiert es jedes gefundene JWT, und wenn dessen Payload role: service_role enthält, meldet es einen offengelegten Supabase-Service-Schlüssel, was kritisch ist. Öffentliche Schlüssel (Stripes pk_live_, Supabases anon oder publishable, die Firebase-Konfiguration) werden absichtlich ausgenommen: Sie sind dafür gemacht, im Browser zu liegen.

Was ein Angreifer damit tut

Genau das, was der Scanner tut, nur massenhaft: Bots laden das JavaScript neuer Domains herunter, sobald sie in Zertifikatsprotokollen auftauchen, und probieren jeden gefundenen Schlüssel aus. Ein geheimer Stripe-Schlüssel erlaubt Rückerstattungen oder das Lesen von Kunden; ein OpenAI-Schlüssel, dein Kontingent zu verbrauchen; ein Supabase-service_role, deine gesamte Datenbank unter Umgehung der Policies zu lesen und zu löschen. Nichts muss „gehackt" werden: Die Datei ist per Definition öffentlich, jeder Besucher bekommt sie.

Wie ein Schlüssel ins Bundle gerät

Fast immer über eine Umgebungsvariable mit dem öffentlichen Präfix: NEXT_PUBLIC_ in Next.js, VITE_ in Vite, NUXT_PUBLIC_ in Nuxt. Das Framework bettet diese Variablen beim Build ins JavaScript ein, was für einen öffentlichen Schlüssel genau richtig und für einen geheimen genau falsch ist. Der andere übliche Weg: den Schlüssel in den Chat von Lovable, Bolt oder Cursor einfügen, um einen Dienst „anzubinden"; das Modell schreibt ihn in die Komponente, die ihn braucht. Mehr dazu in Geheimnisse, die deine App im Browser preisgibt.

Wie du es behebst

  • Rotiere zuerst den Schlüssel. Betrachte ihn als kompromittiert, auch wenn du ihn aus dem Code entfernst: Frühere Bundles liegen noch in Caches und im Deployment-Verlauf. Erzeuge im Dashboard des Dienstes einen neuen und widerrufe den alten.
  • Verlagere den Aufruf auf den Server. In Next.js in einen Route Handler oder eine Server Action; in Nuxt in eine server/api-Route; mit Supabase in eine Edge Function; bei Netlify in eine Function. Der Browser ruft deinen Server auf, und der Server ruft mit dem Schlüssel den Dienst auf.
  • Entferne das öffentliche Präfix von der Variable und hinterlege sie im Dashboard deines Hostings als Servervariable. Braucht eine Variable das Präfix, ist sie öffentlich: Frag dich, ob sie das sein sollte.
  • Prüf die Git-Historie. Wurde der Schlüssel je in ein Repository gepusht, steckt er in der Historie, auch wenn die aktuelle Datei sauber ist.

Prüf, ob es sauber ist

curl -s https://deinedomain.com | grep -oE 'src="[^"]+\.js[^"]*"' | cut -d'"' -f2 | head -5
curl -s https://deinedomain.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

Der erste Befehl listet deine Skripte; der zweite sucht die Muster in einem davon. Kommt in keinem etwas heraus, ist das Bundle sauber. Wiederhole das nach jedem größeren Deployment, denn eine KI-Neugenerierung kann den Schlüssel zurückbringen.

Häufige Fragen

Sind der Supabase-anon-Schlüssel oder Stripes pk_live ein Problem?

Nein. Sie sind absichtlich öffentlich, und der Scanner meldet sie nicht. Deine Daten schützt beim anon-Schlüssel das RLS; Stripe schützt beim pk_live, dass er nur Zahlungstokens erzeugen, aber nichts abbuchen kann.

Er meldet einen Google-Schlüssel, den ich für Maps nutze. Ist das schlimm?

AIza-Schlüssel dürfen legitim öffentlich sein, wenn sie in der Google-Cloud-Konsole auf Domain und API eingeschränkt sind. Ohne Einschränkung kann jeder dein Kontingent verbrauchen. Schränke ihn ein, und das reale Risiko verschwindet, auch wenn das Muster weiter auftaucht.

Ich habe ihn schon aus dem Code entfernt, und er wird trotzdem gemeldet.

Das deployte Bundle ist vielleicht noch das alte, oder der Schlüssel steckt in einer anderen Datei (einem geteilten Chunk, einem Drittskript auf deiner Domain). Deploye erneut, scanne noch einmal und rotiere den Schlüssel in jedem Fall.

Prüf es auf deiner Website

Scanne deine Website kostenlos und sieh, ob dieser und 44 weitere Checks bestehen, mit einem Fix-Prompt für jedes Problem.

Meine Website kostenlos scannen

Aus dem Blog

Verwandte Checks