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.