Zum Inhalt springen

Leitfaden

Supabase Row Level Security richtig einsetzen

Von Lucas Weber, CyberSec42 · Stand 28.10.2026

CyberSec42

Bei Supabase steht der öffentliche Schlüssel (anon key oder publishable key) absichtlich im Frontend. Wer Ihre App öffnet, kann ihn auslesen und damit direkt die Datenbank-API ansprechen. Was er dort sieht und ändern darf, entscheidet allein Row Level Security (RLS): Ist RLS auf einer Tabelle ausgeschaltet oder lässt eine Policy alles durch, sind die Daten für jeden offen. Supabase RLS richtig einsetzen heißt deshalb, RLS auf jeder Tabelle einzuschalten, je Operation eine Policy mit Bezug auf auth.uid() zu schreiben und das Ergebnis mit einem zweiten Konto zu prüfen.

Kurz gefasst.

Wie Supabase schützt

Öffentlicher Schlüssel, Rollen, Policies.

Zum Abschnitt

Typische Fehler

Von fehlender RLS bis zum offenen Bucket.

Zum Abschnitt

Mit KI gebaute Apps

Warum der Fehler unsichtbar bleibt.

Zum Abschnitt

Policy-Beispiele

Minimal und korrekt, zum Übernehmen.

Zum Abschnitt

Selbst prüfen

Security Advisor und Test mit zweitem Konto.

Zum Abschnitt

Checkliste

Alle Punkte vor dem Livegang.

Zum Abschnitt

Wie Supabase Zugriffe schützt

Supabase stellt Ihre Postgres-Datenbank über eine automatisch erzeugte API bereit, erreichbar unter https://<projekt>.supabase.co/rest/v1/. Ihre App spricht diese API direkt aus dem Browser an, ohne eigenen Server dazwischen. Dafür braucht sie einen Schlüssel, und der steht zwangsläufig im ausgelieferten JavaScript.

Das ist gewollt. Die Supabase-Dokumentation zu API-Schlüsseln nennt den publishable key ausdrücklich „sicher, um ihn online zu zeigen“, etwa auf einer Webseite oder im Quellcode, und begründet das so: Jeder kann ihn lesen, deshalb erreicht er nur, was Row Level Security erlaubt. Der ältere anon key ist laut Dokumentation die Vorgängerversion des publishable key. Supabase hat angekündigt, die bisherigen Schlüssel anon und service_role durch publishable und secret keys abzulösen.

Jede Anfrage läuft in Postgres unter einer Rolle: anon für Besucher ohne Anmeldung, authenticated für angemeldete Nutzer. Zwei Prüfungen entscheiden dann über den Zugriff:

Die Dokumentation weist darauf hin, dass in bestehenden Projekten eine neue Tabelle im Schema public mit allen Rechten für anon und authenticated startet. Ohne RLS bedeutet das: Wer die Projekt-URL und den öffentlichen Schlüssel kennt, darf lesen, ändern und löschen.

Ist RLS eingeschaltet und gibt es noch keine Policy, gilt das Gegenteil: Über die API ist dann nichts mehr zu sehen, bis Sie Policies anlegen. Postgres arbeitet in diesem Fall mit einer Standard-Ablehnung. Tabellen, die Sie im Supabase-Dashboard anlegen, haben RLS laut Dokumentation bereits eingeschaltet. Tabellen aus dem SQL-Editor, aus Migrationen oder aus anderen Werkzeugen müssen Sie selbst absichern.

Die typischen Fehler

Die folgenden Fehler sind keine Schwächen von Supabase, sondern Konfigurationen, die genau so wirken, wie sie geschrieben sind. Die meisten meldet der Security Advisor.

1. RLS ist nicht eingeschaltet

Der folgenschwerste Fall: RLS ist aus, die Grants sind vorhanden, der öffentliche Schlüssel reicht für vollen Zugriff. Typische Ursache ist eine Tabelle, die per SQL oder Migration angelegt wurde, wo RLS nicht automatisch aktiv ist. Ein Sonderfall: Es gibt Policies, aber RLS ist ausgeschaltet. Die Policies sehen dann nach Schutz aus, wirken aber nicht.

2. Policies, die alles erlauben

Eine Policy mit using (true) oder with check (true) lässt jede Zeile durch. Für wirklich öffentliche Inhalte wie veröffentlichte Blogbeiträge ist das in Ordnung, für Kundendaten nicht. Supabase beschreibt diese Konstellation in seiner Prüfregel als „häufige Fehlkonfiguration“ und nennt als Ursachen Platzhalter aus der Entwicklung, die nie ersetzt wurden, Kopierfehler aus Beispielen und die Annahme, andere Policies würden schon einschränken.

Diese Annahme ist falsch. Mehrere Policies für dieselbe Operation werden in Postgres standardmäßig mit ODER verknüpft. Eine einzige zu weite Policy hebelt damit alle engeren aus. Supabase empfiehlt zudem je eine eigene Policy für select, insert, update und delete.

3. Der geheime Schlüssel im Frontend

Der service_role key, in der neuen Form der secret key, gehört zur Rolle service_role. Diese Rolle hat das Recht, RLS zu umgehen. Die Dokumentation sagt unmissverständlich: Ein secret key umgeht jede Policy und gehört nie in einen Browser, in eine ausgelieferte App oder in die Versionsverwaltung. Neue secret keys werden zwar abgewiesen, wenn sie erkennbar aus einem Browser kommen, laut Supabase anhand des User-Agent. Aus anderen Werkzeugen funktioniert ein abgeflossener Schlüssel aber weiterhin. Lag er je im Frontend oder in einem öffentlichen Repository, ersetzen Sie ihn und deaktivieren oder löschen Sie den alten.

4. Policies, die nur das Lesen regeln

Eine saubere select-Policy schützt das Lesen. Schreibzugriffe brauchen eigene Regeln, und dort liegen die feineren Fehler:

Ein Detail, das oft zu Fehlersuche führt: Damit update wie erwartet funktioniert, braucht die Tabelle laut Dokumentation auch eine passende select-Policy. Wer dann, um die App „zum Laufen zu bringen“, eine weite Policy ergänzt, öffnet oft mehr, als er wollte.

5. Der Vergleich mit auth.uid() fehlt oder ist falsch

auth.uid() liefert die ID des angemeldeten Nutzers. Eine Policy, die nur prüft, ob jemand angemeldet ist (to authenticated using (true)), gibt jedem Konto alle Zeilen, auch fremde. Bei Apps mit freier Registrierung ist das praktisch dasselbe wie öffentlich. Der Vergleich mit der Besitzer-Spalte gehört in jede Policy für personenbezogene Daten. Zwei weitere Punkte aus der Dokumentation:

6. Berechtigungen aus user_metadata

Die Metadaten in raw_user_meta_data kann der angemeldete Nutzer laut Dokumentation selbst ändern. Eine Policy, die Rollen oder Teamzugehörigkeit aus user_metadata liest, lässt sich deshalb vom Nutzer aushebeln. Für Berechtigungen eignet sich app_metadata, die der Nutzer nicht ändern kann, oder eine eigene Tabelle.

7. Öffentliche Storage-Buckets

Supabase Storage kennt öffentliche und private Buckets. Bei einem öffentlichen Bucket umgeht der Abruf von Dateien laut Dokumentation die Zugriffskontrolle: Wer die URL kennt, lädt die Datei. Hochladen, Löschen, Verschieben und Kopieren bleiben geschützt. Für Profilbilder ist ein öffentlicher Bucket passend, für Rechnungen, Ausweise oder Verträge nicht. Kommt zu einem öffentlichen Bucket noch eine weite select-Policy auf storage.objects hinzu, lässt sich der Inhalt zusätzlich auflisten. Auch dafür gibt es einen eigenen Prüfpunkt im Security Advisor.

8. Views und Funktionen, die RLS umgehen

Views umgehen RLS standardmäßig, weil sie in der Regel mit dem Nutzer postgres angelegt werden und dessen Rechte nutzen. Eine View über eine geschützte Tabelle gibt dann alle Zeilen heraus. Ab Postgres 15 lässt sich das mit security_invoker = true ändern. Ähnlich bei Funktionen mit security definer: Sie laufen mit den Rechten ihres Erstellers. Darf anon oder authenticated eine solche Funktion ausführen, ist sie über /rest/v1/rpc/<name> aufrufbar und kann Daten liefern, die RLS sonst zurückhalten würde.

Warum mit KI gebaute Apps besonders betroffen sein können

Werkzeuge wie Lovable, Bolt oder Cursor erzeugen aus einer Beschreibung in kurzer Zeit eine funktionierende App. Lovable bindet dafür nach eigener Darstellung Supabase als Backend an, und auch andere Werkzeuge können Supabase-Code erzeugen. Die Lovable-Dokumentation nennt falsch konfigurierte RLS-Regeln selbst „eine häufige Ursache für Datenlecks“. Das Risiko liegt weniger im erzeugten Code als im Prüfvorgang danach. Eine fehlende oder zu weite Policy ist im normalen Gebrauch unsichtbar:

Der öffentlich dokumentierte Fall: CVE-2025-48757

Im Mai 2025 wurde CVE-2025-48757 veröffentlicht. Der Eintrag beschreibt unzureichende Row-Level-Security-Policies in Lovable bis zum 15.04.2025, durch die nicht angemeldete Angreifer aus der Ferne beliebige Tabellen der erzeugten Anwendungen lesen oder beschreiben konnten. Der Eintrag ist als „disputed“ markiert: Der Anbieter bestreitet ihn mit der Begründung, dass jeder Kunde der Plattform die Verantwortung für den Schutz der Daten seiner Anwendung übernimmt.

Lovable selbst schreibt in seinem Blog vom Juni 2025, dass mit Supabase verbundene Apps auf RLS beruhen und fehlende oder zu weite Policies Daten offenlegen können. Nach eigener Angabe prüft Lovable Apps vor dem Veröffentlichen automatisch. Die Lovable-Dokumentation hält zugleich fest, dass die Werkzeuge keine vollständige Sicherheit garantieren können, dass Sie selbst dafür verantwortlich sind, dass Ihre App die für ihren Zweck passenden Anforderungen erfüllt, und empfiehlt für Apps mit sensiblen Daten eine zusätzliche professionelle Sicherheitsprüfung.

Policy-Beispiele: minimal und korrekt

Das folgende Muster deckt den häufigsten Fall ab: Jeder Nutzer sieht und bearbeitet nur seine eigenen Einträge. Es folgt der Struktur der Supabase-Dokumentation zu RLS. Die Tabelle notes hat eine Spalte user_id vom Typ uuid.

-- RLS einschalten und nur nötige Rechte vergeben
alter table public.notes enable row level security;
revoke all on table public.notes from anon, authenticated;
grant select, insert, update, delete on table public.notes to authenticated;

-- Lesen: nur eigene Zeilen
create policy "notes_select_own"
on public.notes for select
to authenticated
using ( (select auth.uid()) = user_id );

-- Anlegen: nur mit der eigenen user_id
create policy "notes_insert_own"
on public.notes for insert
to authenticated
with check ( (select auth.uid()) = user_id );

-- Ändern: nur eigene Zeilen, und sie bleiben eigene
create policy "notes_update_own"
on public.notes for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );

-- Löschen: nur eigene Zeilen
create policy "notes_delete_own"
on public.notes for delete
to authenticated
using ( (select auth.uid()) = user_id );

-- Index auf die Spalte, nach der die Policies filtern
create index on public.notes (user_id);

Drei Details stecken darin. (select auth.uid()) statt auth.uid() ist eine Empfehlung der Dokumentation für bessere Leistung. to authenticated sorgt dafür, dass die Policy für Besucher ohne Anmeldung gar nicht erst ausgewertet wird. Und die Grants beschränken anon zusätzlich, denn Policies allein nehmen bereits vergebene Rechte nicht zurück.

Für Dateien gilt dasselbe Prinzip auf der Tabelle storage.objects. Dieses Beispiel, angelehnt an die Storage-Dokumentation, erlaubt Uploads nur in einen Ordner, der der eigenen Nutzer-ID entspricht, in einem privaten Bucket:

create policy "uploads_own_folder"
on storage.objects for insert
to authenticated
with check (
  bucket_id = 'documents' and
  (storage.foldername(name))[1] = (select auth.jwt()->>'sub')
);

Für Lesen, Ändern und Löschen brauchen Sie entsprechende eigene Policies. Für Upserts, also das Überschreiben vorhandener Dateien, verlangt Supabase zusätzlich select und update.

So prüfen Sie Ihre App selbst

Prüfen Sie nur Projekte, die Ihnen gehören oder für die Sie eine Erlaubnis haben. Für die eigene App brauchen Sie dafür keine Spezialwerkzeuge.

Schritt 1: Security Advisor auswerten

Im Supabase-Dashboard finden Sie unter „Advisors“ den Security Advisor. Dieselben Prüfungen gibt es über die Kommandozeile mit supabase db advisors. Für RLS sind laut Dokumentation vor allem diese Prüfpunkte wichtig:

PrüfpunktBedeutung
0013_rls_disabled_in_publicTabelle im Schema public ohne RLS, öffentlich lesbar und beschreibbar
0007_policy_exists_rls_disabledPolicies vorhanden, RLS aber ausgeschaltet
0024_permissive_rls_policyPolicy mit immer wahrer Bedingung wie using (true)
0015_rls_references_user_metadataPolicy stützt sich auf Daten, die der Nutzer selbst ändern kann
0010_security_definer_viewView, die RLS umgeht
0028 / 0029Security-Definer-Funktion, die anon bzw. authenticated aufrufen darf
0025_public_bucket_allows_listingÖffentlicher Bucket, dessen Inhalt sich auflisten lässt
0023_sensitive_columns_exposedTabelle mit vermutlich sensiblen Spalten ohne Zugriffsbeschränkung über die API erreichbar

Nicht jeder Treffer ist ein Fehler, manche Daten sollen öffentlich sein. Entscheiden Sie je Punkt bewusst.

Schritt 2: Mit einem zweiten Konto fremde Zeilen abfragen

Der aussagekräftigste Test ist einfach. Legen Sie zwei Testkonten an, A und B, und erzeugen Sie mit A einige Datensätze. Melden Sie sich dann als B an und versuchen Sie, an die Daten von A zu kommen, etwa in der Browser-Konsole Ihrer App mit dem Supabase-Client:

// angemeldet als Konto B
const { data } = await supabase.from('notes').select('*');
// erwartet: nur Zeilen von B

const { data: changed } = await supabase
  .from('notes')
  .update({ title: 'geändert von B' })
  .eq('id', 'ID-EINER-ZEILE-VON-A')
  .select();
// erwartet: leeres Ergebnis, Zeile von A unverändert

Wichtig beim Lesen des Ergebnisses: Wird eine Änderung durch die using-Bedingung blockiert, meldet Postgres keinen Fehler, sondern trifft einfach null Zeilen. Prüfen Sie daher immer das zurückgegebene Ergebnis und anschließend als A, ob die Zeile unverändert ist. Wiederholen Sie den Test ohne Anmeldung, versuchen Sie als B eine Zeile mit der user_id von A anzulegen und öffnen Sie Datei-URLs in einem privaten Browserfenster.

Schritt 3: Tests ins Projekt übernehmen

Supabase empfiehlt, für jede geschützte Tabelle eine Testdatei unter supabase/tests/ anzulegen, die für anon und authenticated erlaubte und verbotene Zugriffe auf alle vier Operationen prüft, und sie mit supabase test db auszuführen. So fällt auf, wenn eine spätere Änderung eine Policy aufweicht.

Checkliste vor dem Livegang

BereichMaßnahme
RLSAuf jeder Tabelle in einem über die API erreichbaren Schema eingeschaltet, auch bei per SQL oder Migration angelegten Tabellen
GrantsFür anon und authenticated nur die Operationen, die die App braucht
PoliciesJe Operation eine eigene Policy, mit to-Rolle und Vergleich auf (select auth.uid())
Schreibenwith check bei insert und update; Rollen, Rechte und Zahlungsstatus nicht vom Nutzer änderbar
Offene Policiesusing (true) nur für wirklich öffentliche Daten
SchlüsselIm Frontend nur publishable bzw. anon key; secret bzw. service_role key nur serverseitig
MetadatenKeine Berechtigungen aus user_metadata
StorageSensible Dateien nur in privaten Buckets, Policies auf storage.objects
Views und FunktionenViews mit security_invoker = true; Security-Definer-Funktionen nicht für anon freigegeben
KontrolleSecurity Advisor ohne ungeklärte Treffer, Test mit zweitem Konto und ohne Anmeldung bestanden

Was ein Advisor-Scan nicht findet

Der Security Advisor weiß nicht, dass in Ihrer App nur Mitglieder desselben Teams eine Rechnung sehen dürfen, dass ein Nutzer seinen Tarif nicht selbst hochsetzen darf oder dass eine Edge Function eine Prüfung überspringt. Solche Fehler in der Zugriffslogik zeigen sich erst, wenn jemand die App gezielt mit mehreren Rollen angreift. Werden personenbezogene Daten dabei tatsächlich abgerufen, kann das eine Meldepflicht nach Art. 33 DSGVO auslösen. Ob das im Einzelfall zutrifft, ist eine Rechtsfrage. Dieser Artikel ist keine Rechtsberatung.

Ist Ihre Supabase-App wirklich dicht?

Wir prüfen Apps auf Supabase, auch mit KI-Werkzeugen gebaute, im schriftlich vereinbarten Umfang: RLS und Grants auf allen Tabellen, Schreibrechte und Spalten mit Berechtigungsfolgen, Storage-Buckets, Views, Funktionen und Edge Functions, jeweils mit mehreren Testkonten und ohne Anmeldung. Jeder Befund kommt mit einem nachvollziehbaren Nachweis und einem konkreten Korrekturvorschlag. Mehr dazu unter SaaS-Penetrationstest.

App-Sicherheitscheck anfragen

CyberSec42 ist eine unabhängige technische Sicherheitsprüfung, keine akkreditierte Zertifizierungsstelle und keine Rechtsberatung. CyberSec42 steht in keiner Verbindung zu Supabase oder Lovable.

Häufige Fragen

Darf der Supabase anon key im Frontend stehen?

Ja. Der anon key und sein Nachfolger, der publishable key, sind laut Supabase dafür gedacht, öffentlich zu sein. Sie erreichen nur, was Row Level Security erlaubt. Die Voraussetzung ist, dass RLS auf allen über die API erreichbaren Tabellen eingeschaltet ist und die Policies stimmen.

Was passiert, wenn der service_role key öffentlich wird?

Der service_role key, heute secret key, umgeht alle RLS-Policies. Wer ihn hat, kann alle Daten lesen und ändern. Ersetzen Sie ihn sofort durch einen neuen Schlüssel, deaktivieren oder löschen Sie den alten und prüfen Sie die Logs auf unerwartete Zugriffe.

Ist RLS bei neuen Supabase-Tabellen automatisch aktiv?

Bei Tabellen, die Sie im Supabase-Dashboard anlegen, ja. Bei Tabellen aus dem SQL-Editor, aus Migrationen oder aus anderen Werkzeugen müssen Sie RLS laut Dokumentation selbst einschalten.

Reicht der Security Advisor als Prüfung?

Er ist ein guter erster Schritt und findet fehlende RLS, offensichtlich offene Policies und riskante Views oder Funktionen. Ob Ihre Policies zur Logik Ihrer App passen, etwa wer welche Zeile ändern darf, prüft er nicht. Dafür braucht es Tests mit mehreren Konten.

Sind mit Lovable gebaute Apps unsicher?

Nicht grundsätzlich. Lovable prüft Apps nach eigener Angabe vor dem Veröffentlichen auf häufige Probleme, darunter fehlende RLS. Die Verantwortung für korrekte Zugriffsregeln liegt laut Lovable aber beim Betreiber der App. Prüfen Sie Ihre Policies deshalb selbst, wie in diesem Artikel beschrieben.

Weiterlesen

Quellen