Zum Inhalt springen

Supabase RLS testen: 30 von 87 Rechten wirkten nicht

Als Markdown anzeigenIn Claude öffnenIn ChatGPT öffnenIn Perplexity öffnen

Die Frage wird ebenfalls kopiert. Falls der Chat leer öffnet, fügen Sie sie ein.

Kurz gesagt

Testen Sie die Row Level Security (RLS, Zugriffsregeln auf Zeilenebene) von Supabase so, wie ein Mitglied ihr begegnet: Melden Sie sich als jede Rolle an, führen Sie die echte Aktion einmal mit ausgeschaltetem und einmal mit eingeschaltetem Recht aus und prüfen Sie, ob sich das Ergebnis ändert. In einer Produktions-App mit fünf bearbeitbaren Rollen zeigte sich so, dass 30 der 87 Rechte-Kästchen, die ein Admin speichern konnte, in der Datenbank nichts bewirkten. Ein Test je Policy kann das nicht sehen, weil er die Policy prüft, die Sie geschrieben haben, nicht das Kästchen auf dem Bildschirm.

Ich bin Denys, Full-Stack- und KI-Entwickler. Anfang dieses Jahres habe ich für eine Wirtschaftsprüfungsgesellschaft einen Projekt- und Zeittracker auf Supabase gebaut, bei dem jede Zugriffsregel in der Datenbank liegt. Unten steht die Methode, mit der ich diese Regeln nachweise, was sie gefunden hat und was ihr Betrieb kostet.

Warum reichen Policy-Tests nicht, um Supabase RLS zu testen?

Ein Policy-Test beantwortet die Frage: “Tut diese Policy, was ich geschrieben habe?” Ihre Nutzer stellen eine andere: “Wenn ich dieses Kästchen für diese Rolle abwähle, kann sie es dann trotzdem?” In einer kleinen App haben beide Fragen dieselbe Antwort. In einer App mit einer Einstellungsseite, auf der Admins eine Rechtematrix bearbeiten, laufen sie auseinander.

Die App hatte 41 Tabellen mit aktivierter RLS und 402 CREATE POLICY-Anweisungen in 255 Migrationen. Der Admin-Bildschirm zeigte eine Matrix aus Bereichen (Projekte, Aufgaben, Stundenzettel, Dateien und so weiter) gegen Aktionen (ansehen, anlegen, bearbeiten, archivieren), eine Spalte pro Rolle. Jedes gespeicherte Kästchen war ein Versprechen an den Admin. Nichts prüfte, ob die Datenbank es einhielt.

Supabase dokumentiert die Standardwerkzeuge gut: Mit pgTAP-Tests und den Test-Helpern legen Sie Nutzer an, wechseln zu ihnen und prüfen, was sie lesen dürfen. Nutzen Sie sie. Die Methode unten setzt darauf auf: Sie geht von der Matrix aus, die der Admin sieht, nicht von der Liste der Policies.

Wie funktioniert der Matrix-Nachweis?

Jedes Kästchen der Matrix wird zu einer Probe. Eine Probe meldet sich als Mitglied der Rolle an, führt eine echte Aktion aus und hält das Ergebnis fest. Das passiert zweimal: einmal mit ausgeschaltetem, einmal mit eingeschaltetem Kästchen, bei sonst gleichen Bedingungen. Ändert sich das Ergebnis, wird das Kästchen durchgesetzt. Ändert es sich nicht, bewirkt das Kästchen nichts, so überzeugend es auf dem Bildschirm auch aussieht.

  1. Ein Kästchen der MatrixBeispiel: Standard Member · Aufgaben · Anlegen
  2. Kästchen aus: als Mitglied der Rolle handelnEine Aufgabe über denselben Weg anlegen, den die App nutzt. Ergebnis festhalten.
  3. Kästchen an: dieselbe Aktion wiederholenGleicher Nutzer, gleiche Daten. Nur das Kästchen hat sich geändert.
  4. Beide Ergebnisse vergleichenDas Urteil hängt nur davon ab, was die Datenbank getan hat.
  • Ergebnis hat sich geändertDurchgesetzt. Abgelehnt, wenn aus, erlaubt, wenn an.
  • In beiden Fällen gleichWirkungslos. Der Bildschirm verspricht Kontrolle, die es nicht gibt.
  • Bildschirm und Datenbank widersprechen sichEin Bug. Die Seite zeigt einen Button, den die Datenbank ablehnt, oder umgekehrt.
Abbildung 1. Jedes Urteil beruht auf einem Paar echter Ergebnisse, nie auf dem Lesen des Codes.

“Ergebnis” heißt hier, was auch immer ein Verbraucher des Rechts tut: Eine Policy liefert Zeilen oder lehnt einen Schreibvorgang ab, eine Datenbankfunktion wirft einen Fehler, ein Trigger blockiert, oder eine Edge Function antwortet mit 403. Ein Kästchen kann mehrere davon absichern, deshalb brauchten die 87 Kästchen 279 Prüfungen pro Rolle. Jede Prüfung endete mit einem dieser Urteile:

Urteil Prüfungen pro Rolle Bedeutung
Wirkung 227 Das Ergebnis kippte mit dem Kästchen
Wirkungslos 30 Das Ergebnis blieb mit Kästchen aus und an gleich
Unerreichbar 19 Die Aktion ist für diese Rolle gar nicht erreichbar, separat nachgewiesen
Edge Function 1 Durch eine Edge Function durchgesetzt, über HTTP geprüft
Nur Bildschirm 1 Ändert nur, was die Seite zeigt, durch einen Frontend-Test abgedeckt
Bildschirm und Datenbank stimmen überein 1 Was die Seite anbietet, entspricht dem, was die Datenbank erlaubt
Fehlschlag 0 Eine Prüfung, deren Ergebnis nicht der Erwartung entsprach

Was hat der Nachweis gefunden?

Der Lauf am 15. September 2026 deckte alle fünf Rollen ab, die ein Admin bearbeiten kann (Admin, Senior, Standard, Restricted und eine geklonte eigene Rolle). Die Ergebnisse waren für alle Rollen identisch.

87 Rechte-Kästchen, die ein Admin speichern konnte. Danach, was jedes Kästchen tatsächlich durchsetzte. Gleich für alle fünf Rollen. Von der Datenbank durchgesetzt: 55; Anderswo: 1 Edge Function, 1 nur Bildschirm: 2; Bewirkten nichts: 30.
Abbildung 2. Die 87 Rechte-Kästchen, die ein Admin speichern konnte, nach dem, was sie tatsächlich durchsetzte. Gleiches Ergebnis für jede der fünf Rollen.

Keines der 30 ließ jemanden herein. Bei 29 davon las keine Policy, keine Funktion und kein Trigger das Kästchen je aus; das letzte verlor seine Bedeutung, nachdem eine Policy neu geschrieben wurde. Ein echtes Problem ist das trotzdem: Ein Admin, der “Löschen” für eine Rolle abwählt, glaubt, dass die Rolle nicht löschen kann. Wir haben alle 30 ausgeblendet, statt sie anzubinden, denn jedes hätte eine Produktentscheidung gebraucht, und ein Kästchen ohne Wirkung gehört nicht auf den Bildschirm, solange man diese Entscheidung trifft.

Dieselbe Arbeit brachte vier weitere Fehler ans Licht:

Was falsch war Wie es sich zeigte Behebung
30 Kästchen bewirkten nichts Urteil “wirkungslos”, gleiches Ergebnis bei aus und an Aus der Matrix ausgeblendet
Drei Datenbankfunktionen ließen eine Aktion durch, wenn eine Aufgabe keine zuständige Person hatte Eine Probe, die hätte abgelehnt werden müssen, wurde erlaubt Die Funktionen lehnen jetzt ab, wenn der Wert fehlt
Das Zurücksetzen eines Stundenzettels auf Entwurf hing am falschen Kästchen Bildschirm und Datenbank widersprachen sich für eine Rolle An das Recht zum Wiedereröffnen gebunden, zu dem es gehört
Ein Mitglied sah neue Rechte erst nach dem Neuladen Die Datenbank hatte sich geändert, die Seite nicht Rechte werden alle 60 Sekunden und beim Fokus des Fensters aktualisiert
Ein Spalten-Default verletzte seinen eigenen Check-Constraint, daher schlugen Inserts ohne diese Spalte fehl Beim Security-Review der Behebung gefunden, in derselben Arbeit Default auf einen Wert geändert, den der Constraint erlaubt

Der Fehler mit der fehlenden zuständigen Person war der entscheidende. Die Prüfungen waren in PL/pgSQL so geschrieben:

if not (has_permission or assignee_id = auth.uid()) then
  raise exception 'forbidden';
end if;

Ist assignee_id null, ist assignee_id = auth.uid() null, false or null ist null, not null bleibt null, und if behandelt null als false. Die Exception wird nie ausgelöst. In der Produktion hatte keine Aufgabe eine zuständige Person, das war also kein Randfall: Es betraf jede Aufgabe im Geltungsbereich. Die Behebung schließt die Bedingung in coalesce(…, false) ein, sodass ein fehlender Wert zur Ablehnung führt, und sie ging als Erstes und einzeln live, vor dem Rest des Pakets.

Wie lässt man es gegen eine Kopie der Produktion laufen?

Ein Nachweis gegen ein Schema, das von der Produktion abweicht, beweist nichts über die Produktion. Das Harness läuft auf dem lokalen Supabase-Stack, in einer eigenen Datenbank, die eine Kopie des Produktionsschemas plus der zu prüfenden Migrationen ist. Vor jedem Lauf vergleicht es einen Fingerabdruck beider lokalen Datenbanken: 799 Zeilen, die Tabellen, Policies, Funktionen und Grants beschreiben. Jede Abweichung stoppt den Lauf.

  1. ProduktionsschemaAuf einen vertrauenswürdigen Rechner kopiert. Es landet nie auf einem gehosteten CI-Runner.
  2. Lokaler Stack: App-DatenbankÜber die lokale API und Auth bereitgestellt, genutzt von den End-to-End-Prüfungen.
  3. Lokaler Stack: isolierte Nachweis-DatenbankGleiches Schema, nur vom Matrix-Nachweis genutzt.
  4. Fingerabdruck-Prüfung799 Zeilen pro Datenbank. Jede Abweichung stoppt den Lauf vor der ersten Probe.
  5. Proben laufen als jede RolleAls echte angemeldete Mitglieder, über dieselben Wege, die die App nutzt.
  6. Keine SpurenDie Zeilenzahlen am Ende müssen den Zeilenzahlen am Anfang entsprechen.
Abbildung 3. Der Nachweis zählt nur, wenn die Datenbank, auf der er lief, der Produktion entspricht.

Zwei Details machten das Harness vertrauenswürdig:

  • Erst absichtlich kaputt machen. Bevor ich einem grünen Lauf traute, ersetzte ich eine Insert-Policy durch eine, die alles akzeptiert, und ließ den Nachweis erneut laufen. Er musste bei den Kästchen, die diese Policy absichert, rot werden. Ein Harness, das bei einer kaputten Policy grün bleibt, ist Dekoration.
  • Auch den gespeicherten Zustand prüfen. Der erste Durchlauf startet jede Rolle von einer sauberen Ausgangslage. Ein zweiter startet von der aktuell gespeicherten Matrix jeder Rolle, denn ein Kästchen kann sich neben den Kästchen, die ein Admin tatsächlich angehakt hat, anders verhalten.

Wie bleibt es nach dem Release grün?

Ein Nachweis, den man einmal laufen lässt, veraltet mit der nächsten Migration. Der Nachweis läuft jetzt vor jedem Push, der Migrationen, den Auth-Code oder die Matrix selbst berührt.

  1. git pushDie Änderung berührt Migrationen, Auth oder die Matrix.
  2. Pre-Push-Hook: Matrix-NachweisLäuft lokal gegen die Nachweis-Datenbank. Rot blockiert den Push.
  3. Push erreicht mainDas Deployment läuft wie gewohnt.
  4. CI-Lauf auf eigenem RechnerDerselbe Nachweis noch einmal, auf dem Rechner mit der Kopie des Produktionsschemas.
Abbildung 4. Die blockierende Schranke ist lokal, weil die Kopie des Produktionsschemas auf einem Rechner bleibt.

Der Nachweis umfasst 135 Tests in 11 Dateien, neben etwa 3.100 gewöhnlichen Unit-Tests im selben Projekt.

Was hat nicht funktioniert?

  • Gehostetes CI konnte ihn nicht ausführen. Der Nachweis braucht die Kopie des Produktionsschemas, und die wollte ich nicht auf einem gehosteten Runner haben. Deshalb ist die blockierende Schranke ein Pre-Push-Hook, und CI auf einem eigenen Rechner ist eine zweite Prüfung, auf die das Deployment nicht wartet.
  • Feste Zeilenzahlen brachen. Die erste “Keine Spuren”-Prüfung verglich mit festen Zahlen. Die nächste legitime Migration fügte eine Rechte-Zeile hinzu und machte sie rot. Jetzt vergleicht sie die Zahlen am Ende mit denen am Anfang.
  • Übersprungen ist nicht bestanden. Die End-to-End-Prüfungen brauchen die lokale Edge-Laufzeit. Ist sie aus, werden sie übersprungen, und ein Überspringen liest man leicht als grün.
  • Auf einem ausgelasteten Laptop ist er langsam. Die Durchläufe pro Rolle brauchten auf einem belasteten Rechner längere Timeouts.
  • Er beweist, was die Proben beschreiben. Ein Kästchen, für das niemand eine Probe geschrieben hat, wird als Abdeckungslücke gemeldet, nicht als sicher.

So wiederholen Sie das in Ihrer Supabase-App

  1. Listen Sie jedes Recht auf, das der Bildschirm speichern lässt: Rolle × Bereich × Aktion. Diese Liste ist die Spezifikation, nicht Ihre Policies.
  2. Notieren Sie für jedes Kästchen die echte Aktion, die es steuern soll, und woran man eine Ablehnung erkennt (keine Zeilen, ein Fehler, ein 403).
  3. Bauen Sie eine Datenbank, die der Produktion entspricht, und erstellen Sie einen Fingerabdruck, damit eine Schema-Abweichung den Lauf stoppt.
  4. Führen Sie jede Aktion als angemeldetes Mitglied aus, erst mit Kästchen aus, dann an, und vergleichen Sie.
  5. Machen Sie eine Policy absichtlich kaputt und prüfen Sie, dass der Nachweis rot wird.
  6. Blenden Sie jedes wirkungslose Kästchen aus oder binden Sie es an, und machen Sie den Nachweis dann zur Schranke vor dem Push.

Wenn Ihre App schon RLS hat, Sie aber nicht sicher sind, ob sie tut, was die Einstellungsseite sagt, funktioniert dieselbe Methode als Audit einer bestehenden Codebasis, der erste Schritt einer Projektrettung. Über das Projekt hinter diesen Zahlen habe ich in der Fallstudie zur Zeiterfassung für eine Wirtschaftsprüfungsgesellschaft geschrieben.

Unsicher, ob Ihre RLS zu Ihrer Einstellungsseite passt?

Gern tausche ich mich darüber aus, wie Sie sie testen. Ohne Verkaufsgespräch.

Kontakt aufnehmen