← Alle checks
BeveiligingTypische ernst: Kritiek

Scanner voor secrets in JavaScript

Doorzoekt de JavaScript in je browser op gelekte API-sleutels en inloggegevens zoals Stripe, AWS of Supabase.

Wat het checkt

Deze scanner bekijkt het JavaScript dat je site naar de browser stuurt en zoekt naar patronen van API-sleutels en inloggegevens, zoals die van Stripe, AWS of Supabase. Hij markeert geheimen die per ongeluk in de client-code zijn beland. Hij inspecteert alleen wat voor elke bezoeker al openbaar is.

Waarom het belangrijk is

Alles wat in browser-JavaScript wordt meegestuurd, is zichtbaar voor iedereen die de ontwikkelaarstools opent. Een geheime sleutel daar kan worden gebruikt om toegang te krijgen tot je diensten, je geld uit te geven of de gegevens van je gebruikers te lezen. Het is een veelgemaakte fout wanneer server- en client-variabelen door elkaar raken.

Hoe je het oplost

Verplaats alle geheime sleutels naar de server en benader ze alleen vanuit de backend, nooit vanuit browsercode. Gebruik op de client alleen publieke of beperkte sleutels en roteer elk geheim dat al is blootgesteld. In veel frameworks blijven variabelen zonder de publieke prefix alleen op de server.

Hoe deze controle werkt

Scanaris laadt je homepage, verzamelt de JavaScript-bestanden die hij vanaf je eigen domein koppelt en leest ze op zoek naar patronen van bekende inloggegevens: geheime en beperkte Stripe-sleutels (sk_live_, rk_live_), AWS-toegangssleutel-ID's (AKIA…), Google-API-sleutels (AIza…), OpenAI-sleutels, GitHub-tokens (ghp_…) en private sleutels in PEM-formaat. Het decodeert ook elke JWT die het vindt en, als de payload role: service_role bevat, markeert het die als blootgestelde Supabase-servicesleutel, wat kritiek is. Publieke sleutels (Stripe's pk_live_, de anon of publishable van Supabase, de Firebase-configuratie) worden bewust uitgesloten: die zijn ontworpen om in de browser te zitten.

Wat een aanvaller hiermee doet

Precies wat de scanner doet, maar op grote schaal: er zijn bots die het JavaScript van nieuwe domeinen downloaden zodra ze in de certificaatlogs verschijnen en elke gevonden sleutel uitproberen. Een geheime Stripe-sleutel laat terugbetalingen aanmaken of klanten lezen; een OpenAI-sleutel laat je quotum opmaken; een Supabase-service_role laat je hele database lezen en wissen met omzeiling van de policies. Er hoeft niets "gehackt" te worden: het bestand is per definitie publiek, elke bezoeker ontvangt het.

Hoe een sleutel in de bundle belandt

Bijna altijd via een omgevingsvariabele met het publieke voorvoegsel: NEXT_PUBLIC_ in Next.js, VITE_ in Vite, NUXT_PUBLIC_ in Nuxt. Het framework bakt die variabelen bij de build in het JavaScript, precies wat je wilt voor een publieke sleutel en precies wat je niet wilt voor een geheime. De andere gebruikelijke weg is de sleutel in de chat van Lovable, Bolt of Cursor plakken om een dienst te "koppelen": het model schrijft hem in de component die hem nodig heeft. Meer details in geheimen die je app in de browser lekt.

Hoe je het oplost

  • Roteer eerst de sleutel. Beschouw hem als gecompromitteerd, ook als je hem uit de code haalt: eerdere bundles zitten nog in caches en in de deploymentgeschiedenis. Genereer een nieuwe in het dashboard van de dienst en trek de oude in.
  • Verplaats de aanroep naar de server. In Next.js naar een Route Handler of een Server Action; in Nuxt naar een route in server/api; met Supabase naar een Edge Function; op Netlify naar een functie. De browser roept je server aan en de server roept, met de sleutel, de dienst aan.
  • Haal het publieke voorvoegsel van de variabele af en sla hem op in het dashboard van je hosting als servervariabele. Als een variabele het voorvoegsel nodig heeft, is hij publiek: vraag je af of dat zo hoort.
  • Controleer de git-geschiedenis. Als de sleutel ooit naar een repository is gepusht, zit hij in de geschiedenis, ook al is het huidige bestand schoon.

Controleer of het schoon is

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

Het eerste commando toont je scripts; het tweede zoekt de patronen in een ervan. Komt er in geen enkele iets uit, dan is de bundle schoon. Herhaal dit na elke grote deployment, want een AI-hergeneratie kan de sleutel terugzetten.

Veelgestelde vragen

Zijn de Supabase-anon-sleutel of Stripe's pk_live een probleem?

Nee. Ze zijn bewust publiek en de scanner markeert ze niet. Wat je data beschermt met de anon-sleutel is RLS; wat Stripe beschermt met de pk_live is dat hij alleen betaaltokens kan aanmaken, niet kan afrekenen.

Hij markeert een Google-sleutel die ik voor Maps gebruik. Is dat ernstig?

AIza-sleutels mogen legitiem publiek zijn als ze in de Google Cloud-console beperkt zijn per domein en per API. Zonder beperkingen kan iedereen je quotum opmaken. Beperk hem en het echte risico verdwijnt, ook al blijft het patroon opduiken.

Ik heb hem al uit de code gehaald en hij blijft verschijnen.

De gedeployde bundle kan de oude zijn, of de sleutel zit in een ander bestand (een gedeelde chunk, een script van derden op je domein). Deploy opnieuw, scan opnieuw en roteer de sleutel sowieso.

Check het op je site

Scan je site gratis en zie of deze en 44 andere checks slagen, met een fixprompt voor elk probleem.

Mijn site gratis scannen

Uit de blog

Gerelateerde checks