// docs / baas security / supabase rls scanner
Supabase-RLS-Scanner: Tabellen mit fehlender oder defekter Row-Level Security finden
Row-Level Security (RLS) ist das Einzige, was zwischen den Daten deiner Kunden und dem Internet steht, wenn du eine Supabase-gestützte App ausrollst. KI-Coding-Tools generieren RLS-förmigen Code, der kompiliert, ausgeliefert wird und leise Daten leakt — Tabellen ohne aktiviertes RLS, Policies, die lesen aber nie einschränken, Prädikate, die eine Spalte mit sich selbst vergleichen. Dieser Artikel zeigt, was ein Supabase-RLS-Scanner von außen beweisen kann, die vier kaputten RLS-Formen, die in Vibe-coded Apps auftauchen, und wie du dein eigenes Deployment in unter einer Minute scannst.
Was ein externer RLS-Scan beweisen kann
Ein passiver RLS-Scan läuft gegen den PostgREST-Endpunkt, den Supabase unter https://[project].supabase.co/rest/v1/ bereitstellt. Er nutzt nur den veröffentlichbaren anon-Key — denselben, den dein Browser verwendet — und sondiert Tabellen-Listen-Metadaten, anonyme Lesezugriffe und anonyme Schreibzugriffe. Er authentifiziert sich nie als Nutzer und greift nie auf Service-Role-Privilegien zu. Alles, was er tun kann, kann ein nicht authentifizierter Angreifer im Internet auch tun.
Von außerhalb der Datenbank kann ein Scanner Folgendes mit hoher Konfidenz bestätigen:
- RLS ist auf einer Tabelle deaktiviert. PostgREST liefert Zeilen für ein anonymes
SELECT, wenn RLS aus ist oder eine Policy es erlaubt. Beides ist ein Befund. - Die anonyme Rolle kann Tabellen auflisten. Ein
GET /rest/v1/mit dem Anon-Key liefert das OpenAPI-Schema für jede Tabelle zurück, auf die die Rolleanonirgendein Privileg besitzt. KI-generierte Apps vergeben häufigUSAGEauf das Schema undSELECTauf jede Tabelle, was die komplette Schemakarte preisgibt, selbst wenn RLS die eigentlichen Lesezugriffe verweigert. - Die anonyme Rolle kann INSERT ausführen. Ein sondierendes
POSTmit einem Ratet zur Spaltenform gelingt, wenn RLS keineINSERT-Policy hat, die es verweigert — selbst wennSELECTgesperrt ist. - Der Service-Role-Key liegt im Browser-Bundle. Angrenzend an RLS: findet ein Scanner
SUPABASE_SERVICE_ROLE_KEYoder irgendein JWT mitrole: service_roleim JavaScript-Bundle, ist RLS hinfällig — wer diesen Key besitzt, umgeht jede Policy.
Was ein externer Scan nicht beweisen kann
Sei ehrlich über die Grenzen des Scanners. Ein externer RLS-Scan kann deine pg_policies-Tabelle, deine Migrationsdateien und das exakte Prädikat einer Policy nicht lesen. Er schließt aus Black-Box-Verhalten, was bedeutet, dass er manchmal einen Befund meldet, der sich als beabsichtigte öffentliche Daten herausstellt (eine Marketing-Newsletter-Tabelle, ein öffentlicher Produktkatalog). Der FixVibe-Report markiert diese als mittlere Konfidenz, wenn der Scanner die Absicht nicht eindeutig bestimmen kann — schau dir den Tabellennamen an und entscheide.
Die vier kaputten RLS-Formen, die KI-Tools produzieren
Wenn du Cursor, Claude Code, Lovable oder Bolt auf Supabase ansetzt, tauchen über tausende Apps hinweg dieselben vier kaputten RLS-Muster auf. Jedes besteht die Typprüfung, kompiliert und wird ausgeliefert:
Form 1: RLS nie aktiviert
Der häufigste Fehlermodus. Die Migration erstellt die Tabelle, aber der Entwickler (oder das KI-Tool) vergisst ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST liefert die komplette Tabelle fröhlich an jeden mit dem Anon-Key aus. Fix: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;. FORCE ist nicht optional — ohne es umgehen Tabelleneigentümer (und jede Rolle mit Tabelleneigentümerschaft) RLS.
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;Form 2: RLS aktiviert, keine Policies
Ein subtilerer Fehler. RLS ist aktiviert, aber es sind keine Policies geschrieben. Der Standard in PostgreSQL ist verweigern, also sehen authentifizierte Nutzer nichts — und der Entwickler fügt USING (true) hinzu, damit die App funktioniert, was allen erlaubt, alles zu lesen. Fix: schreibe eine Policy, die per auth.uid() einschränkt: CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); und eine passende INSERT/UPDATE/DELETE-Policy.
CREATE POLICY "select_own"
ON public.[name]
FOR SELECT
USING (auth.uid() = user_id);Form 3: Policy vergleicht Spalte mit sich selbst
A copy-paste artefact. The developer writes <code>USING (user_id = user_id)</code> — which is always true — instead of <code>USING (auth.uid() = user_id)</code>. Type-checks pass; the policy permits every row. <strong>Fix:</strong> always compare a column to a function call (<code>auth.uid()</code>, <code>auth.jwt()->>'org_id'</code>, etc.), never to itself or to a constant.
Form 4: Policy auf SELECT, aber nicht auf INSERT/UPDATE
Der Entwickler sperrt Lesezugriffe, vergisst aber Schreibzugriffe. RLS-Policies sind pro Befehl. FOR SELECT schützt nur Lesezugriffe; ein anonymer Client kann immer noch INSERT, wenn keine Policy es verweigert. Fix: schreibe eine Policy pro Befehl oder verwende FOR ALL mit expliziten USING- und WITH CHECK-Klauseln.
Wie der Supabase-RLS-Scanner von FixVibe funktioniert
Der Check baas.supabase-rls läuft in drei Stufen, jede mit expliziten Konfidenzstufen:
- Stufe 1 — Fingerprinting. Der Scanner crawlt die deployte App, parst ihr JavaScript-Bundle und extrahiert die Supabase-Projekt-URL und den Anon-Key aus der Runtime-Konfiguration. Kein DNS-Raten, keine Brute-Force — er liest, was der Browser liest.
- Stufe 2 — Schema-Discovery. Ein einzelnes
GET /rest/v1/mit dem Anon-Key liefert das OpenAPI-Schema für jede Tabelle, die die Anon-Rolle sehen kann. Der Scanner notiert Tabellennamen, liest in dieser Stufe aber keine Zeilendaten. - Stufe 3 — Lese- und Schreib-Sondierungen. Für jede entdeckte Tabelle gibt der Scanner ein anonymes
SELECTmitlimit=1ab. Liefert das Zeilen zurück, ist RLS permissiv. Der Scanner stoppt dort — er zählt keine Zeilen auf, paginiert nicht, modifiziert keine Daten. INSERT-Sondierungen sind hinter verifizierter Domain-Eigentümerschaft und explizitem Opt-in eingegrenzt; sie laufen nie gegen unverifizierte Ziele.
Jeder Befund wird mit der exakten Request-URL, dem Response-Status, der Response-Form (nur Header) und dem Tabellennamen geliefert. Der KI-Fix-Prompt am Ende des Befunds ist ein Copy-Paste-SQL-Block, den du im Supabase-SQL-Editor ausführst.
Was tun, wenn der Scanner etwas findet
Jeder RLS-Befund ist ein Runtime-Notfall. Öffentliche PostgREST-Endpunkte werden binnen Minuten von Angreifern gescannt. Die Behebungssequenz ist mechanisch:
- Auditiere jede Tabelle. Führe
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';im Supabase-SQL-Editor aus. Jede Zeile mitrowsecurity = falseist ein Problem. - Aktiviere RLS auf jeder öffentlichen Tabelle. Standardmäßig
ENABLE ROW LEVEL SECURITYundFORCE ROW LEVEL SECURITYauf jeder erstellten Tabelle — mach es zur Migrations-Vorlage. - Schreibe Policies Befehl für Befehl. Verwende nicht
FOR ALL USING (true). Schreibe explizite Policies für SELECT, INSERT, UPDATE, DELETE — jede aufauth.uid()oder eine Org-ID-Spalte ausauth.jwt()eingegrenzt. - Verifiziere mit einem zweiten Konto. Registriere dich als anderer Nutzer, versuche, die Datensätze eines anderen Nutzers direkt über die REST-API zu lesen. Ist die Antwort
200, ist die Policy kaputt. - Erneut scannen. Nach Anwendung des Fix lass einen FixVibe-Scan erneut gegen dieselbe URL laufen. Der
baas.supabase-rls-Befund sollte verschwinden.
-- Audit every table for missing RLS. Run in the Supabase SQL editor.
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY rowsecurity, tablename;Wie sich das mit anderen Scannern vergleicht
Die meisten generischen DAST-Tools (Burp Suite, OWASP ZAP, Nessus) wissen nicht, was PostgREST ist. Sie crawlen deine App, ignorieren den /rest/v1/-Pfad und berichten über die HTML-Seiten, die sie verstehen. Snyk und Semgrep sind statische Analysetools — sie finden Migrationsdateien in deinem Repo mit fehlenden RLS-Aufrufen, können aber nicht beweisen, dass die deployte Datenbank falsch konfiguriert ist. FixVibe sitzt in dieser Lücke: passiv, BaaS-bewusst, fokussiert darauf, was ein nicht authentifizierter Angreifer von der öffentlichen URL aus beweisen kann.
Häufig gestellte Fragen
Liest oder modifiziert der Scanner meine Daten?
Nein. Passive Scans senden höchstens ein SELECT ... limit=1 pro entdeckter Tabelle, um zu bestätigen, ob RLS anonyme Lesezugriffe erlaubt. Der Scanner notiert die Response-Form, nicht den Zeileninhalt. INSERT-, UPDATE- und DELETE-Sondierungen sind hinter verifizierter Domain-Eigentümerschaft eingegrenzt und laufen nie gegen unverifizierte Ziele.
Funktioniert das, wenn mein Supabase-Projekt pausiert ist oder auf einer eigenen Domain liegt?
Pausierte Projekte liefern 503 auf jede Anfrage — der Scanner meldet das Projekt als nicht erreichbar. Eigene Domains funktionieren, solange die deployte App das Supabase-Client-SDK im Browser lädt; der Scanner extrahiert die Projekt-URL ohnehin aus dem Bundle.
Was, wenn mein Anon-Key rotiert wird oder mein Publishable-Key sich ändert?
Erneut scannen. Der Scanner extrahiert den Key bei jedem Lauf neu aus dem aktuellen Bundle. Die Rotation invalidiert nur den vorherigen Report, nicht den Policy-Zustand der Datenbank.
Prüft der Scanner das neue Supabase-Publishable-Key-Modell (<code>sb_publishable_*</code>)?
Ja. Der Detektor erkennt sowohl die alten anon-JWTs als auch die neueren sb_publishable_*-Keys und behandelt sie identisch — beide sind als öffentlich gedacht und beide lassen RLS als einzige Verteidigungslinie zurück.
Nächste Schritte
Lass einen kostenlosen FixVibe-Scan gegen deine Produktions-URL laufen — der baas.supabase-rls-Check ist in jedem Tarif einschließlich der kostenlosen Stufe aktiviert. Für eine tiefere Lektüre, was sonst noch aus einem Supabase-Projekt leaken kann, siehe Supabase-Service-Role-Key in JavaScript offengelegt und Supabase-Storage-Bucket-Sicherheits-Checkliste. Für den Gesamtüberblick über alle BaaS-Anbieter siehe BaaS-Fehlkonfigurations-Scanner.
