Zum Inhalt springen

Sicherheitsgrundlagen

Welche API-Schlüssel im Frontend sicher sind, und welche nicht

Dein Supabase-anon-Schlüssel darf öffentlich sein. Der service_role-Schlüssel nicht. Er ignoriert jede Regel, die du setzt. So erkennst du den Unterschied.

Vlad Tkachenko6 Min. Lesezeit

Kurz gesagt

  • Einen API-Schlüssel im Code deiner App zu finden ist nicht automatisch ein Problem. Manche Schlüssel gehören genau dorthin.
  • Öffentliche Schlüssel sind im Browser sicher. Geheime nicht. Einer davon im Frontend ist eine offene Kasse.
  • Zwei Prüfungen trennen sie in unter einer Minute: Präfix lesen, und bei einem älteren Supabase-Schlüssel die Rolle dekodieren.

Wer seine App mit Lovable, Bolt, v0, Cursor oder Replit gebaut hat, erlebt früher oder später Folgendes: Jemand öffnet die Seite, drückt F12 und teilt mit, der API-Schlüssel sei „offengelegt". Über eine App, deren Code man selbst nicht lesen kann, ist das eine beunruhigende Nachricht.

Hier ist der Punkt, den Anleitung um Anleitung falsch darstellt: Manche dieser Schlüssel gehören genau dorthin. Jeden sichtbaren Schlüssel als Sicherheitsleck zu behandeln führt entweder zu Panik ohne Anlass oder, deutlich schlimmer, dazu, die Warnung irgendwann zu ignorieren. Auch an dem Tag, an dem sie zählt.

Ist es schlimm, dass mein API-Schlüssel sichtbar ist?

Meistens nicht. Es hängt vollständig davon ab, um welchen Schlüssel es geht.

Jeder Dienst, der mit einem Browser spricht, vergibt zwei verschiedene Arten von Zugangsdaten. Die eine ist eine Adresse. Die andere ist ein Schlüsselbund zum Gebäude. Beide heißen „API-Schlüssel", und das ist der größte Teil der Verwirrung.

Eine Adresse darf veröffentlicht werden. Sie sagt nur, zu welchem Projekt eine Anfrage gehört; die eigentliche Rechteprüfung passiert woanders. Ein Schlüsselbund darf das nicht, denn er ist die Rechteprüfung: Wer ihn hat, darf alles, was er erlaubt, von überall.

Deine App braucht die Adresse im Browser, um überhaupt zu funktionieren. Den Schlüsselbund sollte sie dort nie brauchen.

Warum deine App überhaupt Schlüssel an den Browser schickt

Weil die Anfrage vom Browser deiner Besucherin kommt, nicht von deinem Server.

Wenn deine App die Bestellungen deiner Nutzer lädt, geht diese Anfrage direkt vom Browser der Besucherin an deinen Datenbankanbieter. Sie muss sagen, zu welchem Projekt sie gehört, und diese Kennung muss in der Seite stehen, weil die Anfrage genau von dort gestellt wird.

Es gibt keine Variante, in der diese Kennung geheim bleibt. Sie erreicht jede Besucherin, so ist es gedacht. Und genau deshalb teilen Anbieter ihre Zugangsdaten in zwei Sorten: Sie wissen, dass eine davon öffentlich wird, also haben sie diese harmlos gemacht.

Die Sicherheit entsteht nicht dadurch, die Adresse zu verstecken. Sie entsteht durch die Regeln am anderen Ende: bei Supabase durch Row Level Security, die Zeile für Zeile entscheidet, wer was sehen darf. Diese Regeln sind das, was zu prüfen sich lohnt, und sie anzuschalten ist nicht dasselbe wie geschützt zu sein.

Row Level Security ist das, was den anon-Schlüssel sicher macht. Ein service_role-Schlüssel geht direkt daran vorbei.

Die zwei Familien von Schlüsseln

AnbieterSchlüsselIm Browser sicher?Was er tut
Supabasesb_publishable_…, in älteren Projekten anonGehört hierhinSagt, um welches Projekt es geht. Jede Anfrage wird weiterhin von deinen Row-Level-Security-Regeln gefiltert.
Supabasesb_secret_…, in älteren Projekten service_roleNiemalsUmgeht Row Level Security vollständig. Liest und schreibt jede Zeile in jeder Tabelle, egal was deine Regeln sagen.
Stripepk_live_… (öffentlich)Gehört hierhinErstellt Bezahlformulare. Kann kein Geld bewegen und keine Kundendaten lesen.
Stripesk_live_… (geheim)NiemalsVollzugriff auf das Konto: Abbuchungen, Erstattungen, Auszahlungen, Kundendaten.
OpenAI / Anthropicjeder API-SchlüsselNiemalsEs gibt keine öffentliche Variante. Jeder Schlüssel belastet direkt dein Konto.

Das Muster gilt über diese drei hinaus. Bietet ein Anbieter nur eine Sorte Schlüssel an, geh davon aus, dass er geheim ist und auf einen Server gehört.

So erkennst du den Unterschied in 60 Sekunden

Bei Stripe und den meisten Anbietern reicht das Präfix. pk_ ist öffentlich und sicher. sk_ ist geheim und nicht. Manche Anbieter schreiben _test_ oder _live_ in die Mitte. Ein durchgesickerter Testschlüssel ist ein deutlich kleineres Problem als ein Live-Schlüssel, aber tausche beide aus.

Bei Supabase hängt es davon ab, wie alt dein Projekt ist. Supabase hat zwei verschiedene Sorten Schlüssel ausgegeben, und beide sind heute in laufenden Apps unterwegs.

Neuere Projekte: Präfix lesen, genau wie bei Stripe. sb_publishable_… ist der, der in deine App gehört. sb_secret_… ist der, den du heute austauschst. Da ist nichts zu dekodieren. Wofür der Schlüssel da ist, steht vorne drauf. Was sich geändert hat und was das für deine App bedeutet, falls du diese Schlüssel noch nicht kennst.

Ältere Projekte: Du musst in den Schlüssel hineinsehen. Das ursprüngliche Paar, anon und service_role, sind JWTs: drei durch Punkte getrennte Blöcke Kauderwelsch, und der mittlere Block ist lesbare Information, keine Verschlüsselung. Dort steht die Rolle im Klartext.

Ein Werkzeug braucht es in keinem Fall. Öffne im Supabase-Dashboard Settings → API Keys, dort steht, welcher Schlüssel welcher ist. Willst du lieber den Schlüssel prüfen, den du tatsächlich in deiner App gefunden hast: Der mittlere Abschnitt eines älteren Schlüssels dekodiert zu etwas wie diesem hier.

{
  "iss": "supabase",
  "ref": "abcdefghij…",
  "role": "anon",          ← darauf kommt es an
  "iat": 1750000000
}
Bei einem älteren Schlüssel ist der mittlere Abschnitt lesbarer Text, keine Verschlüsselung. Ein Wort darin entscheidet, ob der Schlüssel ausgeliefert werden durfte.

"role": "anon" ist der sichere. "role": "service_role" ist der, den du heute austauschst. Bei einem älteren Schlüssel ist dieses eine Wort der ganze Unterschied, und der Grund, warum ein Scanner, der bloß schlüsselförmige Zeichenketten sucht, Rauschen statt einer Antwort liefert: Er kann dir nicht sagen, welchen von zwei identisch aussehenden Schlüsseln du ausgeliefert hast.

Wenn du deine App nicht Schlüssel für Schlüssel durchgehen willst: Unser kostenloser Scan liest deine Live-Seite und sagt dir, welche davon von außen zu sehen sind. Das dauert etwa 20 Sekunden und braucht kein Konto: App scannen.

Was passiert, wenn ein geheimer Schlüssel durchsickert

Jede Zeile in deiner Datenbank wird für den lesbar und beschreibbar, der den Schlüssel gefunden hat, einschließlich Tabellen, die du deiner App nie zugänglich gemacht hast, und einschließlich der persönlichen Daten deiner Nutzer. Row Level Security gilt für einen geheimen Supabase-Schlüssel nicht (sb_secret_…, oder service_role in einem älteren Projekt). Das ist der Zweck dieses Schlüssels.

Der Schaden ist auch nicht theoretisch. Meist fällt er als Supportnachricht über Daten auf, die sich von selbst geändert haben, oder als Tabelle, die plötzlich leer ist.

Ein durchgesickerter sk_live_-Schlüssel von Stripe bedeutet Erstattungen, Abbuchungen und Kundendaten. Ein durchgesickerter Schlüssel eines KI-Anbieters bedeutet eine Rechnung, manchmal eine sehr große, die eintrifft, bevor es jemandem auffällt.

Nichts davon verlangt einen versierten Angreifer. Automatische Scanner durchsuchen öffentliche Seiten genau nach solchen Zeichenketten, und sie müssen nicht wissen, wer du bist, um deine zu finden.

Wenn du die plattformspezifische Fassung möchtest, was zu prüfen ist: Wir haben eine verständliche Anleitung für Supabase-Apps.

Was jetzt zu tun ist

Was zu tun ist

  • Finde jeden Schlüssel in deiner App und bestimme, welcher es ist. Bei Stripe und bei neueren Supabase-Schlüsseln das Präfix; bei älteren Supabase-Schlüsseln das Feld role im Inneren.
  • Findest du einen geheimen Schlüssel im Frontend, tausche ihn zuerst aus. Ihn aus dem Code zu löschen schließt die Tür nicht: Der alte Wert steckt weiterhin in deiner Versionsgeschichte und in zwischengespeicherten Kopien deiner Seite.
  • Verschiebe das, wofür dieser Schlüssel nötig war, auf einen Server: eine Edge Function, eine Serverless-Route, irgendetwas, das nicht der Browser ist.
  • Schalte Row Level Security für jede Tabelle ein und prüfe dann, dass sie wirklich greift. Ein öffentlicher Schlüssel (sb_publishable_… oder anon) ist nur wegen dieser Regeln sicher; ohne sie liest er deine ganze Datenbank.
  • Sieh nach jeder Offenlegung eines geheimen Schlüssels in Abrechnung und Protokolle. Der Austausch stoppt, was danach kommt, nicht das, was schon passiert ist.

Wenn du das lieber als Liste durchgehst: Die Sicherheits-Checkliste in 10 Minuten deckt diesen Punkt ab und die anderen Dinge, die man in einer frisch veröffentlichten App abschalten sollte.

Und falls ein Leck je eine Tabelle leert, hängt es ganz davon ab, was du gesichert hattest, ob du sie zurückbekommst.

FAQ

Jemand sagt, mein API-Schlüssel sei offengelegt. Sollte ich in Panik geraten?

Nicht bevor du weißt, um welchen Schlüssel es geht. Beginnt er mit pk_ oder ist es ein Supabase-anon-Schlüssel, ist er für die Öffentlichkeit gedacht und alles ist in Ordnung. Beginnt er mit sk_ oder ist es ein service_role-Schlüssel, tausche ihn sofort aus und sieh dann nach, worauf er Zugriff hatte.

Kann ich den Schlüssel nicht einfach verstecken?

Nein. Alles, was dein Browser benutzen kann, kann ein Besucher auch lesen. Minifizieren, Umbenennen oder Verschleiern hält jemanden nur ein paar Sekunden auf. Die Lösung ist nie, einen geheimen Schlüssel im Frontend zu verstecken, sondern ihn auf einen Server zu verschieben oder einen öffentlichen Schlüssel zu verwenden, der von vornherein sichtbar sein durfte.

Warum gibt mir Supabase einen Schlüssel, den jeder lesen kann?

Weil der anon-Schlüssel nicht das ist, was deine Daten schützt. Er sagt nur, mit welchem Projekt du sprichst. Der eigentliche Schutz ist Row Level Security, die Zeile für Zeile entscheidet, was jeder Besucher sehen darf. Deshalb ist ein anon-Schlüssel mit eingeschalteter RLS unproblematisch, und derselbe Schlüssel ohne RLS eine offene Datenbank.

Mein Supabase-Schlüssel beginnt mit sb_publishable_. Ist das derselbe wie der anon-Schlüssel?

Er hat dieselbe Aufgabe. Supabase hat seinen Schlüsseln neue Namen gegeben: sb_publishable_ hat den anon-Schlüssel abgelöst, sb_secret_ den service_role-Schlüssel. Ein Schlüssel, der mit sb_publishable_ beginnt, gehört also in deine App. Einer, der mit sb_secret_ beginnt, niemals. Ältere Projekte tragen weiterhin die ursprünglichen anon- und service_role-Schlüssel, und die funktionieren weiter; unter Umständen siehst du beide Paare nebeneinander im Dashboard.

Ich habe bereits einen geheimen Schlüssel in meine App eingefügt. Was jetzt?

Tausche ihn zuerst aus: Erzeuge im Dashboard des Anbieters einen neuen Schlüssel und widerrufe den alten. Nur das schließt die Tür wirklich. Ihn aus dem Code zu löschen reicht nicht, weil die alte Version noch in deiner Versionsgeschichte und in zwischengespeicherten Kopien steckt. Verschiebe danach die Arbeit, die ihn brauchte, auf einen Server, und prüfe Abrechnung und Protokolle auf Dinge, die nicht von dir stammen.

Geschrieben von

Vlad Tkachenko

Gründer, Reeve

Ich schaue mir den ganzen Tag Apps an, die mit Lovable, Bolt, v0, Cursor und Replit gebaut wurden, und die kurze Liste von Fehlern, die darin immer wieder auftauchen.

Mehr über den Autor

Weiterlesen

Unsicher, wie es um deine eigene App steht?

Starte einen kostenlosen Scan und erhalte in rund 20 Sekunden eine verständliche Note von A bis F. Ohne Konto, ohne Karte.

App kostenlos scannen

Automatisierte externe Prüfung, kein vollständiges Audit. Das Fehlen von Funden ist keine Garantie für Sicherheit.