Supabase RLS: der eine kritische Fehler, der AI-gebaute Apps leaken lässt
Supabase RLS: der eine kritische Fehler, der AI-gebaute Apps leaken lässt
Fast jeder AI-App-Datenleak geht auf denselben Supabase-RLS-Fehler zurück: fehlende Row Level Security. So findest und behebst du ihn in Minuten.
Dieser Beitrag dient der allgemeinen technischen Information und ersetzt keine individuelle Prüfung oder Rechtsberatung. Stand zum Zeitpunkt der Veröffentlichung.
Fast jeder spektakuläre Datenleak bei einer AI-gebauten App geht auf denselben einen Fehler zurück: Row Level Security ist nicht aktiviert, während der öffentliche anon-Key im Browser liegt. Der Key ist by design öffentlich. RLS ist die eigentliche Schutzschicht, und genau die vergessen AI-Generatoren reihenweise.
Inhaltsverzeichnis
- Der eine Fehler, der fast jeden AI-App-Leak erklärt
- Wie RLS in Supabase wirklich funktioniert (und was der anon-Key ist)
- Warum AI-Generatoren genau hier stolpern
- Drei reale Vorfälle, ein Muster: CVE-2025-48757, Moltbook, Lovable-EdTech
- Der Fehler hinter dem Fehler: RLS an, aber Policy falsch
- In zehn Minuten prüfen: Finde den Fehler in deiner App
- Sofort beheben: RLS aktivieren und korrekte Policies setzen
- Warum ein grüner Security Advisor noch keine Sicherheit ist
- Mein Vorgehen: laufende Aufsicht statt Momentaufnahme
- Quellen
Der eine Fehler, der fast jeden AI-App-Leak erklärt
Ich habe in den letzten Monaten genug AI-gebaute Supabase-Apps angeschaut, um ein Muster zu sehen, das sich fast langweilig wiederholt. Die spektakulären Schlagzeilen klingen nach raffinierten Angriffen, aber der technische Kern ist fast immer derselbe: Eine Tabelle liegt im public-Schema, der öffentliche anon-Key steckt im Browser-Bundle, und Row Level Security ist nicht aktiviert. Mehr braucht es nicht. Wer die Projekt-URL und den Key hat, und beides liegt offen im ausgelieferten JavaScript, liest und schreibt direkt in der Datenbank.
Das ist kein exotischer Edge-Case. Es ist die mit Abstand häufigste kritische Fehlkonfiguration, die mir bei AI-generierten Apps begegnet. Und sie passt exakt in die Kategorie, die OWASP in den Top 10:2021 auf Platz 1 führt: Broken Access Control. Laut OWASP wurden 94 Prozent der getesteten Anwendungen auf irgendeine Form von Broken Access Control geprüft, und die Kategorie stieg von Platz 5 auf Platz 1. OWASP ist dabei deutlich: Zugriffskontrolle ist nur in vertrauenswürdigem serverseitigem Code oder einer serverlosen API wirksam, nicht im Client.[8] Genau dort, serverseitig in der Datenbank, sitzt bei Supabase RLS. Fehlt es, fehlt die einzige Schicht, die zwischen anonymem Besucher und deinen Daten steht.
In diesem Beitrag zeige ich, wie RLS wirklich funktioniert, warum AI-Generatoren ausgerechnet hier stolpern, und ich gehe das Muster anhand von drei realen Vorfällen durch: CVE-2025-48757, dem Moltbook-Vorfall und einem Lovable-EdTech-Fall. Danach zeige ich, wie du den Fehler in deiner eigenen App in wenigen Minuten findest und behebst, und warum ein grüner Linter trotzdem noch keine Sicherheit ist.
Wie RLS in Supabase wirklich funktioniert (und was der anon-Key ist)
Supabase legt eine Postgres-Datenbank offen und stellt eine automatisch generierte REST-API davor. Damit der Client darauf zugreifen kann, gibt es zwei Schlüsseltypen, und der Unterschied ist der ganze Punkt.
Der publishable beziehungsweise anon-Key ist laut Supabase ausdrücklich "safe to expose online", konkret auf einer Webseite, in einer Mobile- oder Desktop-App, in GitHub Actions, CLIs und im Quellcode.[5] Er ist also kein Geheimnis, sondern by design öffentlich. Wer eine Supabase-App im Browser öffnet und ins Netzwerk-Tab schaut, findet ihn. Das ist beabsichtigt. Der Schutz deiner Daten kommt laut Supabase nicht über die Geheimhaltung dieses Keys, sondern über Postgres und die eingebauten Rollen anon und authenticated.[5] Und genau hier setzt Row Level Security an: RLS ist Postgres' Mechanismus, der pro Zeile entscheidet, welche Rolle was sehen oder ändern darf.
Der zweite Schlüssel ist der secret beziehungsweise service_role-Key. Der hat laut Supabase vollen Zugriff auf die Projektdaten und umgeht RLS komplett. Supabase ist hier unmissverständlich: niemals öffentlich machen, niemals in einem Browser verwenden, auch nicht auf localhost.[5] Wer diesen Key clientseitig ausliefert, hat ein anderes, ebenfalls katastrophales Problem. Im Standardfall ist aber gar nicht der service_role-Key das Thema, sondern der völlig korrekt exponierte anon-Key, der auf eine ungeschützte Tabelle trifft.
Der entscheidende Satz steht direkt in der Supabase-Doku: RLS muss immer auf jeder Tabelle aktiviert sein, die in einem exponierten Schema gespeichert ist, und das ist standardmäßig das public-Schema.[4] Fehlt RLS, gilt laut Supabase: jede Tabelle ohne RLS mit passendem Grant ist für Rollen mit passenden Data-API-Grants erreichbar, zum Beispiel anon.[6] Im Klartext heißt das: ohne RLS ist die Tabelle für jeden mit der Projekt-URL offen.
Warum AI-Generatoren genau hier stolpern
Der Fallstrick liegt im Default-Verhalten, und das ist subtiler, als es klingt. Laut Supabase-Doku ist RLS standardmäßig aktiviert, wenn eine Tabelle über den Table Editor im Dashboard angelegt wird. Wird die Tabelle dagegen per rohem SQL oder über den SQL-Editor erstellt, muss man RLS selbst aktivieren und jeder Postgres-Rolle nur die nötigen Rechte geben.[4]
Und genau so arbeiten AI-Generatoren. Sie klicken nicht im Dashboard, sie schreiben Migrations und SQL. Ein create table-Statement aus einem Generator hat RLS damit nicht automatisch an. Wenn der Generator nicht im selben Atemzug enable row level security plus passende Policies erzeugt, entsteht exakt die offene Tabelle, über die wir hier reden. Der Code "funktioniert" sofort: die App liest und schreibt, das Feature ist da, der Demo-Flow läuft. Dass dieselbe Tabelle auch für jeden Fremden offen ist, sieht man dem funktionierenden Happy Path nicht an.
Dazu kommt das Architekturmuster, das Tools wie Lovable nutzen. Laut der CVE-Disclosure von Matt Palmer verlässt sich Lovable für Vertraulichkeit und Integrität ausschließlich auf RLS, während der Client direkte REST-Calls mit dem öffentlichen anon-Key macht.[2][3] Das ist ein legitimes Pattern, solange RLS sauber konfiguriert ist. Es hat aber keinerlei Sicherheitsnetz, wenn RLS fehlt. Palmer bringt die Lehre auf den Punkt: Das eigentliche Problem war nicht der exponierte Public-Key, wie zunächst vermutet, denn den liefert Supabase by design, sondern die fehlende RLS-Konfiguration.[2]
Das deckt sich mit dem, was ich generell bei AI-Code sehe und in der Launch-Checkliste für produktionsreife Software ausführlicher beschreibe: Generatoren bauen das Feature, nicht die Betriebsreife. Authentifizierung, also das Login, kommt meistens. Autorisierung, also die Frage, wer welche konkrete Zeile sehen darf, fehlt zu oft.
Drei reale Vorfälle, ein Muster: CVE-2025-48757, Moltbook, Lovable-EdTech
Drei Fälle, dasselbe Muster. Ich schreibe Zahlen aus Vorfällen bewusst als das, was sie sind: Angaben der jeweiligen Quelle, nicht meine eigene Prüfung.
CVE-2025-48757 (vom Hersteller bestritten). Laut NVD erlaubt eine unzureichende Row-Level-Security-Policy in von Lovable generierten Seiten bis 2025-04-15 remote unauthentifizierten Angreifern, beliebige Datenbanktabellen der generierten Seiten zu lesen oder zu beschreiben.[1] Die Einstufung ist CVSS 9.3 (CVSS v3.1), CWE-863 (Incorrect Authorization), veröffentlicht am 29.05.2025.[1] Wichtig zur Einordnung: Die CVE ist vom Hersteller bestritten, mit dem Argument, jeder Kunde sei für den Schutz der Daten seiner App selbst verantwortlich.[1] Laut der Disclosure von Matt Palmer fand ein Scan das Muster in 303 verwundbaren Endpunkten über 170 Projekte, rund 10,3 Prozent von 1.645 analysierten Lovable-Apps. Palmer weist selbst darauf hin, dass der Scan nur Homepages untersuchte und nicht login-geschützte Bereiche, die Zahl also eine Untergrenze ist.[2] Bestätigt wird der Umfang sekundär von einer weiteren Analyse, die ebenfalls über 170 betroffene Apps nennt.[11]
Moltbook (laut Wiz Research). Beim AI-Agenten-Netzwerk Moltbook lag laut Wiz Research ein hardcodierter Supabase-publishable-Key im clientseitigen JS-Bundle, und ohne RLS-Policies gewährt dieser Key laut Wiz vollen Datenbankzugriff für jeden, der ihn hat. RLS war laut Wiz komplett deaktiviert.[9] Laut Wiz waren so rund 1,5 Millionen API-Auth-Tokens, 35.000 E-Mail-Adressen und insgesamt etwa 4,75 Millionen Records exponiert, entdeckt am 31.01.2026.[9] Eine zweite Quelle rundet anders, mit rund 1,49 Millionen Records, weshalb ich die Zahlen ausdrücklich Wiz zuschreibe.[12] Behoben wurde es laut Wiz in mehreren Phasen bis zum 01.02.2026, schlicht durch das Aktivieren von RLS-Policies.[9] Kein Zero-Day, kein ausgefeilter Exploit. Ein fehlendes enable row level security.
Lovable-EdTech-App (laut Sicherheitsbericht). Hier ist Genauigkeit wichtig, denn es kursieren zwei verschiedene Vorfälle. Laut einem Sicherheitsbericht (Bastion, offengelegt am 27.02.2026) waren bei einer EdTech-App 18.697 User-Records betroffen, darunter 14.928 eindeutige E-Mail-Adressen, aufgeteilt in 4.538 Studenten, 10.505 Enterprise-Nutzer und 870 mit voller PII. Gefunden wurden 16 Schwachstellen, davon 6 kritisch. Die App nutzte den anon-Key für direkte Browser-REST-Calls ohne RLS.[10] Der lehrreichste Befund: eine invertierte Auth-Logik, bei der der Schutzmechanismus die Richtigen blockiert und die Falschen durchlässt.[10] Wichtig: Der platformweite Tenant-Isolation-Vorfall vom 20.04.2026 ist ein separater Fall, den ich hier nicht mit dieser EdTech-App vermische.
Das Muster über alle drei: kein cleverer Angriff, sondern eine offene oder falsch konfigurierte Tür. Genau deshalb ist dieser Fehler so gefährlich. Er ist trivial auszunutzen und trivial zu übersehen.
Der Fehler hinter dem Fehler: RLS an, aber Policy falsch
Es wäre zu einfach, wenn "RLS aktivieren" das Problem komplett lösen würde. Tut es nicht. RLS zu aktivieren ist die notwendige Bedingung, nicht die hinreichende.
Der Lovable-EdTech-Fall zeigt das exemplarisch: Eine invertierte Auth-Logik kann selbst bei aktivem RLS dazu führen, dass anonyme Zugriffe durchgehen, weil die Policy genau falsch herum prüft.[10] Eine Policy, die zu viel freigibt, ist genauso ein Loch wie eine fehlende. Und eine RPC-Funktion (eine als API exponierte Postgres-Funktion) kann Daten zurückgeben, an denen die RLS-Policy der zugrunde liegenden Tabelle vorbeigeht.
Deshalb braucht es zwei Dinge: korrekte Policies pro Zugriffsart, und einen echten Multi-User-Test. Der Test ist banal, aber er ist der einzige, der die Frage wirklich beantwortet: Loggt euch als Nutzer A ein, merkt euch eine fremde Datensatz-ID von Nutzer B, und versucht, sie über die API zu lesen. Wenn das gelingt, ist die Policy falsch, egal wie grün der Linter leuchtet. Das ist konzeptionell exakt der IDOR-Test, den OWASP unter A01 beschreibt: ein Parameter wie eine Konto-ID wird auf ein fremdes Objekt umgebogen.[8]
In zehn Minuten prüfen: Finde den Fehler in deiner App
Du brauchst für den ersten Check weder ein Audit-Budget noch Spezialwerkzeug. Drei Schritte reichen für eine belastbare erste Einschätzung.
Schritt 1: Database Linter. Supabase hat einen eingebauten Linter. Der relevante Lint ist rls_disabled_in_public mit Severity ERROR. Er markiert jede public-Tabelle ohne RLS. Die Begründung im Lint ist genau unser Thema: Ist RLS auf einer public-Tabelle nicht aktiviert, kann jeder mit der Projekt-URL in der betroffenen Tabelle Zeilen anlegen, lesen, ändern oder löschen (CRUD).[7] Jeder ERROR aus diesem Lint ist eine offene Tür.
Schritt 2: Der reale anon-Key-Test. Nimm den öffentlichen anon-Key deiner App, den du im Browser-Bundle ohnehin findest, und sprich damit die REST-API direkt an. Versuche, eine Tabelle ohne Login zu lesen. Kommen Daten zurück, die nur eingeloggte Nutzer sehen sollten, hast du den Fehler reproduziert.
Schritt 3: Der Multi-User-Test. Wie oben beschrieben: Nutzer A versucht, Daten von Nutzer B über dessen ID zu lesen. Das deckt falsche Policies auf, die der Linter nicht sieht.
Wenn dir schon dieser kurze Durchgang Bauchschmerzen macht, ist das ein gutes Signal, genauer hinzusehen. Für eine schnelle, strukturierte erste Selbsteinschätzung ohne Setup habe ich den interaktiven AI-App-Risiko-Check gebaut: ein paar gezielte Fragen, am Ende eine grobe Einordnung, wo deine App steht.
Sofort beheben: RLS aktivieren und korrekte Policies setzen
Das Aktivieren selbst ist eine Zeile. Laut Supabase-Doku:
alter table "table_name" enable row level security;
Danach ist über den anon-Key erst einmal nichts zugänglich, und das ist gewollt: ohne Policy gibt RLS standardmäßig nichts frei.[4] Du fügst nun pro Zugriffsart genau die Policy hinzu, die du brauchst. Das verbreitete Muster aus der Doku, jeden Nutzer auf seine eigenen Zeilen zu begrenzen, sieht für eine profiles-Tabelle so aus:[4]
create policy "User can see their own profile only."
on profiles for select
using ( (select auth.uid()) = user_id );
Zwei Dinge sind hier wichtig. Erstens: Du brauchst pro Operation eine passende Policy, also getrennt für select, insert, update und delete, je nachdem, was clientseitig erlaubt sein soll. Eine select-Policy schützt keine Schreibzugriffe. Zweitens das (select auth.uid()) statt nacktem auth.uid(): Das ist ein Performance-Pattern aus der Doku, kein Sicherheits-Pattern. Laut Supabase erzeugt das Wrapping einen initPlan, sodass der Postgres-Optimierer die Funktion einmal pro Statement statt einmal pro Zeile auswertet.[4] In den Doku-Benchmarks sinkt eine gewrappte uid-Policy von 179 ms auf 9 ms, eine Verbesserung von rund 95 Prozent.[4] Auf einer großen Tabelle ist das der Unterschied zwischen brauchbar und unbenutzbar. Sicherer wird die Policy dadurch nicht, nur schneller.
Warum ein grüner Security Advisor noch keine Sicherheit ist
Ein grüner Linter ist ein gutes Gefühl, aber kein Sicherheitsnachweis. Er beantwortet genau eine Frage: Ist RLS auf den public-Tabellen aktiviert? Er beantwortet die wichtigeren nicht.
Er prüft nicht, ob deine Policies fachlich richtig sind. Er merkt nicht, wenn die Auth-Logik invertiert ist, wie im EdTech-Fall.[10] Er sieht nicht, ob irgendwo doch ein service_role-Key im Client gelandet ist, der RLS ohnehin umgeht.[5] Und er prüft nicht, ob eine RPC-Funktion an der Policy vorbei zu viel zurückgibt. Ein grüner Linter heißt "kein offensichtlicher Konfigurationsfehler". Er heißt nicht "die Zugriffslogik stimmt".
Das ist kein Versagen des Tools, sondern eine Grenze seiner Bauart. Ein Linter prüft Konfiguration, kein fachliches Verhalten. Die Frage, ob Nutzer A die Daten von Nutzer B sehen darf, ist eine Geschäftslogik-Frage, und die kann ein automatischer Check nicht beantworten. Dafür braucht es den Multi-User-Test und ein Paar Augen, das versteht, was die App eigentlich erlauben soll.
Mein Vorgehen: laufende Aufsicht statt Momentaufnahme
Ich könnte dir hier eine einmalige RLS-Prüfung verkaufen, und für eine erste Bestandsaufnahme ist das auch sinnvoll. Aber ich halte den Einmal-Audit-Gedanken bei AI-gebauten Apps für die falsche Form. Eine RLS-Prüfung ist eine Momentaufnahme. AI-Apps ändern sich aber wöchentlich: Ein neuer Prompt, ein neues Feature, eine neue Migration, und schon liegt wieder eine Tabelle ohne RLS im public-Schema. Genau die Geschwindigkeit, die AI-Entwicklung attraktiv macht, macht den Einmal-Bericht nach kurzer Zeit veraltet.
Deshalb arbeite ich mit Veriploy als laufende technische Aufsicht statt als punktuellem Audit: regelmäßiger Blick auf Repo, RLS- und Konfigurationsrisiken, CVEs und Infrastruktur, priorisierte Risiken und klare nächste Schritte statt einer Scanner-Wand. Wer mit einer Bestandsaufnahme starten will, beginnt mit der Baseline (790 EUR einmalig). Wer die laufende Begleitung will, findet die Pakete gestaffelt: vom monatlichen Blick (Oversight, 990 EUR pro Monat) über engeres Sparring (Guard, 1.950 EUR pro Monat) bis zur engen Begleitung produktiver Produkte (Launch, 3.900 EUR pro Monat), Scale auf Anfrage.
Klar abgegrenzt, weil mir das wichtig ist: Veriploy macht deine App nicht sicher, gibt keine Garantie und ist kein Penetrationstest-Ersatz. Es ist auch keine Feature-Entwicklung. Es schafft Sichtbarkeit und Priorisierung, damit du fundiert entscheidest, was wirklich zuerst geschlossen werden muss. Bei einem Fehler, der so trivial auszunutzen und so leicht zu übersehen ist wie fehlendes RLS, ist genau dieses zweite Paar Augen oft der Unterschied zwischen einer ruhigen Nacht und einer Schlagzeile.
Quellen
- NVD, CVE-2025-48757 (CVSS 9.3, CVSS v3.1, CWE-863, vom Hersteller bestritten, veröffentlicht 29.05.2025): https://nvd.nist.gov/vuln/detail/CVE-2025-48757
- Matt Palmer, „Statement on CVE-2025-48757" (Disclosure, 303 Endpunkte / 170 Projekte / 1.645 gescannt, Homepages-only): https://mattpalmer.io/posts/statement-on-CVE-2025-48757/
- Matt Palmer, „CVE-2025-48757" (technische Analyse des RLS-Musters): https://mattpalmer.io/posts/CVE-2025-48757/
- Supabase Docs, Row Level Security (Default-Verhalten, Policy-Syntax, initPlan-Performance, Benchmarks): https://supabase.com/docs/guides/database/postgres/row-level-security
- Supabase Docs, API Keys (publishable vs. secret key, RLS-Schutz, „never use in a browser"): https://supabase.com/docs/guides/api/api-keys
- Supabase Docs, Securing your API (Tabelle ohne RLS ist für anon zugänglich): https://supabase.com/docs/guides/api/securing-your-api
- Supabase Splinter, Lint 0013 rls_disabled_in_public (Severity ERROR, Remediation): https://supabase.github.io/splinter/0013_rls_disabled_in_public/
- OWASP Top 10:2021, A01 Broken Access Control (#1 Risiko, 94 %, server-side enforcement, IDOR): https://owasp.org/Top10/2021/A01_2021-Broken_Access_Control/
- Wiz Research, „Exposed Moltbook database reveals millions of API keys" (laut Wiz: 1,5 Mio. Tokens, 35.000 E-Mails, ~4,75 Mio. Records, RLS deaktiviert, entdeckt 31.01.2026): https://www.wiz.io/blog/exposed-moltbook-database-reveals-millions-of-api-keys
- Bastion, „Lovable Data Breach" (EdTech-App offengelegt 27.02.2026: 18.697 Records, 14.928 E-Mails, 16 Schwachstellen, invertierte Auth-Logik): https://bastion.tech/blog/lovable-april-2026-data-breach/
- Superblocks, „Lovable vulnerability explained: how 170+ apps were exposed" (Sekundär-Bestätigung des Scan-Umfangs): https://www.superblocks.com/blog/lovable-vulnerabilities
- Implicator.ai, „Moltbook left every AI agent's API keys in an open database" (Sekundär-Bestätigung, rundet auf ~1,49 Mio. Records): https://www.implicator.ai/moltbook-left-every-ai-agents-api-keys-in-an-open-database-security-researcher-finds/
Häufige Fragen
Was ist der häufigste kritische Supabase-RLS-Fehler bei AI-gebauten Apps?▼
Ist der öffentliche anon-Key von Supabase ein Sicherheitsproblem?▼
Was war CVE-2025-48757?▼
Was ist beim Moltbook-Vorfall passiert?▼
Reicht es, RLS zu aktivieren, damit meine Daten sicher sind?▼
Wie prüfe ich, ob RLS auf all meinen Tabellen aktiv ist?▼
Wie aktiviere ich RLS und setze eine korrekte Policy?▼
Warum soll ich auth.uid() in (select auth.uid()) verpacken?▼
Ist ein grüner Supabase Security Advisor ein Sicherheitsnachweis?▼
Macht Veriploy meine Supabase-App sicher oder ist das ein Pentest?▼
Geschrieben von
Timo Wevelsiep
Entwickler & Gründer · Veriploy
Veriploy: technische Aufsicht für AI-gebaute Software. Betrieben von Timo Wevelsiep.