RATGEBER · SICHERHEIT
Supabase: Row Level Security richtig aktivieren
Ohne Row Level Security kann jede Rolle mit Grant eine Tabelle lesen und schreiben. So aktiviert ihr RLS und legt Policies je Aktion an.
4 Min. LesezeitZuletzt geprüft am
Warum „ohne Policy“ nicht automatisch sicher heißt
Laut Supabase-Doku kann eine Tabelle in einem öffentlich erreichbaren Schema ohne RLS von jeder Rolle gelesen und beschrieben werden, die einen Grant darauf hat. Deshalb gehört RLS auf jede Tabelle in diesen Schemas.
Wer Policies hinzufügt, entfernt damit keine bestehenden Grants. Die Doku empfiehlt, nach dem Aktivieren die Rechte der Rollen anon und authentifiziert (authenticated) zurückzunehmen und nur das zu erlauben, was die Policies vorsehen.
RLS aktivieren und Policies je Aktion anlegen
Aktiviert wird RLS pro Tabelle mit alter table <name> enable row level security. Danach braucht jede Aktion ihre eigene Policy:
select: Bedingung inusinginsert: Bedingung inwith checkupdate:usingfür bestehende Zeilen undwith checkfür das Ergebnisdelete: Bedingung inusing
Rollen und eine Falle: anon mit „true“
Supabase ordnet jede Anfrage einer Postgres-Rolle zu: anon für nicht angemeldete Besucher, authenticated für angemeldete Nutzer. Eine Policy für anon mit using ( true ) erlaubt allen Besuchern, alle Zeilen zu lesen. Das ist nur richtig, wenn die Daten wirklich öffentlich sind.
RLS ist aktiv und trotzdem offen
Eingeschaltete RLS schützt nur, wenn keine Policy allen das Lesen erlaubt. KI-Tools legen solche Policies an, damit Daten ohne Login sichtbar sind. Prüft deshalb mit pg_policies, ob bei anon oder public die Bedingung true steht. Mehr dazu im Ratgeber zum Lovable-Datenleck.
Views umgehen RLS
Ab Postgres 15 lässt sich eine View mit security_invoker = true an die RLS-Regeln der zugrunde liegenden Tabellen binden. Bei älteren Versionen nimmt ihr anon und authenticated den Zugriff auf die View oder legt sie in ein Schema, das nicht öffentlich erreichbar ist.
Der Secret Key umgeht RLS
Ein Secret Key arbeitet mit der Rolle service_role, und die hat das Attribut bypassrls. Anfragen mit diesem Key sehen also alle Zeilen. Er gehört nie ins Frontend, siehe den Ratgeber zum öffentlichen Supabase-Key. Laut Doku umgeht er RLS nur, wenn die Anfrage kein Zugriffstoken eines Nutzers trägt. Trägt sie eines, gelten die Policies des angemeldeten Nutzers, auch wenn der Client mit einem Secret Key gestartet wurde.
So prüft ihr es
Öffnet die App ohne Anmeldung und ruft mit dem Publishable Key eine Tabelle ab, die nur angemeldete Nutzer sehen sollen. Kommen Zeilen zurück, fehlt eine Regel. Der Check von LaunchCheck prüft passiv von außen und erkennt offene Tabellen nicht; dieser Selbsttest ersetzt das.
Für automatisierte Tests verweist die Supabase-Doku auf pgTAP. Die Tests laufen mit supabase test db; für Policies gibt es Prüffunktionen wie policies_are, policy_roles_are und policy_cmd_is.
Typische Fehler
Diese Fehler nennt die Supabase-Doku im Zusammenhang mit Policies:
- Sich nur auf RLS verlassen und die Grants nicht prüfen. Mit aktivem RLS und ohne passende Policy ist eine Aktion gesperrt. Überflüssige Rechte von
anontrotzdem zurückzunehmen ist eine zweite Sicherung, falls RLS später einmal abgeschaltet wird. - Die Klausel
to <rolle>weglassen. Die Doku empfiehlt, die Rolle einer Policy immer zu nennen. - Ohne angemeldeten Nutzer liefert
auth.uid()null. Prüft das ausdrücklich, zum Beispiel mitauth.uid() is not null and auth.uid() = user_id, statt darauf zu vertrauen, dass ein Vergleich mitnullstill scheitert. - Für ein
updatefehlt dieselect-Policy. Die Doku verlangt dafür zusätzlich eine passendeselect-Policy. - Zwei Policies, die sich gegenseitig lesen. Postgres meldet dann den Fehler
42P17(unendliche Rekursion in der Policy). Die Doku löst das mit security-definer-Funktionen, bei denensearch_path = ''gesetzt ist und die Namen mit Schema geschrieben werden. - Rechte an
raw_user_meta_dataknüpfen. Diese Daten kann der angemeldete Nutzer selbst ändern. Für Berechtigungen taugtraw_app_meta_data, das der Nutzer nicht ändern kann. - Views vergessen. Sie umgehen RLS, siehe oben.
Beispiel: Profile gehören dem Besitzer
Aus der Supabase-Doku: Nutzer lesen, anlegen und ändern nur ihr eigenes Profil. Die Spalte user_id verweist auf den Besitzer.
Die Doku rät außerdem, jede Spalte zu indexieren, nach der eine Policy filtert (hier user_id). Sonst wird jede Abfrage teurer, je mehr Zeilen die Tabelle hat.
create policy "Users can view their own profile."
on profiles for select
to authenticated
using ( (select auth.uid()) = user_id );
create policy "Users can create their own profile."
on profiles for insert
to authenticated
with check ( (select auth.uid()) = user_id );
create policy "Users can update their own profile."
on profiles for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );Häufige Fragen
Reicht es, RLS zu aktivieren?
Für die Sicherheit zunächst ja: Ohne Policy ist die Tabelle für anon und authenticated gesperrt. Damit eure App wieder lesen und schreiben kann, legt ihr je Aktion eine Policy an.
Warum liefert mein Update nichts?
Für ein update braucht es zusätzlich eine passende select-Policy.
Umgehen Views RLS?
Ja, standardmäßig, weil Views meist mit dem Nutzer postgres angelegt werden. Ab Postgres 15 lässt sich das mit security_invoker = true ändern.
Wie teste ich Policies?
Von Hand wie oben beschrieben, automatisiert mit pgTAP über supabase test db.
Quellen
Eure App prüfen
Der kostenlose Check lädt eure App wie ein Besucher und zeigt, was von außen sichtbar ist. Ihr bestätigt vorher, dass ihr die App verantwortet oder zur Prüfung befugt seid.