Meistern Sie Row-Level Security in Supabase. Erfahren Sie, wie Sie häufige RLS-Fehler vermeiden, Leistungseinbrüche verhindern, Rekursionen lösen und Zugriffskontrollen testen.
Kurze Antwort
Row-Level Security (RLS) ist eine fundamentale Sicherheitsfunktion der PostgreSQL-Datenbank, die den Datenzugriff auf Zeilenebene basierend auf Benutzerrollen und dem jeweiligen Ausführungskontext einschränkt.[1] Bei korrekter Implementierung innerhalb von Supabase fungiert RLS als primäre Autorisierungsschicht. Sie stellt sicher, dass authentifizierte und anonyme Benutzer nur Datensätze lesen, einfügen, aktualisieren oder löschen können, die durch administrative Richtlinien ausdrücklich erlaubt sind.
Trotz dieses robusten Designs entstehen häufig kritische Schwachstellen durch Implementierungsfehler. Zu den häufigsten RLS-Fehlern, die vermieden werden müssen, gehören unsachgemäße Rollenzuweisungen, das Übersehen von Standardeinstellungen der Datenbank (wie das Belassen von RLS im deaktivierten Zustand bei neu erstellten Tabellen), die Verlagerung von Sicherheitslogik in die Anwendungsschicht anstelle der Datenbank und unzureichendes Testen der gesamten Matrix von Datenbankoperationen. Darüber hinaus können scheinbar korrekte Richtlinien katastrophale Leistungsengpässe oder endlose Rekursionsschleifen verursachen, wenn das zugrunde liegende Verhalten des PostgreSQL-Abfrageoptimierers nicht vollständig verstanden wird.
Um eine strenge Datenisolation zu gewährleisten, müssen Entwicklungsteams das RLS-Setup rigoros testen. Entwickler sollten automatisierte Testkonten mit unterschiedlichen Rollen verwenden, um die RLS-Konfigurationen systematisch zu validieren. Test-Frameworks wie pgTAP bieten die notwendigen Werkzeuge, um diese Validierungen direkt innerhalb der Datenbank-Engine durchzuführen, bevor eine Anwendung in die Produktionsumgebung überführt wird.[2]
Warum dieses Thema wichtig ist
Das Paradigma der modernen Anwendungsentwicklung hat sich in den letzten Jahren massiv in Richtung Backend-as-a-Service-Plattformen (BaaS) verschoben. Supabase, das auf PostgreSQL und PostgREST basiert, ermöglicht es Entwicklern, die Datenbank direkt aus Frontend-Client-Anwendungen heraus abzufragen. Während diese Architektur die Entwicklung beschleunigt und den Bedarf an benutzerdefinierter Middleware drastisch reduziert, verändert sie gleichzeitig die Sicherheitsgrenze grundlegend. Die Datenbank ist nicht länger hinter einem abstrahierten Backend-Server verborgen, sondern ihre Anwendungsprogrammierschnittstelle (API) ist direkt über das öffentliche Internet erreichbar.
In dieser Architektur beruht die Sicherheit vollständig auf Row-Level Security-Richtlinien. Wenn RLS falsch konfiguriert oder deaktiviert ist, ist die Anwendung inhärent kompromittiert. Die Schwere dieses Risikos wurde im Mai 2025 mit der Offenlegung der Sicherheitslücke CVE-2025-48757 deutlich unterstrichen.[3] Sicherheitsforscher entdeckten, dass 10,3 % der analysierten Anwendungen, die mit dem KI-gestützten Entwicklungstool Lovable erstellt wurden (was 303 Endpunkten in 170 Projekten entspricht), mit Supabase-Tabellen ausgeliefert wurden, die für jeden, der den öffentlichen anonymen Schlüssel (anon) besaß, vollständig lesbar und beschreibbar waren.
Die Grundursache für CVE-2025-48757 war kein systemischer Fehler in PostgreSQL oder Supabase, sondern ein architektonisches Versäumnis: Tabellen, die über SQL-Skripte oder den Supabase Table Editor erstellt werden, haben standardmäßig RLS deaktiviert. Da der anon-Schlüssel absichtlich in das Frontend-JavaScript-Bundle integriert wird, um anfängliche Verbindungen zu ermöglichen, wird jede Tabelle ohne ausdrücklichen Befehl zur RLS-Aktivierung vollständig für nicht authentifizierte Remote-Angreifer zugänglich. Angreifer können den anon-Schlüssel einfach aus dem Client-Bundle extrahieren und HTTP-REST-Anfragen ausführen, um sensible Daten zu exfiltrieren, Datensätze zu manipulieren oder Privilegien zu eskalieren.
Diese architektonische Verschiebung hat eine weitreichende Debatte innerhalb der Software-Engineering-Community ausgelöst. Führungskräfte von konkurrierenden Datenbankplattformen und Backend-Diensten, wie PlanetScale und Convex, haben argumentiert, dass die Verlagerung der Sicherheitslogik in die Datenbank selbst anfällig für stille Ausfälle und schwer zu debuggende Fehlkonfigurationen ist. Sie weisen darauf hin, dass traditionelle Backend-Schichten explizitere Authentifizierungsprüfungen erzwingen. Nichtsdestotrotz bleibt RLS ein äußerst mächtiges Werkzeug für Datenisolation, das eine nahtlose Integration und hohe Leistung ermöglicht – vorausgesetzt, die Umsetzung erfolgt mit technischer Disziplin.
Die Auswirkungen einer fehlerhaften Zugriffskontrolle gehen weit über theoretische Datenlecks hinaus. Fehlerhafte Zugriffskontrollen sind laut OWASP-Richtlinien die am weitesten verbreitete Kategorie von Web-Schwachstellen. Regulatorische Rahmenbedingungen wie die Datenschutz-Grundverordnung (DSGVO), HIPAA und SOC 2 erfordern die strikte Durchsetzung von Datenisolation. Ein Versagen in der RLS-Architektur verstößt direkt gegen diese Compliance-Standards und führt zu schweren finanziellen Strafen, dem Verlust des Kundenvertrauens und strukturellen Geschäftsschäden. Daher ist die Beherrschung von RLS nicht nur eine technische Optimierung, sondern ein kritischer organisatorischer Imperativ für jedes Unternehmen, das Supabase in Produktionsumgebungen einsetzt.
Haupterklärung
Um eine Supabase-Anwendung effektiv zu sichern, ist ein tiefes Verständnis der Art und Weise erforderlich, wie die Plattform moderne Webauthentifizierung mit den grundlegenden Sicherheitsmechanismen von PostgreSQL verbindet. Das System stützt sich auf eine Kombination aus Berechtigungen, Richtlinien und einer Middleware-Übersetzungsschicht, die HTTP-Kontexte in SQL-Variablen umwandelt.
PostgreSQL-Primitive: Berechtigungen (Grants) versus Richtlinien (Policies)
Die Zugriffskontrolle in PostgreSQL arbeitet auf zwei unterschiedlichen Ebenen, die oft miteinander verwechselt werden: Berechtigungen auf Tabellenebene (Grants) und Sicherheit auf Zeilenebene (Policies).[4]
| Mechanismus | Beschreibung | Geltungsbereich | Umgehungsmöglichkeit |
|---|---|---|---|
| Berechtigungen (Grants) | Bestimmen, welche Rollen die Erlaubnis haben, bestimmte Operationen (z. B. SELECT, INSERT) auf einer Datenbankstruktur auszuführen. | Tabellenebene | Gilt für alle Instanzen, es sei denn, sie werden ausdrücklich widerrufen. |
| Richtlinien (Policies) | Legen explizit fest, welche spezifischen Zeilen innerhalb einer Tabelle von einer berechtigten Rolle aufgerufen oder geändert werden dürfen. | Zeilenebene | Kann nur von Superusern oder Rollen mit dem BYPASSRLS-Attribut umgangen werden. |
In einer Standard-Supabase-Bereitstellung werden globale Berechtigungen auf primäre Rollen angewendet, die den Zugriff auf das public-Schema ermöglichen. Wenn RLS jedoch nicht aktiviert ist, bedeutet eine globale SELECT-Berechtigung, dass der Benutzer jede einzelne Zeile in der Tabelle lesen kann. Die Aktivierung von RLS versetzt die Tabelle in einen Zustand der standardmäßigen Verweigerung (Default-Deny) für den Zeilenzugriff. Dies erfordert explizite Richtlinien, um jegliche Dateninteraktion zu ermöglichen.
Die PostgREST-Übersetzungsschicht und Rollenzuordnung
Supabase nutzt PostgREST, um HTTP-REST-Anfragen automatisch in SQL-Abfragen zu übersetzen. Während dieser Übersetzung inspiziert PostgREST den Autorisierungs-Header der eingehenden Anfrage. Supabase ordnet jede eingehende HTTP-Anfrage einer von drei primären PostgreSQL-Rollen zu:
| Rolle | Authentifizierungsstatus | Verwendungszweck und Sicherheitsimplikationen |
|---|---|---|
| anon | Nicht authentifiziert | Repräsentiert Anfragen, bei denen der Benutzer nicht angemeldet ist, aber den öffentlichen Projektschlüssel besitzt. Ermöglicht den Zugriff auf öffentliche Daten, wenn RLS dies erlaubt. |
| authenticated | Authentifiziert | Repräsentiert Anfragen von Benutzern, die sich erfolgreich über Supabase Auth angemeldet haben und ein gültiges JSON Web Token (JWT) besitzen. |
| service_role | Privilegiertes Backend | Eine hochgradig privilegierte Serverrolle, die alle RLS-Richtlinien automatisch umgeht. Sie ist ausschließlich für administrative Server-seitige Aufgaben vorgesehen und darf niemals dem Frontend ausgesetzt werden. |
Wenn ein authentifizierter Benutzer eine Anfrage stellt, PostgREST authentifiziert das JWT und setzt eine lokale Transaktionsvariable (eine Grand Unified Configuration oder GUC), die die JWT-Ansprüche (Claims) enthält. Hilfsfunktionen wie auth.uid() und auth.jwt() extrahieren lediglich die eindeutige Kennung des Benutzers (sub-Claim) und zugehörige Metadaten aus dieser GUC. Dies ermöglicht es der Datenbank, Richtlinien dynamisch im Kontext des anfragenden Benutzers zu bewerten.
Die Anatomie einer Richtlinie: USING versus WITH CHECK
Ein kritischer Bestandteil bei der Definition von RLS-Richtlinien ist die Unterscheidung zwischen den Ausdrücken, die zur Bewertung des Datenzugriffes verwendet werden. Richtlinien nutzen zwei primäre Klauseln:
- USING-Klausel: Definiert die Bedingung, die vorhandene Zeilen erfüllen müssen, um für die Abfrage sichtbar zu sein oder von ihr geändert zu werden. Sie beantwortet die Frage: Darf der Benutzer diesen spezifischen, bereits existierenden Datensatz sehen oder mit ihm interagieren?.
- WITH CHECK-Klausel: Definiert die Bedingung, die neue oder aktualisierte Zeilen erfüllen müssen, um in die Datenbank geschrieben zu werden. Sie beantwortet die Frage: Ist der resultierende Zustand der Zeile nach der Operation zulässig?.
Die Anwendung dieser Klauseln hängt vollständig von der ausgeführten SQL-Operation ab, was bei der Implementierung häufig zu Verwirrung führt:
| SQL-Operation | Erfordert USING | Erfordert WITH CHECK | Erklärung der Datenbanklogik |
|---|---|---|---|
| SELECT | Ja | Nein | Es werden keine neuen Daten geschrieben; es werden ausschließlich vorhandene Zeilen bewertet. |
| INSERT | Nein | Ja | Es werden keine vorhandenen Zeilen abgefragt; die Bewertung erfolgt für die neu eingehende Zeile. |
| UPDATE | Ja | Ja | Bewertet die vorhandene Zeile (USING), um zu sehen, ob sie als Ziel ausgewählt werden kann, sowie den neuen Zeilenzustand (WITH CHECK), um zu prüfen, ob die Änderung zulässig ist. |
| DELETE | Ja | Nein | Bewertet ausschließlich vorhandene Zeilen, um zu verifizieren, ob sie zum Entfernen berechtigt sind. |
Das Verständnis dieser Matrix ist von größter Bedeutung. Eine UPDATE-Operation ist einzigartig, da sie einen bestehenden Zustand ändert, um einen neuen Zustand zu schaffen. Wenn eine UPDATE-Richtlinie nur eine USING-Klausel definiert, wendet PostgreSQL die USING-Logik automatisch auf die WITH CHECK-Phase an. Explizite Definitionen werden jedoch dringend empfohlen, um Logiklücken zu vermeiden, die zu unerwünschten Privilegien-Eskalationen führen könnten.
Praktische Beispiele
Die Implementierung von RLS erfordert weit mehr als nur grundlegende Eigentumsprüfungen. Eine produktionsreife Datenbank muss automatisierte Sicherheitsdurchsetzungen, komplexe relationale Datenmodelle, rollenbasierte Zugriffskontrollen und systematische Testverfahren berücksichtigen.
Beispiel 1: Automatisches Aktivieren von RLS für neue Tabellen
Um Schwachstellen ähnlich der CVE-2025-48757 zu mindern, können Organisationen Datenbank-Trigger implementieren, die die RLS-Aktivierung automatisch erzwingen, sobald eine neue Tabelle erstellt wird. Das folgende SQL-Skript richtet eine SECURITY DEFINER-Funktion ein, die auf Data Definition Language (DDL)-Befehle hört und RLS auf jede neu erstellte Tabelle im öffentlichen Schema anwendet:
CREATE OR REPLACE FUNCTION rls_auto_enable()
RETURNS EVENT_TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = pg_catalog
AS $$
DECLARE
cmd record;
BEGIN
FOR cmd IN
SELECT * FROM pg_event_trigger_ddl_commands()
WHERE command_tag IN ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
AND object_type IN ('table','partitioned table')
LOOP
IF cmd.schema_name IN ('public') THEN
EXECUTE format('ALTER TABLE IF EXISTS %s ENABLE ROW LEVEL SECURITY', cmd.object_identity);
RAISE LOG 'rls_auto_enable: RLS auf % aktiviert', cmd.object_identity;
END IF;
END LOOP;
END;
$$;
DROP EVENT TRIGGER IF EXISTS ensure_rls;
CREATE EVENT TRIGGER ensure_rls
ON ddl_command_end
WHEN TAG IN ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO')
EXECUTE FUNCTION rls_auto_enable();Diese Implementierung stellt sicher, dass alle zukünftigen Tabellen standardmäßig in einem sicheren, abgeriegelten Zustand verbleiben, bis explizite RLS-Richtlinien formuliert werden.
Beispiel 2: Richtlinien für benutzerbezogene Daten (CRUD)
Für eine Standardanwendung, bei der Benutzer ihre eigenen Dokumente oder Profile verwalten, müssen die Richtlinien für alle CRUD-Operationen explizit definiert werden. Darüber hinaus sollte die Funktion auth.uid() aus Leistungsgründen in eine skalare Unterabfrage (Subquery) eingebettet werden (dieses Konzept wird in Abschnitt 5 ausführlich erläutert).
-- RLS aktivieren
ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY;
-- 1. Lesezugriff (SELECT)
CREATE POLICY "Benutzer sehen eigene Dokumente" ON public.documents
FOR SELECT TO authenticated
USING (user_id = (select auth.uid()));
-- 2. Erstellungszugriff (INSERT)
CREATE POLICY "Benutzer erstellen eigene Dokumente" ON public.documents
FOR INSERT TO authenticated
WITH CHECK (user_id = (select auth.uid()));
-- 3. Änderungszugriff (UPDATE)
CREATE POLICY "Benutzer aktualisieren eigene Dokumente" ON public.documents
FOR UPDATE TO authenticated
USING (user_id = (select auth.uid()))
WITH CHECK (user_id = (select auth.uid()));
-- 4. Löschzugriff (DELETE)
CREATE POLICY "Benutzer löschen eigene Dokumente" ON public.documents
FOR DELETE TO authenticated
USING (user_id = (select auth.uid()));Die Bereitstellung expliziter Richtlinien für INSERT, UPDATE und DELETE garantiert, dass böswillige Akteure Daten weder modifizieren noch zerstören können, selbst wenn sie erfolgreich eine SELECT-Einschränkung umgehen.
Beispiel 3: Rollenbasierte Zugriffskontrolle (RBAC) über App-Metadaten
Komplexe Anwendungen erfordern administrative Rollen, Abonnementstufen oder spezifische Privilegien. Während Entwickler oft dazu neigen, diese Informationen in einer separaten roles-Tabelle zu speichern und SQL-Joins auszuführen, bietet Supabase eine hochperformante Alternative über die Tabelle auth.users.
Das Supabase-Authentifizierungssystem enthält zwei spezifische JSONB-Spalten: raw_user_meta_data und raw_app_meta_data.
- Die Spalte
raw_user_meta_dataist durch den authentifizierten Benutzer vom Client aus beschreibbar (z. B. übersupabase.auth.update()). Das Speichern von Autorisierungsdaten in dieser Spalte ermöglicht sofortige Schwachstellen zur Privilegienerweiterung, da Benutzer sich selbst administrative Rechte zuweisen könnten. - Die Spalte
raw_app_meta_dataist für den Client streng schreibgeschützt und kann nur sicher über das Backend (unter Verwendung des service_role-Schlüssels) aktualisiert werden.
Durch das Injizieren benutzerdefinierter Ansprüche (Custom Claims, wie Abonnementstufen oder administrative Flags) in raw_app_meta_data werden die Daten automatisch in das JWT des Benutzers serialisiert. Dies ermöglicht es der Datenbank, Privilegien sofort zu bewerten, ohne sekundäre Tabellenverknüpfungen ausführen zu müssen, was die Skalierbarkeit erheblich verbessert.
-- Funktion zur Auswertung eines im JWT eingebetteten benutzerdefinierten Claims
CREATE OR REPLACE FUNCTION is_admin()
RETURNS boolean
LANGUAGE sql STABLE
AS $$
SELECT COALESCE((auth.jwt() -> 'app_metadata' ->> 'role'), 'user') = 'admin';
$$;
-- Anwendung der Funktion in einer RLS-Richtlinie
CREATE POLICY "Administratoren können alle Unternehmenseinträge einsehen"
ON public.corporate_records
FOR SELECT TO authenticated
USING ((select is_admin()));Dieses Muster eliminiert die Notwendigkeit rekursiver Datenbankaufrufe und verlagert die Autorisierungslogik direkt in das kryptografische Token.
Beispiel 4: RLS-Tests mit pgTAP
Logiktests auf Anwendungsebene reichen nicht aus, um die Datenbanksicherheit zu validieren. Da PostgREST direkt mit PostgreSQL kommuniziert, muss das Testen auf der Datenbankebene erfolgen. Das robusteste Framework zur Validierung von PostgreSQL-Strukturen und -Richtlinien ist pgTAP (Test Anything Protocol).
pgTAP ermöglicht es Ingenieuren, Unit-Tests für Tabellen, Ansichten, Funktionen und RLS-Richtlinien unter Verwendung nativer SQL-Syntax zu schreiben. Die Tests simulieren reale API-Anfragen, indem sie den Ausführungskontext und die JWT-Ansprüche dynamisch ändern.
| pgTAP-Testfunktion | Einsatzzweck in Supabase |
|---|---|
| has_table('name') | Bestätigt, dass kritische Datenbanktabellen vorhanden sind. |
| rls_enabled('public') | Prüft systematisch, ob RLS für das gesamte schema aktiviert ist. |
| results_eq() | Validiert, ob eine Abfrage exakt die erwarteten Daten oder die erwartete Zeilenanzahl zurückgibt (z. B. nur die eigenen Daten des Benutzers). |
| is_empty() | Überprüft negative Autorisierungstests, um sicherzustellen, dass nicht berechtigte Abfragen leere Ergebnisse liefern. |
Ein Standard-pgTAP-Test kapselt seine Operationen innerhalb einer Datenbanktransaktion (BEGIN; ... ROLLBACK;), um sicherzustellen, dass Testdaten den permanenten Datenbankstatus nicht verändern.
BEGIN;
CREATE EXTENSION IF NOT EXISTS pgtap WITH SCHEMA extensions;
SELECT plan(3); -- Anzahl der erwarteten Zusicherungen (Assertions)
-- 1. Setup-Phase: Erstellen von Mock-UUIDs
\set user_a '11111111-1111-1111-1111-111111111111'
\set user_b '22222222-2222-2222-2222-222222222222'
-- Einfügen von Testdaten unter Umgehung von RLS (als Superuser)
INSERT INTO public.documents (id, title, user_id) VALUES
(1, 'Geheimes Dokument A', :'user_a'),
(2, 'Geheimes Dokument B', :'user_b');
-- 2. Kontextwechsel: Simulation der Anmeldung von Benutzer A
SET LOCAL role authenticated;
SELECT set_config('request.jwt.claim.sub', :'user_a', true);
-- 3. Assertions (Zusicherungen)
-- Test 1: Überprüfen, ob Benutzer A nur sein eigenes Dokument lesen kann
SELECT results_eq(
'SELECT count(*)::int FROM public.documents',
ARRAY[1],
'Benutzer A sollte nur 1 Dokument sehen'
);
-- Test 2: Überprüfen, ob Benutzer A sein Dokument aktualisieren kann
SELECT lives_ok(
$$ UPDATE public.documents SET title = 'Aktualisiert A' WHERE id = 1 $$,
'Benutzer A kann sein eigenes Dokument aktualisieren'
);
-- Test 3: Überprüfen, ob Benutzer A das Dokument von Benutzer B nicht löschen kann
SELECT results_ne(
$$ DELETE FROM public.documents WHERE id = 2 RETURNING 1 $$,
$$ VALUES(1) $$,
'Benutzer A darf Dokumente von Benutzer B nicht löschen'
);
-- 4. Teardown-Phase
SELECT * FROM finish();
ROLLBACK;Diese Testskripte können über die Supabase CLI automatisiert in CI/CD-Pipelines (Continuous Integration) ausgeführt werden (supabase test db), wodurch sichergestellt wird, dass keine Migration unbeabsichtigt Sicherheitsregeln deaktiviert.
Häufige Fehler und wie man sie vermeidet
Selbst erfahrene Datenbankarchitekten stoßen auf Fallstricke, wenn sie Autorisierungskonzepte der Anwendungsschicht in Richtlinien der Datenbankebene übersetzen. Im Folgenden werden die am weitesten verbreiteten Fehler in Supabase-Bereitstellungen aufgeführt.
Fehler 1: RLS deaktiviert lassen (Die stille Offenlegung)
Wie durch die Schwachstelle CVE-2025-48757 hervorgehoben wurde, ist das Vergessen der Ausführung von ALTER TABLE tablename ENABLE ROW LEVEL SECURITY; die häufigste und verheerendste Sicherheitslücke. Wenn RLS im public-Schema deaktiviert ist, PostgREST gibt die gesamte Tabelle für die Öffentlichkeit frei. Jeder, der den öffentlichen anon-Schlüssel besitzt – der leicht aus dem Netzwerk-Payload des Frontends extrahiert werden kann – kann vollständige Tabellenscans, Datendumps und unbefugte Datenmodifikationen ausführen.
Um dies zu beheben, müssen Teams das Supabase-Dashboard so konfigurieren, dass RLS bei neuen Tabellen automatisch aktiviert wird, oder sie müssen explizite RLS-Befehle in alle Migrationsskripte integrieren. Selbst Konfigurationstabellen, die als öffentlich zugänglich gedacht sind, müssen RLS mit einer expliziten Lese-Richtlinie (z. B. USING (true) nur für SELECT) versehen haben, um unbefugte Schreibzugriffe zu verhindern.
Fehler 2: Die Leistungs-Falle der auth.uid()-Funktion
Ein weit verbreitetes Leistungsproblem tritt auf, wenn Entwickler Supabase-Hilfsfunktionen wie auth.uid() direkt innerhalb von Gleichheitsprüfungen in RLS-Richtlinien verwenden.
Das Schreiben von USING (auth.uid() = user_id) erscheint zunächst logisch. Aufgrund der Art und Weise, wie PostgreSQL JSON-basierte Konfigurationen parst und in UUIDs umwandelt, erkennt der Abfrageoptimierer (Query Optimizer) die Funktion jedoch nicht als unveränderliche Konstante für die Dauer der Abfrage. Als Folge davon löst PostgreSQL einen sequenziellen Scan aus und wertet die Funktion auth.uid() für jede einzelne Zeile in der Tabelle neu aus. Bei einer Tabelle mit einer Million Zeilen erzwingt dies eine Million JSON-Parsing-Operationen. Eine Abfrage, die Millisekunden dauern sollte, benötigt plötzlich Minuten, was faktisch zu Denial-of-Service-Bedingungen (DoS) führt.
Die Lösung besteht darin, die Funktion in eine skalare Unterabfrage zu hüllen. Das Schreiben von USING ((select auth.uid()) = user_id) zwingt den Abfrageplaner, einen sogenannten initPlan auszuführen. Die Datenbank bewertet die UUID des Benutzers genau einmal, speichert das Ergebnis zwischen und nutzt Standardindizierung für den Rest der Auswertung. Benchmarks zeigen, dass diese Optimierung die Ausführungsgeschwindigkeit von Abfragen bei großen Datensätzen um das mehr als 100-fache verbessern kann (z. B. von 179 ms auf 9 ms). Diese Wrapping-Technik gilt gleichermaßen für auth.jwt(), auth.role() und benutzerdefinierte SECURITY DEFINER-Funktionen.
Fehler 3: Endlose Rekursion in relationalen Richtlinien
Multi-Tenant-Architekturen erfordern häufig Richtlinien, die die Mitgliedschaft eines Benutzers in einer Organisation überprüfen. Ein Standardansatz besteht darin, eine Richtlinie in der Tabelle organizations zu erstellen, die eine Tabelle organization_members abfragt. Wenn die Tabelle organization_members jedoch ebenfalls eine Richtlinie enthält, die zur Bestimmung der Sichtbarkeit die Tabelle organizations abfragt, erkennt PostgreSQL eine zirkuläre Abhängigkeit. Dies bricht die Abfrage sofort mit dem Fehler infinite recursion detected in policy for relation ab.
Um den Rekursionszyklus zu durchbrechen, müssen Architekten die RLS-Richtlinien der sekundären Tabelle während der Autorisierungsprüfung umgehen. Dies wird erreicht, indem die Mitgliedschaftsbewertung in eine PostgreSQL-Funktion des Typs SECURITY DEFINER verlagert wird. Da SECURITY DEFINER-Funktionen mit den Rechten des Benutzers ausgeführt werden, der die Funktion erstellt hat (in der Regel der Superuser), umgehen sie die RLS-Prüfungen der zugrunde liegenden Tabellen inhärent.[5]
Fehler 4: Sicherheitslücken im search_path von SECURITY DEFINER-Funktionen
Obwohl SECURITY DEFINER-Funktionen Rekursionen beheben und komplexe Join-Overheads reduzieren, bergen sie ein hohes Risiko der Privilegienerweiterung, wenn sie unsauber programmiert werden. Standardmäßig führen PostgreSQL-Funktionen Abfragen mit einem dynamischen search_path aus. Ein böswilliger Akteur könnte theoretisch eine Funktion mit identischem Namen in einem anderen Schema erstellen, den search_path ändern und die SECURITY DEFINER-Funktion dazu verleiten, willkürlichen bösartigen Code mit Superuser-Rechten auszuführen.
Jede SECURITY DEFINER-Funktion muss daher explizit einen gesperrten search_path festlegen. Das Anhängen von SET search_path = '' (ein leerer Pfad) zwingt die Funktion dazu, Objekte strikt unter Verwendung ihrer vollständigen Schema-Namen aufzulösen, wodurch Schema-Hijacking-Angriffsvektoren neutralisiert werden.[5]
Fehler 5: Die Fehlkonstruktion der WITH CHECK-Klausel bei Updates
Wie im Architekturabschnitt erörtert, ist die UPDATE-Operation eine hybride Aktion. Ein häufiger und schwerwiegender Logikfehler tritt auf, wenn Entwickler versuchen, eine UPDATE-Richtlinie explizit zu definieren, die Integritätsprüfung jedoch durch das Schreiben von WITH CHECK (true) annullieren.
Das Erstellen einer Richtlinie wie CREATE POLICY "Beiträge aktualisieren" ON posts FOR UPDATE USING (user_id = auth.uid()) WITH CHECK (true); ermöglicht es einem Benutzer, seinen eigenen Beitrag als Ziel auszuwählen (was die USING-Klausel erfüllt). Der Benutzer könnte jedoch den user_id-Payload modifizieren, um das Eigentum des Beitrags auf einen anderen Benutzer zu übertragen, da die Bedingung true in der WITH CHECK-Klausel jede Modifikation blind akzeptiert. Bei UPDATE-Richtlinien muss der WITH CHECK-Ausdruck streng die Grenze des USING-Ausdrucks erzwingen (z. B. WITH CHECK (user_id = (select auth.uid()))), es sei denn, eine Eigentumsübertragung ist ein beabsichtigtes Softwaremerkmal.
Fehler 6: Exposition des service_role-Schlüssels in Client-Umgebungen
Der service_role-Schlüssel ist ein hochgradig privilegiertes kryptografisches Token, das alle RLS-Richtlinien über die gesamte Supabase-Instanz hinweg systembedingt umgeht. Sein einziger Zweck besteht darin, serverseitige administrative Aufgaben zu erleichtern. Ein fataler Fehler entsteht, wenn Entwickler diesen Schlüssel in den Frontend-Anwendungscode integrieren (z. B. durch das Präfix NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY in Next.js-Frameworks).
Der service_role-Schlüssel must strikt im Backend verbleiben und darf ausschließlich in Edge-Funktionen, sicheren API-Routen oder Continuous-Integration-Servern existieren. Wenn der Schlüssel versehentlich in ein Repository übertragen oder in einem Client-Bundle offengelegt wird, muss er sofort über das Supabase-Dashboard rotiert werden, gefolgt von einer strengen Prüfung der Datenbankprotokolle auf unbefugten Zugriff.
Fehler 7: Fehlende Indizes bei Richtlinienspalten
RLS wendet Filterlogik in massiver Skalierung auf Abfragen an. Wenn eine RLS-Richtlinie den Zugriff basierend auf einer bestimmten Spalte filtert (z. B. user_id oder tenant_id) und diese Spalte nicht indiziert ist, ist die Datenbank gezwungen, bei jeder Abfrage einen sequenziellen Scan durchzuführen. Jede Spalte, die innerhalb einer USING- oder WITH CHECK-Klausel verwendet wird, muss zwingend durch einen B-Tree-Index unterstützt werden. Dies stellt sicher, dass der Abfrageplaner der Datenbank zulässige Zeilen in logarithmischer Zeit lokalisieren kann.
Häufig gestellte Fragen (FAQ)
Was ist Row-Level Security (RLS)? RLS ist eine native Sicherheitsfunktion in PostgreSQL-Datenbanken, die den Datenzugriff auf Zeilenebene basierend auf Benutzerrollen einschränkt. In der Architektur von Supabase fungiert RLS als Torwächter, der Abfragen und Aktualisierungen dynamisch filtert, sodass ein ausführender Benutzer nur mit der spezifischen Teilmenge von Datensätzen interagiert, für die er nachgewiesen autorisiert ist.[1][4]
Was sind häufige RLS-Fehler, die vermieden werden sollten? Zu den gravierendsten Fehlern gehören unsachgemäße Rollenzuweisungen, das Übersehen von Standardeinstellungen (wie das Versäumnis, RLS für neu erstellte Tabellen zu aktivieren), sowie unzureichendes Testen. Darüber hinaus führen architektonische Fehler – wie das Schreiben von Richtlinien, die endlose Rekursionen auslösen, oder das Auslassen restriktiver WITH CHECK-Klauseln bei Aktualisierungsvorgängen – häufig zu schweren Datenschwachstellen.
Wie teste ich mein RLS-Setup? Verwenden Sie Testkonten mit unterschiedlichen Rollen, um Ihre RLS-Konfigurationen systematisch zu validieren. Durch die Nutzung der pgTAP-Erweiterung in Kombination mit der Supabase-CLI können Entwickler automatisierte Datenbank-Unit-Tests erstellen.[2] Diese Skripte verändern den Ausführungskontext dynamisch (wechseln zwischen anonymen, authentifizierten und Service-Rollen), um mathematisch zu verifizieren, dass die Grenzen der Datenisolation in allen Szenarien intakt bleiben.
Ist es sicher, den anonymen Schlüssel (anon) im Frontend offenzulegen? Ja, der anon-Schlüssel ist explizit dafür konzipiert, in clientseitigen JavaScript-Bundles sicher offengelegt zu werden. Dies gilt jedoch nur unter der absoluten Voraussetzung, dass Row-Level Security auf allen öffentlichen Tabellen strikt aktiviert und korrekt konfiguriert ist. Der Schlüssel identifiziert lediglich das Projekt gegenüber der Datenbank; es sind die RLS-Richtlinien, die den tatsächlichen Datenzugriff kontrollieren.
Wann Sie professionelle Hilfe in Anspruch nehmen sollten
Obwohl Supabase einen Großteil der Infrastrukturschicht vereinfacht, erreichen komplexe Zugriffsarchitekturen oft einen Schwellenwert, an dem professionelle Intervention unerlässlich wird. Ingenieurteams sollten unter folgenden Bedingungen qualifizierte Sicherheitsexperten oder spezialisierte Datenbankarchitekten hinzuziehen:
- Komplexe Multi-Tenant-Implementierungen: Wenn eine Anwendung tiefe organisatorische Hierarchien aufweist (z. B. Unternehmenssysteme mit Gruppen, Untergruppen und granularer Berechtigungsvererbung), wird das Schreiben leistungsstarker RLS-Richtlinien hochgradig komplex. Fachleute können optimierte SECURITY DEFINER-Strukturen oder Schema-Variablen entwerfen, um verschachtelte Beziehungen zu verarbeiten, ohne unter endloser Rekursion oder inakzeptabler Latenz zu leiden.
- Compliance und Auditierung: Anwendungen, die Gesundheitsdaten (HIPAA), Finanzaufzeichnungen (PCI-DSS) oder Unternehmensdaten (SOC 2) verarbeiten, erfordern strengste Zugriffskontrollen. Unabhängige Sicherheitsaudits durch Dritte und automatisierte Penetrationstests (mit spezialisierten Tools zum Scannen nach exponierten Endpunkten oder Speicher-Buckets) sind erforderlich, um die Einhaltung gesetzlicher Vorschriften nachzuweisen.
- Architektonische Migrationen: Wenn eine Organisation über direkte Client-zu-Datenbank-Anfragen hinaus skaliert und benutzerdefinierte Backend-Middleware (Edge-Funktionen, REST-Wrapper) implementieren muss, können Sicherheitsarchitekten sicherstellen, dass der service_role-Schlüssel sicher verwendet wird, ohne isolierte Mandantendaten unbeabsichtigt zu überbrücken.
Fazit
Row-Level Security ist das fundamentale defensive Rückgrat jeder Anwendung, die auf modernen Backend-as-a-Service-Architekturen aufbaut. Die Möglichkeit, eine Datenbank-API sicher dem öffentlichen Internet preiszugeben, ist ein äußerst leistungsstarkes Paradigma, erfordert jedoch eine kompromisslose Einhaltung von Best Practices im Sicherheitsbereich. Das Ausmaß der Schwachstellen, die durch einfache Versäumnisse offengelegt werden – wie das Vergessen der RLS-Aktivierung oder das Fehlkonfigurieren einer WITH CHECK-Klausel – verdeutlicht nachdrücklich, dass die Datenbanksicherheit nicht als Nebensächlichkeit behandelt werden darf.
Durch das tiefe Verständnis der PostgreSQL-Primitive, die Supabase zugrunde liegen, die Optimierung von Richtlinien für die Leistung des Abfrageplaners, die strikte Trennung von Client- und Service-Schlüsseln sowie die Implementierung rigoroser automatisierter Datenbanktests mittels pgTAP können Organisationen die Geschwindigkeit der BaaS-Entwicklung voll ausschöpfen, ohne die Datenintegrität oder das Vertrauen der Benutzer zu gefährden. Sicherheit in diesem Ökosystem erfordert ständige Wachsamkeit; sie muss während des gesamten Softwareentwicklungslebenszyklus kontinuierlich integriert, getestet und validiert werden.
References
Click any inline citation to jump to its matching source entry.
- [1]Row Level Security, Supabase Docs, accessed June 20, 2026
- [2]Database Testing with pgTAP, Supabase Docs, accessed June 20, 2026
- [3]Lovable CVE-2025-48757 security advisory, accessed June 20, 2026
- [4]CREATE POLICY, PostgreSQL Documentation, accessed June 20, 2026
- [5]CREATE FUNCTION (SECURITY DEFINER), PostgreSQL Documentation, accessed June 20, 2026
Passende nächste Schritte
Vertiefen Sie das Thema mit den wichtigsten Service- und Projektseiten.
PostgreSQL Leistungsoptimierung
Erfahren Sie, wie Sie relationale und dokumentenbasierte Cloud-Datenbanken und KI-Workflows optimal konfigurieren.
Multi-Tenant-Architekturen
Leitfaden zur Implementierung von mandantenfähigen Strukturen für mobile und Web-Apps.
CI/CD-Pipelines für Migrationen
Wie wir Datenbankmigrationen, Schema-Updates und Tests automatisiert und sicher in die Cloud einbinden.




