Supabase-Key-Checker

Zwei Keys, äußerlich identisch, gegenteilige Folgen.

Füge einen Supabase-Key ein und finde heraus, welchen du in der Hand hast. Es wird nichts übertragen: der Key wird in deinem Browser dekodiert, denn einen produktiven Datenbankzugang zur Prüfung an eine Website zu schicken wäre genau der Fehler, den dieses Tool abfangen soll.

Läuft in deinem Browser. Uns wird nichts geschickt.

Was hier geprüft wird, und wie eine gute Antwort aussieht

Warum sich die beiden Keys mit bloßem Auge nicht unterscheiden lassen

Beide sind JSON Web Tokens. Beide sind gleich lang, beginnen mit denselben Zeichen und werden aus benachbarten Zeilen derselben Einstellungsseite kopiert. Der einzige Unterschied ist ein Claim, vergraben im mittleren Abschnitt: role, das entweder anon oder service_role lautet. Dieser Abschnitt ist Base64, keine Verschlüsselung, also kann ihn jeder lesen. Dieses Tool liest ihn nur für dich.

service_role: umgeht jede Regel, die du geschrieben hast

Dieser Key ignoriert Row Level Security vollständig. Nicht "hat weitreichende Rechte": ignoriert die Regeln. Jede Policy, die eine Zeile mit auth.uid() vergleicht, wird übersprungen, sodass der Inhaber jede Zeile jeder Tabelle liest, ändert und löscht, als wären die Regeln nie geschrieben worden. Wenn er je in Client-Code, einem öffentlichen Repository, einem Screenshot oder einem Support-Ticket aufgetaucht ist, behandle ihn als bereits kompromittiert und roll ihn. Automatische Sammler finden Zugangsdaten in öffentlichen Bundles binnen Stunden.

anon: öffentlich by Design, und nur so sicher wie deine Regeln

Der anon-Key ist dafür gedacht, an Browser zu gehen, ihn in deinem JavaScript zu finden ist also kein Problem. Was er wert ist, hängt vollständig von deiner Row Level Security ab. Mit aktiviertem RLS und Regeln, die Zeilen mit dem angemeldeten Nutzer vergleichen, ist er genau das, was er sein soll. Mit abgeschaltetem RLS auf einer Tabelle, oder einer Regel, die using (true) lautet, liest dieser öffentliche Key diese ganze Tabelle für jeden, der deine Seite geladen hat, also für alle.

Wie das in der Praxis schiefgeht

Fast immer gleich. Irgendetwas liefert ein leeres Array oder einen Berechtigungsfehler, das Schnellste, damit das aufhört, ist der andere Key, und es funktioniert sofort, weil dieser Key die Regel ignoriert, die dich zu Recht aufgehalten hat. Der Fehler war das System bei der Arbeit. Keys zu tauschen behebt kein Zugriffsproblem, es entfernt die Zugriffskontrolle, und es geht meistens live, weil danach sichtbar nichts kaputt ist.

Der Ablauf-Claim

Supabase-Keys tragen ein exp. Wir melden es, weil ein abgelaufener Key eine ganze Klasse verwirrender Produktionsfehler erklärt, aber beachte: Ablauf ist kein Gegenmittel gegen Offenlegung. Ein service_role-Key, der geleakt ist und später ablief, war das ganze Fenster über gültig, in dem er öffentlich war.

So behebst du es

Wenn das ein anon-Key ist, gibt es hier nichts zu beheben und die Arbeit liegt in deinen Policies. Wenn es ein service_role-Key ist, der je in die Nähe eines Browsers gekommen ist, roll zuerst und untersuch danach.

RLS aktivieren und eine echte Policy schreiben
-- A table without this is readable by anyone holding the anon key,
-- which is everyone who has loaded your site.
alter table public.documents enable row level security;

-- Scope every row to its owner. "using" filters reads;
-- "with check" constrains writes. A policy with only "using" lets
-- someone insert rows they will not be able to see afterwards.
create policy "owners read their documents"
  on public.documents for select
  using (auth.uid() = user_id);

create policy "owners write their documents"
  on public.documents for insert
  with check (auth.uid() = user_id);
Tabellen finden, bei denen RLS noch aus ist
-- Run this in the SQL editor. Every row it returns is a table the
-- anon key can read in full.
select tablename
from pg_tables
where schemaname = 'public'
  and rowsecurity = false;

-- And this one, for the policy that looks present but grants
-- everything to everyone:
select tablename, policyname, qual
from pg_policies
where schemaname = 'public'
  and qual = 'true';
Wohin welcher Key gehört
anon key
  -> client code is fine. VITE_SUPABASE_ANON_KEY,
     NEXT_PUBLIC_SUPABASE_ANON_KEY and friends are correct.

service_role key
  -> server only. An edge function, a server route, or your host's
     secret store. Never in a variable your bundler inlines, which
     means never behind NEXT_PUBLIC_, VITE_, PUBLIC_ or REACT_APP_.

# Check what you actually shipped:
grep -rE "service_role|SERVICE_ROLE" ./dist ./build ./.next 2>/dev/null

# And check the history, because deleting a key from a file does
# not delete it from the repository:
git log -p -S 'service_role' -- . | head -50
Rollen
1. Supabase dashboard -> Project Settings -> API -> rotate.
2. Update the secret in your host and redeploy the server side.
3. Confirm nothing still reads the old value, then revoke it.
4. Read the logs for the exposure window. A key that was public
   for a week deserves a look at what it was used for, not just
   a replacement.
Was dieses Tool nicht sehen kann

Das hier liest einen Key, den du schon hast. Es kann dir nicht sagen, ob er in deinem Produktions-Bundle steckt, ob er in deiner Git-Historie liegt, oder ob die Tabellen, die er erreicht, Policies haben, die den Namen verdienen. Für das Erste lass den Scanner für offengelegte API-Keys auf deine deployte URL los. Für die anderen beiden liegt die Antwort im Repository und nicht in irgendetwas, das von außen sichtbar wäre.

Auch den Code scannenZum Einstieg kostenlos. Repository verbinden, der erste Scan läuft in rund zwei Minuten.