Nach dem Restore fehlt die Tabelle: wie Row Level Security eine Rücksicherung übersteht
Die Policy übersteht die Rücksicherung, die Rechte der Anwendungsrolle nicht. Am Minimalbeispiel gemessen: warum PostgreSQL dann eine fehlende Tabelle meldet, warum der Restore-Test grün bleibt und welche Reparatur den Schutz abschaltet.
Row Level Security ist schnell eingebaut: Policy anlegen, Anwendungsrolle ohne Sonderrechte, fertig. Dann kommt die erste Rücksicherung, die Anwendung meldet eine fehlende Tabelle, und die Tabelle ist da. Die naheliegende Reparatur funktioniert sofort und schaltet den Schutz still ab.
Die Lücke fand in einem Projekt eine Prüfung im Code-Review, bevor sie jemanden nachts geweckt hätte. Für diesen Beitrag habe ich sie an einem Minimalbeispiel nachgestellt: eine Tabelle kunden mit drei Zeilen, zwei Mandanten, PostgreSQL 16.14, Clientwerkzeuge in Version 17.11. Die vom 17er-Client erzeugte Zeile SET transaction_timeout habe ich vorher entfernt, ein reines Versionsartefakt. Alle Meldungen und Zahlen unten stammen aus diesem Lauf.
Der Aufbau, der eigentlich stimmt
Die Tabelle trägt ENABLE und FORCE ROW LEVEL SECURITY und eine Policy, die gegen die Sitzungsvariable app.tenant_id vergleicht. Die Anwendung verbindet sich mit einer eigenen Rolle, die weder Eigentümer noch Superuser ist und kein BYPASSRLS hat. Ohne gesetzten Mandanten sieht sie null Zeilen, mit Mandant a genau dessen zwei.
Die Sicherung läuft mit pg_dump --no-owner --no-privileges, damit ein Stand nicht an den Rollen eines bestimmten Servers hängt (solange die Policy keine Rolle nennt). Die Rücksicherung ersetzt das Schema komplett. Das ist Absicht: --clean entfernt nur, was im Dump steht, neuere Tabellen blieben liegen.
DROP SCHEMA IF EXISTS public CASCADE;
CREATE SCHEMA public;
-- danach der Inhalt des Dumps, alles in einer TransaktionJeder Schritt ist vernünftig. Zusammen nehmen sie der Rolle alle Rechte am Schema und an seinen Objekten.
Was nach der Rücksicherung wirklich vorliegt
Die Rücksicherung lief fehlerfrei durch. Danach, jeweils als Anwendungsrolle, sofern nicht anders angegeben:
| Prüfung | Ergebnis |
|---|---|
SELECT 1 | 1 |
SELECT count(*) FROM kunden | relation "kunden" does not exist |
SELECT count(*) FROM public.kunden | permission denied for schema public |
Policy, ENABLE, FORCE (Katalog) | alle vorhanden |
| Rechteliste des Schemas (Katalog) | leer, vorher drei Einträge |
Einträge in pg_default_acl | 0, vorher 2 |
Die zweite Zeile ist die teure. PostgreSQL überspringt bei der Namensauflösung jedes Schema, auf dem die Rolle kein USAGE hat. Die Tabelle ist aus Sicht der Rolle also tatsächlich nicht auffindbar. Die Meldung führt nur zuverlässig zur falschen Frage: Hat die Rücksicherung Daten verloren? Der Blick als Superuser zeigt drei Zeilen, was die Verwirrung nicht kleiner macht.
Auch ohne eigenes GRANT auf das Schema wäre das passiert. Das mitgelieferte public-Schema erlaubt allen Rollen USAGE. Ein neu angelegtes Schema gleichen Namens tut das nicht. Die Standardrechte aus ALTER DEFAULT PRIVILEGES ... IN SCHEMA public verschwinden mit dem Schema gleich mit.
Die Reparatur, die keine ist
Es ist spät, die Anwendung steht, und ein Zugang funktioniert nachweislich: der aus der Sicherung. Also trägt jemand ihn in die Anwendung ein. Beim offiziellen Docker-Image ist der Benutzer aus POSTGRES_USER ein Superuser, und Superuser übergehen Row Level Security immer, auch bei FORCE. Mit gesetztem Mandanten b, dem genau eine Zeile gehört, liefert die Abfrage in meinem Lauf 3 von 3 Zeilen. Keine Fehlermeldung, die Anwendung käme sofort wieder hoch. Die Störung wäre behoben, der Mandantenschutz auch.
Die zweite Versuchung ist die Reparatur in Raten. Da knallt wenigstens jede Lücke einzeln:
- Nur
GRANT USAGEauf das Schema:permission denied for table kunden - Zusätzlich Tabellenrechte: Lesen geht, das Einfügen scheitert an
permission denied for sequence kunden_id_seq - Zusätzlich Sequenzrechte: läuft, bis eine Migration eine neue Tabelle anlegt, denn die Standardrechte fehlen weiterhin
Die letzte Stufe ist die unangenehmste, weil sie erst bei der nächsten Migration mit neuer Tabelle auffällt. Das kann Wochen später sein.
Warum der Restore-Test das nicht sieht
Ein ordentlicher Restore-Test spielt den Stand in eine Wegwerf-Datenbank und prüft, ob alle Tabellen da sind und die Migrationsversion stimmt. In meinem Nachbau zählte die Sicherungsrolle dort brav 3 Zeilen, während die Anwendungsrolle in derselben Datenbank permission denied for table kunden bekam (dort bleibt das mitgelieferte public-Schema erhalten, daher der andere Fehler). Der Test beantwortet die Frage „Sind die Daten zurück?“, nicht die Frage „Kommt die Anwendung wieder an sie heran?“. Auch die Gesundheitsprüfung hilft nicht, wenn sie nur SELECT 1 ausführt: Das gelingt ohne jedes Schemarecht.
Eine Rücksicherung ist erst bestanden, wenn die Anwendungsrolle mit gesetztem Mandanten genau dessen Zeilen sieht und ohne Mandanten keine.
Was stattdessen trägt
Die Rechte stehen nicht nur in der Migration, sondern auch in einem idempotenten Schritt, den der API-Dienst bei jedem Start unter dem Eigentümerzugang ausführt. Eine Migration läuft nach einer Rücksicherung nicht noch einmal, denn der Dump bringt seine Versionstabelle mit. Der Neustart der Dienste gehört deshalb fest zum Ablauf der Rücksicherung:
GRANT USAGE ON SCHEMA public TO app_rolle;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_rolle;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO app_rolle;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_rolle;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO app_rolle;
-- die Versionstabelle gehört den Migrationen, nicht der Anwendung
REVOKE ALL ON TABLE alembic_version FROM app_rolle;Dazu kommen zwei Wachen. Ist die für die Anwendung konfigurierte Rolle Superuser, hat sie BYPASSRLS oder ist sie der Eigentümer, bricht der Start mit Begründung ab. Und die Gesundheitsprüfung liest eine geschützte Tabelle statt SELECT 1, sodass fehlende Rechte als Störung sichtbar werden. Beide haben Grenzen: Die erste prüft die konfigurierte Rolle, nicht die tatsächliche Verbindung, und die zweite akzeptiert null Zeilen. Ein von Hand eingetragener Superuser-Zugang käme an beiden vorbei. Mein Rat deshalb: Die Anwendung fragt beim Start ihre eigene Verbindung ab (current_user, rolsuper, rolbypassrls) und verweigert den Dienst, wenn sie zu viel darf.
Gesichert wird mit einer Rolle, die Row Level Security übergehen darf: Als Anwendungsrolle bricht pg_dump mit query would be affected by row-level security policy for table "kunden" ab, ebenso als Eigentümer ohne Superuser, sobald FORCE gesetzt ist. Das ist die gute Variante des Fehlers. Mit --enable-row-security läuft der Dump als Anwendungsrolle dagegen fehlerfrei durch: ohne Mandant ohne eine Zeile aus kunden, mit Mandant nur mit dessen Zeilen.
Lässt man --no-privileges beim Sichern und Einspielen weg, kommen die Rechte der Anwendungsrolle im Beispiel zurück, auch die Standardrechte. Das ist eine brauchbare Alternative, nur legt der Dump keine Rollen an, und die Standardrechte nennen den Eigentümer trotz --no-owner beim Namen. Auf einem neuen Server müssen beide Rollen also vorher existieren, sonst bricht die Rücksicherung mit role ... does not exist ab.
Fazit
Row Level Security hat die Rücksicherung im Beispiel überstanden: Policy, ENABLE und FORCE kamen mit dem Dump zurück. Verloren gingen die Rechte daneben, und genau das erzeugt den Druck, zu dem Zugang zu greifen, für den die Policy nicht gilt. Wer Row Level Security einführt, sollte deshalb mindestens einmal zurücksichern und danach als Anwendungsrolle zählen, nicht als Superuser. Der Superuser sieht immer alle drei Zeilen. Das ist ja das Problem.
- #PostgreSQL
- #Row Level Security
- #pg_dump
- #Backup
- #Restore
- #Mandantentrennung
Weiterlesen
Woran RAG-Projekte scheitern: vier Fehler jenseits der Technik
13. November 2026 · RAG, Security, Engineering · 5 Min. Lesezeit
Der Wächter, der den ganzen Agenten anhält: was ein Fehlalarm wirklich kostet
8. November 2026 · KI-Agenten, Security, Engineering · 5 Min. Lesezeit
PHP2Shell: Was hinter den zwei kritischen PHP-Lücken steckt und was du jetzt tun musst
22. Oktober 2026 · Security, Engineering, PHP · 5 Min. Lesezeit