MELLEPRISE MELLEPRISE MP Blog
← Alle Artikel

Dein supabase anon key ist öffentlich — das ist kein Bug

Pastell-Comic eines Hausschlüssels an der Haustür mit Schild: Jeder darf rein, wenn du kein Schloss setzt

Dein supabase anon key steckt schon im JavaScript-Bundle, das jeder in DevTools öffnen kann — und ihn in eine clientseitige .env zu legen, macht ihn nicht privat.

Preview funktioniert. Auth-Screens sehen solide aus. Du suchst im Repo nach geleakten Secrets und gerätst in Panik, wenn du eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 in einer React-Datei findest. Du postest den Key in ein „ist das exponiert?“-Forum. Die Antwort ist ja — und für die anon-Rolle ist das normal. Der echte Fehler: einen öffentlichen Projekt-Identifier wie ein Passwort behandeln, während Row Level Security aus bleibt.

supabase anon key ist kein Passwort

Supabase gibt dir bei Projekterstellung zwei API-Keys: anon und service_role. Sie sind nicht austauschbar.

Der Anon-Key ist für Browser, Mobile Apps und jede nicht vertrauenswürdige Umgebung gedacht. Er authentifiziert Requests als anon-Postgres-Rolle — oder als eingeloggter Nutzer, wenn du ein Session-JWT anhängst. Supabase dokumentiert, dass dieser Key sichtbar sein wird. Sicherheit kommt nicht aus Geheimhaltung. Sie kommt aus Policies, die begrenzen, was die anon-Rolle lesen oder schreiben darf.

Der Service-Role-Key umgeht Row Level Security. Er ist ein Root-Key für deine Datenbank-API. Er gehört nur auf Server, die du kontrollierst: Edge Functions, Node-API, CI-Skripte. Taucht die Service Role in Frontend-Code oder einem öffentlichen GitHub-Repo auf, rotiere sie sofort. Dieser Leak ist ein Notfall.

Was Gründer oft falsch verstehen:

  • „API-Key“ heißt „geheim halten wie Stripes Secret Key“
  • Anon-Key auf eine Server-Route legen ohne RLS — und sich sicher fühlen
  • Service Role im Client nutzen, weil Inserts „ohne Policies funktionierten“
  • Anon-Key rotieren und denken, das fixt einen Datenleak — Angreifer lesen den neuen Key aus dem Bundle

Stripes Publishable Key folgt dem gleichen Prinzip: öffentlich by Design, gefährlich nur, wenn dein Backend dem Client vertraut. Deine Datenbankschicht muss Regeln serverseitig durch RLS erzwingen — nicht durch Hoffnung, dass niemand einen String aus dem Network-Tab kopiert.

Key im Frontend

Jede Lovable-, Vite- oder Next.js-App, die Supabase aus dem Browser aufruft, shipt den Anon-Key. Build-Tools injizieren VITE_SUPABASE_ANON_KEY oder NEXT_PUBLIC_SUPABASE_ANON_KEY beim Compile. Der Wert landet in minifiziertem JS auf deinem CDN. Keine Obfuscation überlebt fünf Minuten.

Wie jemand ihn in der Praxis extrahiert:

  1. Deine Live-Site in Chrome öffnen.
  2. DevTools → Sources oder Network.
  3. Nach supabase.co oder eyJ suchen.
  4. Key und Projekt-URL aus Client-Init oder Fetch-Header kopieren.

Das ist kein raffinierter Angriff. So ist die Plattform gebaut. KI-Assistenten generieren createClient(url, anonKey) in der ersten Datei, die sie anfassen — weil Supabase-Quickstart es so sagt. RLS fügen sie selten im selben Schritt hinzu, weil Policies in SQL leben, nicht in React.

Was du im Repo siehst:

  • .env mit VITE_-Prefix — trotzdem ins Client-Bundle gebündelt
  • Hardcodierte Keys in lib/supabase.ts — gleiche Exposition, schlechtere Hygiene
  • Kommentare „nicht committen“ über einem Key, der schon in der Git-History steckt
  • Server-Routen, die Supabase proxen und versehentlich die Service Role weiterreichen

Den Anon-Key hinter deiner eigenen API zu verstecken kann casual Scraping reduzieren. Es ersetzt keine Policies. Dein Proxy wird die neue Angriffsfläche. Wenn jede Query nicht validiert und scoped ist, hast du das Problem verschoben, nicht gelöst.

Warum KI den Key in Client-Code legt

Supabase JavaScript Client ist für direkten Browser-Zugang designed. Der schnellste Tutorial-Pfad: URL pasten, Anon-Key pasten, .from('table').select() aufrufen. Das Modell folgt diesem Pfad. Row Level Security ist ein zweiter Dashboard-Schritt mit SQL. Die Preview „funktioniert“, weil Testdaten leer sind oder Grants auf einem Dev-Projekt weit offen sind.

Du bist nicht nachlässig. Die Toolchain optimiert für grüne Preview, nicht für ein Threat Model. Der Fix ist architektonisch — nicht „Key aus Git entfernen“.

Was der supabase anon key tatsächlich steuert

Der Anon-Key signiert HTTP-Requests an PostgREST. Postgres wendet dann an:

  1. Rollen-Grants — welche Tabellen die anon-Rolle überhaupt anfassen darf
  2. Row Level Security — welche Zeilen deine Policies für diese Rolle oder das JWT passieren
  3. Deine App-Auth-Session — wenn vorhanden, auth.uid() in Policies

Mit deaktiviertem RLS erlauben Grants allein oft SELECT * auf öffentlichen Tabellen. Der Anon-Key reicht zum Download. Mit aktiviertem RLS und ohne Policies blockiert alles — auch deine App, bis du Allow-Regeln schreibst.

Kombiniere den Anon-Key mit supabase rls, bevor du die App als privat behandelst. Der Anon-Key ist die Türklingel. Policies sind das Schloss.

Daten absichern — Schritt für Schritt

Auf demselben Supabase-Projekt machen, auf das dein Produktions-Bundle zeigt — nicht auf einer Wegwerf-Dev-Kopie.

  1. Prüfen, welcher Key wo liegt. Repo nach service_role durchsuchen. Taucht er in Client-Code auf, Key im Supabase-Dashboard → Settings → API rotieren und Nutzung in Server-only Env-Var verschieben.
  2. RLS aktivieren auf jeder Tabelle mit Nutzerdaten, Billing-Status oder privatem Inhalt.
  3. Policies schreiben für SELECT, INSERT, UPDATE, DELETE — scoped auf auth.uid() oder dein Membership-Modell.
  4. Ausgeloggt testen. REST-API nur mit Anon-Key und ohne JWT aufrufen. Ergebnis: leer oder 401, nicht volle Tabellen.
  5. Eingeloggt testen. Normale App-Flows. Fehlende INSERT-Policies zeigen sich als stille Speicher-Fehler.
  6. Policies als Migrationen committen, damit der nächste Deploy nicht von Dashboard-Klicks abdriftet.

Anon-Key rotieren ist optionale Hygiene nach einem öffentlichen Repo-Leak. Es fixt kein fehlendes RLS. Jeder, der deine Site besucht, liest den neuen Key aus dem Bundle.

Pre-Launch-Checkliste

  • Anon-Key nur in client-sicheren Env-Vars (VITE_, NEXT_PUBLIC_)
  • Service-Role-Key nur auf Server, nie in Browser-Bundles oder öffentlichen Repos
  • RLS auf allen sensiblen Tabellen aktiviert
  • Ausgeloggte Anon-Requests können keine privaten Zeilen listen
  • Policies passen dazu, wie die App user_id beim Insert setzt
  • Produktionsprojekt ist dasselbe, das im Live-Bundle referenziert wird

Der supabase anon key war nie dein Geheimnis. Row Level Security und Server-only Keys sind es.

Auth-URLs, Webhooks, Env-Vars und TLS scheitern am selben Launch-Wochenende wie Datenbank-Exposition. Die lovable deploy-Checkliste deckt die anderen vier Produktions-Einstellungen ab, damit du nicht Leak für Leak überrascht wirst.

FAQ

Ist der supabase anon key geheim?

Nein. Supabase erwartet den Anon-Key in clientseitigem Code. Er identifiziert dein Projekt und signiert Requests als anon-Rolle. Behandle ihn als öffentlich. Schütze Daten mit Row Level Security, nicht durch Verstecken des Keys.

Kann jemand mit dem supabase anon key meine Daten stehlen?

Er kann deine Supabase-REST-API mit demselben Key aufrufen, den deine App nutzt. Ohne RLS-Policies kann das jede Zeile exponieren, die deine Grants erlauben. Mit korrekten Policies greift der Key nur auf das zu, was deine Regeln für eingeloggte oder anonyme Nutzer erlauben.

Soll ich den supabase anon key in eine Server-only Env-Var verschieben?

Anon auf den Server zu legen versteckt ihn vor neugierigen Blicken, ersetzt aber kein RLS. Jeder Browser-Client braucht trotzdem einen öffentlichen Key oder einen Proxy, den du kontrollierst. Service Role nur auf vertrauenswürdigen Servern. Nie die Service-Role-Key ins Frontend shippen.