// docs / baas security / supabase service role exposure
Supabase service-rolsleutel blootgesteld in JavaScript: wat het betekent en hoe je het vindt
De Supabase service-rolsleutel is de hoofdsleutel voor je database. Wie hem in handen heeft, omzeilt Row-Level Security, kan elke kolom van elke tabel lezen en kan alles schrijven of verwijderen wat hij maar wil. Hij is ontworpen om uitsluitend in server-side code te bestaan β nooit in de browser. Wanneer een AI-codeertool hem naar de JavaScript-bundle verstuurt, is je database in feite openbaar. Dit artikel legt de JWT-vorm uit die een gelekte sleutel identificeert, de drie AI-toolpatronen die het lek veroorzaken, wat te doen in het eerste uur na detectie, en hoe er automatisch op te scannen voordat gebruikers dat doen.
Wat de service-rolsleutel is
Supabase geeft voor elk project twee verschillende sleutels uit: de anon-sleutel (ook wel de publiceerbare sleutel genoemd in nieuwere projecten) en de service_role-sleutel. Beide zijn JSON Web Tokens ondertekend door het JWT-geheim van je project. Het verschil zit in de role-claim die in de JWT-payload is gebakken β anon voor de openbare sleutel, service_role voor de hoofdsleutel. PostgREST, Supabase Storage en Supabase Auth schakelen allemaal over naar de alles-omzeilende modus wanneer ze de service_role-claim zien.
Decodeer elke Supabase-sleutel op jwt.io en bekijk de payload. De vorm van een service-rol-JWT is onmiskenbaar:
Gedecodeerde payload van een service-rol-JWT (hieronder weergegeven als een blok met syntaxismarkering).
{
"iss": "supabase",
"ref": "[project-ref]",
"role": "service_role",
"iat": 1700000000,
"exp": 2000000000
}Nieuwere Supabase-projecten geven geheimen-stijl sleutels uit met het voorvoegsel sb_secret_ in plaats van een JWT. Het gedrag is identiek β alles wat sb_secret_ draagt in een openbare bundle is even catastrofaal.
Hoe AI-codeertools de service-rolsleutel lekken
We hebben dezelfde drie patronen gezien in duizenden vibe-gecodeerde apps. Elke begint met een ontwikkelaar die een AI-tool om hulp vraagt en eindigt met de servicekey die in een bundle wordt geΓ―nlined.
Patroon 1: Enkel .env-bestand met NEXT_PUBLIC_-voorvoegsel
De ontwikkelaar vraagt de AI-tool om "Supabase op te zetten" en accepteert een enkele .env met beide sleutels. De AI-tool β getraind op een corpus waar de meeste omgevingsvariabelen worden blootgesteld via NEXT_PUBLIC_* β voorziet beide van het voorvoegsel NEXT_PUBLIC_. Next.js inlinet alles wat aan dat voorvoegsel voldoet in de client-bundle tijdens de build. Lanceer naar Vercel, en de servicekey zit in main.[hash].js.
Patroon 2: Verkeerde sleutel in createClient-aanroep
De ontwikkelaar plakt beide sleutels in een config.ts-bestand dat de AI heeft gegenereerd, en de AI vult de browserzijdige createClient()-aanroep per ongeluk met process.env.SUPABASE_SERVICE_ROLE_KEY. De build trekt de variabele binnen, en de JWT belandt in de bundle.
Patroon 3: Service-rolsleutel hardcoded in seed-scripts
De ontwikkelaar vraagt de AI-tool om een script te schrijven dat de database vult. De AI hardcodeert de service-rolsleutel rechtstreeks in het bestand (in plaats van uit de omgeving te lezen), commit het bestand naar de repository, en de openbare GitHub-repo of de /scripts/seed.js-route van de gedeployde app serveert nu de sleutel.
Hoe de FixVibe-bundelscan de lek detecteert
De bundel-geheimen-check van FixVibe downloadt elk JavaScript-bestand waarnaar de gedeployde app verwijst β entry-chunks, lazy-loaded chunks, web workers, service workers β en draait ze door een detector die alles decodeert dat aan de JWT-vorm voldoet (eyJ[base64-header].eyJ[base64-payload].[signature]). Als de gedecodeerde payload "role": "service_role" bevat, rapporteert de scan dit als een kritieke bevinding met het bestandspad en de exacte regel waar de sleutel verschijnt. Dezelfde check matcht ook het nieuwere sb_secret_*-patroon op voorvoegsel.
De scan authenticeert nooit met de ontdekte sleutel. Het identificeert de vorm en rapporteert de lek β de sleutel gebruiken om de uitbuitbaarheid te bewijzen zou ongeautoriseerde toegang tot je database zijn. Het bewijs zit in de JWT-payload zelf.
Gedetecteerd β wat te doen in het eerste uur
Een gelekte service-rolsleutel is een runtime-noodsituatie. Veronderstel dat de sleutel is gescraped β aanvallers monitoren openbare bundles in realtime. Behandel de database als gecompromitteerd totdat je de sleutel hebt geroteerd en recente activiteit hebt geaudit.
- Roteer de sleutel onmiddellijk. Ga in het Supabase-dashboard naar Project Settings β API β Service role key β Reset. De oude sleutel wordt binnen enkele seconden ongeldig. Alle service-side code die de sleutel gebruikt, moet worden bijgewerkt en opnieuw gedeployd voordat de rotatie van kracht wordt.
- Audit recente database-activiteit. Open Database β Logs in het dashboard. Filter op de laatste 7 dagen. Zoek naar ongebruikelijke
SELECT *-query's tegen tabellen met PII, groteUPDATE- ofDELETE-statements, en aanvragen van IP's buiten je bekende infrastructuur. Supabase logt dex-real-ip-header bij elke aanvraag. - Controleer opslagobjecten. Bezoek Storage β Logs en bekijk recente bestandsdownloads. Een gelekte service-rolsleutel geeft alles-omzeilende toegang tot private buckets.
- Verwijder de sleutel uit broncodebeheer. Zelfs na rotatie betekent het achterlaten van de JWT in je git-geschiedenis dat hij vindbaar is in de openbare repo. Gebruik
git filter-repoof BFG Repo-Cleaner om hem uit de geschiedenis te schrobben en force-push dan (waarschuw eerst medewerkers). - Hersannen na fix. Voer een verse FixVibe-scan uit tegen de opnieuw gedeployde app. De bundel-geheimen-bevinding zou moeten verdwijnen. Bevestig dat er geen
service_role-JWT en geensb_secret_*-string in een chunk overblijft.
Het lek in de eerste plaats voorkomen
De structurele oplossing is naamgevingsdiscipline plus tooling-niveau-vangrails:
- Voorzie de servicekey nooit van een voorvoegsel
NEXT_PUBLIC_*,VITE_*of een ander bundle-inlining-voorvoegsel. De naamgevingsconventie is de grens β elk framework respecteert het. - Houd de servicekey volledig uit
.envop de ontwikkelaarsmachine. Lees hem uit een geheimenbeheerder (Doppler, Infisical, Vercel-versleutelde env-vars) bij deployment, commit hem nooit lokaal. - <strong>Mark every Supabase client construction with explicit context.</strong> Files named <code>supabase/browser.ts</code> use the anon key; files named <code>supabase/server.ts</code> use the service-role key with <code>import 'server-only'</code> at the top. The <code>server-only</code> import causes a build error if a client component tries to consume the module.
- <strong>Add a pre-commit hook that greps for JWT-shaped strings.</strong> <code>git diff --staged | grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'</code> catches both anon and service tokens before they leave your machine.
- Voeg een CI-poort toe die de build-output scant. Na
next build, grep de.next/static/chunks/-output op deservice_role-string. Laat de build mislukken als er iets matcht.
# Pre-commit hook: refuse any staged JWT-shaped string.
git diff --staged \
| grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+' \
&& echo "JWT detected in staged changes β refusing commit" \
&& exit 1
# CI gate: fail the build if "service_role" shipped to the static bundle.
grep -RE 'service_role|sb_secret_' .next/static/chunks/ \
&& echo "Service-role credential leaked into bundle" \
&& exit 1Veelgestelde vragen
Hoe snel vinden aanvallers daadwerkelijk gelekte Supabase service-rolsleutels?
Openbare-bundlescanners doorlopen nieuwe deployments binnen enkele minuten. Onderzoekers hebben werkende exploits tegen nieuwe Supabase-projecten gedocumenteerd binnen een uur na de eerste deployment. Behandel elke service-rolblootstelling als een 60-minuten-venster, niet een 60-dagen-venster.
Is het roteren van de sleutel genoeg, of moet ik uitgaan van data-exfiltratie?
Rotatie maakt de gelekte sleutel ongeldig, maar maakt data die al is opgehaald niet ongedaan. Als je tabellen PII, betalingsgegevens of gereguleerde gegevens bevatten, heb je mogelijk een meldingsplicht onder GDPR (72 uur), CCPA of HIPAA. Audit de logs en raadpleeg juridisch advies als de audit verdachte toegang toont.
Kan RLS me beschermen als de service-rolsleutel lekt?
Nee. Row-Level Security wordt volledig omzeild door de service_role-claim. Dat is by design β de sleutel bestaat precies om backend-code RLS te laten overslaan voor admin-operaties. De mitigatie is om ervoor te zorgen dat de sleutel nooit een context bereikt waar een aanvaller hem kan lezen.
Is dit van toepassing op het nieuwe Supabase publiceerbare / geheime sleutelmodel (<code>sb_publishable_</code> / <code>sb_secret_</code>)?
Ja β identieke risicoklasse. De sb_secret_*-sleutel is het nieuwe geheim-sleutelformaat dat de service-rol-JWT vervangt voor nieuwere projecten. Alles wat sb_secret_* in een bundle draagt, is net zo catastrofaal als een gelekte service-rol-JWT. De bundel-geheimen-detector van FixVibe matcht beide vormen.
En de anon / publiceerbare sleutel β is die veilig in de bundle?
Ja, by design. De anon-sleutel is bedoeld om in de browser te leven en is wat elke Supabase-webclient gebruikt. De veiligheid hangt volledig af van of RLS correct is geconfigureerd op elke openbare tabel. Zie het Supabase RLS-scanner-artikel voor wat je moet controleren.
Volgende stappen
Voer een FixVibe-scan uit tegen je productie-URL β de bundel-geheimen-check is gratis, geen aanmelding, en rapporteert service_role-blootstelling in minder dan een minuut. Combineer dit met het Supabase RLS-scanner-artikel om te verifiΓ«ren dat de RLS-laag zijn werk doet, en de Supabase opslagbucket-beveiligingschecklist om bestandstoegang te vergrendelen. Voor achtergrond over waarom AI-tools deze lekklasse zo betrouwbaar genereren, lees Waarom AI-codeertools beveiligingsgaten achterlaten.
