Alle Beiträge
Vorfall6 Min. Lesezeit

7.403 Lovable-Apps haben Nutzerdaten geleakt. Ist deine Supabase-Tabelle dabei?

Translucent database cylinder with an open glowing drawer spilling envelopes and ID cards into the dark

Diese Woche hat die Hamburger Sicherheitsfirma Crissy Field gemeinsam mit dem Handelsblatt eine Untersuchung zu Apps veröffentlicht, die mit Lovable gebaut wurden. Sie zogen 159.041 Lovable-Projekte aus dem Common-Crawl-Archiv, gebaut zwischen Dezember 2025 und September 2026, und stellten eine einfache Frage: Was gibt die Datenbank einem völlig Fremden heraus, der nicht eingeloggt ist?

Die Antwort war ernüchternd. Laut dem Bericht von Startup Fortune nutzten 34.545 dieser Projekte Supabase als Backend, und 24.664 davon lieferten Datenbankeinträge ganz ohne Authentifizierung aus. Auf 7.403 Websites enthielten diese Einträge E-Mail-Adressen, verteilt auf 10.560 Tabellen. RuntimeWire berichtet, dass zu den offengelegten Daten Passnummern, Zahlungsinformationen und mehr als 200.000 Chatnachrichten gehörten. Bei einem Dienstleister waren über 1.000 Passdatensätze lesbar; ein Lifestyle-Forum leakte neben diesen Chatnachrichten 80.000 Nutzerprofile.

Es war keine Zero-Day-Lücke im Spiel, und im dramatischen Sinne wurde nichts „gehackt“. Die Forscher nutzten reine Leseanfragen mit dem jeweils eigenen öffentlichen Key der Website. Die Daten lagen einfach für jeden bereit, der danach fragte.

Wenn du irgendetwas auf Supabase ausgeliefert hast, besonders mit einem KI-Builder, zeigt dir dieser Beitrag, wie du deine eigene App in wenigen Minuten prüfst und das Problem richtig behebst. Es ist kein reines Lovable-Problem: Bolt, Cursor, v0 und von Hand geschriebene Apps machen denselben Fehler.

Was eigentlich schiefgelaufen ist

Supabase stellt deine Postgres-Tabellen über eine automatisch generierte REST-API bereit. Dein Frontend spricht mit dieser API über den Anon-Key (auch Publishable Key genannt). Dieser Key ist dafür gedacht, öffentlich zu sein. Er wird in deinem JavaScript-Bundle ausgeliefert, und jeder kann ihn in den DevTools lesen.

Dieses Design ist in Ordnung, solange auf jeder Tabelle Row Level Security (RLS) mit sinnvollen Policies aktiviert ist. RLS entscheidet, welche Zeilen eine bestimmte Anfrage sehen darf. Wie TheCyberSecGuru anmerkt, mit einem Zitat aus der Supabase-Dokumentation selbst: „Ohne RLS kann jeder mit deinem Anon-Key auf alle Daten zugreifen.“

Die Fehlerkette sieht also so aus:

  • Ein KI-Tool (oder ein müder Mensch) generiert create table profiles (...).
  • Niemand ergänzt enable row level security.
  • Die App funktioniert im Test einwandfrei, denn der Bug besteht ja gerade darin, dass sie zu viel erlaubt.
  • Jeder, der den Anon-Key aus deinem Bundle kopiert, kann select * auf diese Tabelle ausführen.

Wenn du Hintergrundwissen zur Funktionsweise von RLS willst, erklärt unser Guide zu Supabase Row Level Security die Grundlagen. Im Folgenden konzentrieren wir uns aufs Testen und Beheben.

Supabase Row Level Security: der vollständige Guide

So prüfst du deine App (5 Minuten)

1. Schau sie dir von außen an, so wie die Forscher. Öffne deine Live-Website, öffne die DevTools und durchsuche das geladene JavaScript nach supabase.co. Dort findest du deine Projekt-URL und direkt daneben den Anon-Key. Frag dann im Terminal eine Zeile aus einer Tabelle ab, die du für privat hältst:

curl "https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*&limit=1" \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

Was dir die Antwort sagt:

  • Ein leeres Array [] oder ein Berechtigungsfehler: gut, anonyme Nutzer können diese Tabelle nicht lesen.
  • Echte Zeilen: Du hast dasselbe Problem wie diese 7.403 Websites.

Wiederhole das für jede Tabelle mit Nutzerdaten: profiles, users, messages, orders, bookings, leads, waitlist und so weiter. Wartelisten- und Kontaktformular-Tabellen vergisst man leicht, und sie enthalten fast immer E-Mail-Adressen.

Wenn du schon im Bundle bist, prüf auch, ob der gefundene Key der Anon-Key ist und nicht der service_role-Key. Die Service-Rolle umgeht RLS vollständig. Steckt sie in deinem Frontend, behandle sie als geleakt und rotiere sie sofort: Alles in deinem Bundle kann jeder Besucher und jedes Drittanbieter-Skript auf der Seite lesen, wie die Nutzer von Polymarket erfahren mussten, und Bots jagen bereits nach geleakten Keys. Unser Beitrag zum Rotieren der Secrets deiner App ohne Ausfallzeit erklärt die Rotation, und API-Keys im clientseitigen JavaScript zeigt, was sonst noch gern in Bundles landet.

API-Keys im Frontend-Code: was du sicher ausliefern kannst

2. Prüf von innen. Liste im Supabase-SQL-Editor jede Tabelle im public-Schema auf und ob RLS aktiv ist:

select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;

Jede Zeile mit rowsecurity = false ist mit dem Anon-Key lesbar (und möglicherweise beschreibbar). Der Security Advisor im Supabase-Dashboard markiert solche Tabellen ebenfalls. Lies seine Warnungen, statt sie wegzuklicken.

3. Such nach „RLS an, aber trotzdem offen“. Aktiviertes RLS ist noch nicht die Ziellinie. Liste deine Policies auf:

select tablename, policyname, roles, cmd, qual
from pg_policies
where schemaname = 'public';

Achte auf qual = true bei einer select-Policy, die für anon oder public gilt. Das ist eine Policy, die sagt „alle dürfen alles lesen“, und damit dasselbe wie gar kein RLS. KI-Tools generieren manchmal genau das, um eine Fehlermeldung verschwinden zu lassen.

So behebst du es

Aktiviere RLS für jede Tabelle:

alter table public.profiles enable row level security;
alter table public.messages enable row level security;
-- repeat for every table in public

Mit aktivem RLS und ohne Policies ist die Tabelle gesperrt: Der Anon-Key bekommt nichts zurück. Das ist ein sicherer Ausgangszustand. Füge jetzt nur den Zugriff hinzu, den du wirklich brauchst.

Schreib eng gefasste Policies. Ein typisches Setup nach dem Muster „Nutzer dürfen nur ihre eigene Zeile sehen und bearbeiten“:

create policy "Read own profile"
on public.profiles for select
to authenticated
using ((select auth.uid()) = user_id);

create policy "Update own profile"
on public.profiles for update
to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);

Darauf solltest du achten:

  • Beschränke Policies auf authenticated statt public, außer die Daten sind wirklich öffentlich (Blogbeiträge, Produktlisten).
  • Trenne Lesen und Schreiben. Eine Tabelle kann gefahrlos öffentlich lesbar sein und trotzdem streng abgesicherte Inserts brauchen.
  • Öffentliche Tabellen leaken trotzdem Spalten. Soll eine profiles-Tabelle für Anzeigenamen öffentlich sein, ist eine E-Mail- oder Telefonspalte in derselben Tabelle ebenfalls öffentlich. Verschiebe private Felder in eine separate Tabelle oder stelle eine View bereit, die nur die unbedenklichen Spalten enthält.
  • Views können RLS umgehen. In Postgres laufen Views standardmäßig mit den Rechten ihres Besitzers. Erstelle Views mit security_invoker = true, damit das RLS des Aufrufers greift.
  • Storage ist eine eigene Baustelle. Öffentliche Buckets liefern Dateien an jeden aus, der die URL kennt. Hochgeladene Ausweise, Rechnungen und Avatare privater Nutzer gehören in private Buckets mit Storage-Policies.

Mach es zum Standard, nicht zur Aufräumaktion. Die Stichprobe der Forscher umfasst zehn Monate an neuen Apps, das heißt: Das passiert bei jeder neuen Tabelle wieder. Sorg dafür, dass man es kaum vergessen kann:

  • Ergänze die Projektanweisungen deines KI-Tools um eine Regel: Auf jedes create table in public müssen enable row level security und explizite Policies folgen.
  • Prüf generierte Migrationen vor dem Merge auf create table ohne passendes enable row level security.
  • Wiederhole den Test von außen nach jeder Schemaänderung, nicht nur beim Launch.

Wenn du Firebase statt Supabase nutzt, gibt es dort dieselbe Fehlerklasse in Form offener Security Rules. Siehe Firebase Security Rules: Lass deine Datenbank nicht mehr offen.

Firebase Security Rules: so sicherst du deine Datenbank ab

Wenn du offengelegte Daten gefunden hast

Eine korrigierte Policy verhindert künftige Lesezugriffe, macht vergangene aber nicht ungeschehen. Sei ehrlich zu dir selbst, was erreichbar war und wie lange. Prüf deine Supabase-API-Logs auf ungewöhnliche Massenabfragen von unbekannten IPs, rotiere alle Secrets, die in lesbaren Tabellen gespeichert waren, und kläre deine Meldepflichten, falls personenbezogene Daten wie E-Mail-Adressen, Ausweise oder Zahlungsdaten offenlagen. In der EU greifen dann wahrscheinlich die Meldevorschriften der DSGVO.

Kurze Checkliste

Bevor du einen Haken dahinter setzt, stell sicher, dass:

  • jede Tabelle in public rowsecurity = true hat.
  • keine select-Policy auf Tabellen mit privaten Daten using (true) verwendet.
  • Policies auf authenticated beschränkt und an auth.uid() gebunden sind.
  • private Spalten nicht in öffentlich lesbaren Tabellen liegen.
  • Views security_invoker = true verwenden.
  • nur der Anon-Key in deinem Frontend steckt, niemals service_role.
  • private Dateien in privaten Storage-Buckets liegen.
  • du nach jeder Schemaänderung erneut von außen testest.

Prüf deine App von außen

Unsere Sicherheits-Checkliste für KI-gebaute Apps deckt die anderen Punkte ab, die du vor dem Launch prüfen solltest. Das Unbequeme an dieser Untersuchung ist, wie gewöhnlich die Methode war: öffentlicher Key, Leseanfrage, Daten kommen zurück. Genau diese Sicht hat ein Angreifer, und genau diese Sicht solltest du regelmäßig prüfen.

AppSafe scannt deine Live-App von außen. Der verifizierte vollständige Scan enthält reine Lese-Checks auf offene Supabase- und Firebase-Datenbanken und sucht nach API-Keys, die in deinen JavaScript-Bundles offenliegen, und geplante Scans können dich benachrichtigen, wenn sich nach einem Deploy etwas ändert. Neugierig, wie ein Report aussieht? Wirf einen Blick auf den Beispiel-Report.

Starte jetzt einen kostenlosen Scan deiner App und finde heraus, was ein Fremder sehen kann, bevor es jemand anderes tut.

Diese Checkliste automatisch durchlaufen

Ein kostenloser Scan prüft deine App auf all diese Probleme und liefert dir für jeden Befund einen KI-Fix-Prompt.

Meine App gratis scannen