// docs / baas security / supabase service role exposure
Supabase-Service-Role-Key in JavaScript offengelegt: was es bedeutet und wie man ihn findet
Der Supabase-Service-Role-Key ist der Hauptschlüssel zu deiner Datenbank. Wer ihn besitzt, umgeht Row-Level Security, kann jede Spalte jeder Tabelle lesen und alles schreiben oder löschen. Er ist dafür gedacht, ausschließlich in serverseitigem Code zu leben — niemals im Browser. Wenn ein KI-Coding-Tool ihn in das JavaScript-Bundle ausliefert, ist deine Datenbank faktisch öffentlich. Dieser Artikel erklärt die JWT-Form, die einen geleakten Key identifiziert, die drei KI-Tool-Muster, die das Leak produzieren, was in der ersten Stunde nach der Entdeckung zu tun ist, und wie man automatisch danach scannt, bevor Nutzer es tun.
Was der Service-Role-Key ist
Supabase stellt zwei verschiedene Keys pro Projekt aus: den anon-Key (auch Publishable-Key in neueren Projekten genannt) und den service_role-Key. Beide sind JSON Web Tokens, signiert mit dem JWT-Secret deines Projekts. Der Unterschied ist der role-Claim, der in den JWT-Payload eingebacken ist — anon für den öffentlichen Key, service_role für den Hauptschlüssel. PostgREST, Supabase Storage und Supabase Auth schalten alle in einen Alles-umgehen-Modus, wenn sie den service_role-Claim sehen.
Dekodiere einen beliebigen Supabase-Key bei jwt.io und sieh dir den Payload an. Die Form eines Service-Role-JWT ist unverkennbar:
Dekodierter Payload eines Service-Role-JWT (unten als hervorgehobener Block gezeigt).
{
"iss": "supabase",
"ref": "[project-ref]",
"role": "service_role",
"iat": 1700000000,
"exp": 2000000000
}Neuere Supabase-Projekte stellen Secret-artige Keys mit dem Präfix sb_secret_ statt eines JWT aus. Das Verhalten ist identisch — alles, was sb_secret_ in einem öffentlichen Bundle trägt, ist genauso katastrophal.
Wie KI-Coding-Tools den Service-Role-Key leaken
Wir haben über tausende Vibe-coded Apps hinweg dieselben drei Muster gesehen. Jedes beginnt damit, dass ein Entwickler ein KI-Tool um Hilfe bittet, und endet damit, dass der Service-Key in einem Bundle inline landet.
Muster 1: Einzelne .env-Datei mit NEXT_PUBLIC_-Präfix
Der Entwickler bittet das KI-Tool, „Supabase einzurichten" und akzeptiert eine einzelne .env mit beiden Keys. Das KI-Tool — trainiert auf einem Korpus, in dem die meisten Umgebungsvariablen via NEXT_PUBLIC_* exponiert werden — präfixiert beide mit NEXT_PUBLIC_. Next.js inlined alles, was zu diesem Präfix passt, zur Build-Zeit ins Client-Bundle. Auf Vercel deployen, und der Service-Key liegt in main.[hash].js.
Muster 2: Falscher Key im createClient-Aufruf
Der Entwickler fügt beide Keys in eine config.ts-Datei ein, die die KI generiert hat, und die KI füllt versehentlich den browserseitigen createClient()-Aufruf mit process.env.SUPABASE_SERVICE_ROLE_KEY. Der Build zieht die Variable ein, und das JWT landet im Bundle.
Muster 3: Service-Role-Key fest in Seed-Skripten codiert
Der Entwickler bittet das KI-Tool, ein Skript zu schreiben, das die Datenbank befüllt. Die KI codiert den Service-Role-Key direkt in die Datei (statt aus der Umgebung zu lesen), committet die Datei ins Repository, und das öffentliche GitHub-Repo oder die /scripts/seed.js-Route der deployten App liefert nun den Key aus.
Wie der FixVibe-Bundle-Scan das Leak entdeckt
Der Bundle-Secrets-Check von FixVibe lädt jede JavaScript-Datei herunter, die die deployte App referenziert — Entry-Chunks, lazy-geladene Chunks, Web Workers, Service Workers — und schickt sie durch einen Detektor, der alles dekodiert, was zur JWT-Form passt (eyJ[base64-header].eyJ[base64-payload].[signature]). Enthält der dekodierte Payload "role": "service_role", meldet der Scan dies als kritischen Befund mit Dateipfad und exakter Zeile, in der der Key erscheint. Derselbe Check matched auch das neuere sb_secret_*-Muster per Präfix.
Der Scan authentifiziert sich nie mit dem entdeckten Key. Er identifiziert die Form und meldet das Leak — den Key zu nutzen, um die Ausnutzbarkeit zu beweisen, wäre unautorisierter Zugriff auf deine Datenbank. Der Beweis liegt im JWT-Payload selbst.
Entdeckt — was in der ersten Stunde zu tun ist
Ein geleakter Service-Role-Key ist ein Runtime-Notfall. Nimm an, dass der Key abgegriffen wurde — Angreifer überwachen öffentliche Bundles in Echtzeit. Behandle die Datenbank als kompromittiert, bis du den Key rotiert und die jüngste Aktivität auditiert hast.
- Rotiere den Key sofort. Im Supabase-Dashboard zu Project Settings → API → Service role key → Reset. Der alte Key wird binnen Sekunden invalidiert. Jeder serverseitige Code, der den Key nutzt, muss aktualisiert und neu deployed werden, bevor die Rotation greift.
- Auditiere die jüngste Datenbankaktivität. Öffne Database → Logs im Dashboard. Filtere auf die letzten 7 Tage. Halte Ausschau nach ungewöhnlichen
SELECT *-Queries gegen Tabellen mit PII, großenUPDATE- oderDELETE-Statements und Requests von IPs außerhalb deiner bekannten Infrastruktur. Supabase loggt denx-real-ip-Header bei jedem Request. - Prüfe Storage-Objekte. Besuche Storage → Logs und überprüfe die jüngsten Datei-Downloads. Ein geleakter Service-Role-Key gewährt auch zu privaten Buckets Alles-umgehen-Zugriff.
- Entferne den Key aus der Versionskontrolle. Auch nach der Rotation bedeutet das JWT in deiner Git-Historie, dass es im öffentlichen Repo auffindbar ist. Nutze
git filter-repooder BFG Repo-Cleaner, um es aus der Historie zu scrubben, dann Force-Push (warne Mitarbeiter vorher). - Nach dem Fix erneut scannen. Lass einen neuen FixVibe-Scan gegen die neu deployte App laufen. Der Bundle-Secrets-Befund sollte verschwinden. Bestätige, dass kein
service_role-JWT und keinsb_secret_*-String mehr in irgendeinem Chunk verbleibt.
Das Leak von vornherein verhindern
Die strukturelle Lösung ist Namensdisziplin plus Leitplanken auf Tool-Ebene:
- Präfixiere den Service-Key niemals mit
NEXT_PUBLIC_*,VITE_*oder einem anderen Bundle-inlining Präfix. Die Namenskonvention ist die Grenze — jedes Framework respektiert sie. - Halte den Service-Key komplett aus der
.envauf dem Entwicklerrechner heraus. Lies ihn beim Deploy aus einem Secret-Manager (Doppler, Infisical, Vercel-verschlüsselte Env-Variablen), committe ihn niemals lokal. - <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.
- Füge ein CI-Gate hinzu, das den Build-Output scannt. Nach
next buildgreppe in der Ausgabe.next/static/chunks/nach dem Stringservice_role. Lass den Build scheitern, wenn etwas matched.
# 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 1Häufig gestellte Fragen
Wie schnell finden Angreifer tatsächlich geleakte Supabase-Service-Role-Keys?
Public-Bundle-Scanner durchsuchen neue Deployments innerhalb von Minuten. Forscher haben funktionierende Exploits gegen neue Supabase-Projekte in unter einer Stunde ab dem ersten Deploy dokumentiert. Behandle jede Service-Role-Offenlegung als 60-Minuten-Fenster, nicht als 60-Tage-Fenster.
Reicht es, den Key zu rotieren, oder muss ich Datenexfiltration annehmen?
Die Rotation invalidiert den geleakten Key, macht aber bereits abgezogene Daten nicht ungeschehen. Wenn deine Tabellen PII, Zahlungsdaten oder irgendwelche regulierten Daten enthalten, kann eine Meldepflicht nach GDPR (72 Stunden), CCPA oder HIPAA bestehen. Auditiere die Logs und konsultiere Rechtsberatung, wenn das Audit verdächtige Zugriffe zeigt.
Kann RLS mich schützen, wenn der Service-Role-Key leakt?
Nein. Row-Level Security wird durch den service_role-Claim vollständig umgangen. Das ist by design — der Key existiert genau dafür, dass Backend-Code RLS für Admin-Operationen überspringen kann. Die Mitigation ist, sicherzustellen, dass der Key nie in einen Kontext gelangt, in dem ein Angreifer ihn lesen kann.
Gilt das für das neue Supabase-Publishable-/Secret-Key-Modell (<code>sb_publishable_</code> / <code>sb_secret_</code>)?
Ja — identische Risikoklasse. Der sb_secret_*-Key ist das neue Secret-Key-Format, das das Service-Role-JWT für neuere Projekte ersetzt. Alles, was sb_secret_* in einem Bundle trägt, ist genauso katastrophal wie ein geleaktes Service-Role-JWT. Der Bundle-Secrets-Detektor von FixVibe erkennt beide Formen.
Was ist mit dem Anon-/Publishable-Key — ist der im Bundle sicher?
Ja, by design. Der Anon-Key ist dafür gedacht, im Browser zu leben, und ist das, was jeder Supabase-Webclient verwendet. Seine Sicherheit hängt vollständig davon ab, dass RLS auf jeder öffentlichen Tabelle korrekt konfiguriert ist. Siehe den Artikel Supabase-RLS-Scanner für die Prüfpunkte.
Nächste Schritte
Lass einen FixVibe-Scan gegen deine Produktions-URL laufen — der Bundle-Secrets-Check ist kostenlos, ohne Anmeldung, und meldet service_role-Offenlegung in unter einer Minute. Kombiniere das mit dem Artikel Supabase-RLS-Scanner, um zu verifizieren, dass die RLS-Schicht ihre Arbeit tut, und der Supabase-Storage-Bucket-Sicherheits-Checkliste, um den Dateizugriff zu sperren. Für Hintergrund, warum KI-Tools diese Leak-Klasse so zuverlässig produzieren, lies Warum KI-Coding-Tools Sicherheitslücken hinterlassen.
