„Supabase-Sicherheit" bedeutet für eine mit KI gebaute App zwei verschiedene Dinge. Das erste ist die Projektkonfiguration: was Supabase von Haus aus offenlegt und was du selbst schließen musst. Das zweite ist, was Lovable, Bolt oder Cursor mit Supabase anstellen, wenn sie den Code erzeugen, und daher stammen fast alle Löcher. Dieser Guide deckt beides ab, mit drei SQL-Abfragen, um dein Projekt in einer Minute zu prüfen, einem Review-Prompt für deine KI und der Methode, von außen zu kontrollieren, dass alles dicht ist.
Teil 1: Wie Sicherheit in Supabase funktioniert
Die Daten-API ist absichtlich öffentlich
Supabase stellt vor dein Postgres eine REST-API (PostgREST), die der Browser direkt mit dem anon-Schlüssel (oder dem neueren publishable-Schlüssel) aufruft. Dieser Schlüssel steckt im JavaScript jedes Nutzers, und das ist normal: Er identifiziert das Projekt, er gewährt keine Rechte. Wer welche Zeile lesen oder schreiben darf, entscheidet Row Level Security. Der andere Schlüssel, service_role (oder secret), umgeht RLS vollständig und darf nur auf einem Server leben. Wenn du diese Trennung verstehst, verstehst du 80 % der Supabase-Sicherheit.
RLS und Policies: die einzige echte Barriere
Eine neue Tabelle im Schema public ist über die API erreichbar, sobald du sie anlegst. Ohne aktiviertes RLS liest und schreibt jeder mit dem anon-Schlüssel alle Zeilen. Mit aktiviertem RLS und ohne Policies kommt niemand hinein. Mit Policies kommt hinein, wen sie zulassen. Daher die Regel: RLS auf jeder Tabelle und eine explizite Policy pro Operation (select, insert, update, delete). Die konkreten Muster (jeder Nutzer seine Zeilen, Teamzugehörigkeit, öffentlicher Nur-Lese-Inhalt) stehen in was du in Supabase vor dem Launch prüfen solltest.
Was RLS umgeht, ohne dass du es merkst
- Views. Standardmäßig läuft eine View mit den Rechten dessen, der sie erstellt hat, ignoriert also das RLS der abgefragten Tabellen. Lege sie mit
with (security_invoker = on)an. - Security-Definer-Funktionen. Eine so markierte RPC-Funktion läuft als ihr Besitzer und liest, was sie will. Nutze sie nur, wenn es unbedingt nötig ist, mit
set search_path = '', und entzieheanondas Ausführungsrecht, wenn ein Besucher sie nicht aufrufen soll. - Exponierte Schemas. Alles in
public(Tabellen, Views und Funktionen) ist über die API erreichbar. Ist eine Funktion intern, verschieb sie in ein anderes Schema. - Der service_role-Schlüssel. Im Frontend, in einem öffentlichen Repository oder in einem an eine KI gepasteten Prompt bedeutet er volle Kontrolle über die Datenbank.
Auth: Einstellungen, die einen Blick verdienen
Unter Authentication findest du die E-Mail-Bestätigung (lass sie an), die Liste erlaubter Redirect URLs (ist sie leer oder enthält Platzhalter, kann ein Magic Link auf einer fremden Domain landen), die Rate-Limits, Captcha für Registrierung und Wiederherstellung, die Passwortrichtlinie, den Schutz vor geleakten Passwörtern und anonyme Anmeldungen (aus, außer du nutzt sie bewusst). Jede ist eine Checkbox; keine ist standardmäßig falsch, aber die KI kippt sie gern um, damit „die Tests durchlaufen".
Storage, Realtime und Edge Functions
Öffentliche Buckets liefern jede Datei an jeden mit der URL: Für Nutzer-Uploads nimm private Buckets, Policies auf storage.objects und signierte URLs mit Ablauf, dazu Größenlimit und erlaubte Typen pro Bucket. In Realtime respektieren Postgres-Änderungen das RLS, aber Broadcast- und Presence-Kanäle brauchen eigene Policies, wenn sie private Daten transportieren. In Edge Functions gehen Geheimnisse über supabase secrets set, nie in den Code, und wenn du die JWT-Prüfung für einen Webhook abschaltest, prüf die Signatur des Absenders selbst.
Das Projekt selbst
Der direkte Postgres-Verbindungsstring (mit dem Datenbankpasswort) darf deinen Server oder deine Umgebungsvariablen nie verlassen. Aktiviere den SSL-Zwang, erwäge Netzwerkbeschränkungen im Bezahlplan und schau regelmäßig in den Security Advisor des Dashboards (Database, Advisors): Er meldet Tabellen ohne RLS, Security-Definer-Views, Funktionen mit veränderbarem search_path und exponierte Auth-Tabellen.
Teil 2: Was die KI mit Supabase falsch macht
Das sind die Fehler, die in mit Lovable, Bolt oder Cursor erzeugten Apps am häufigsten auftauchen, nach Häufigkeit sortiert:
- Tabellen ohne RLS anlegen. Der Generator schreibt die Migration, die App läuft, und niemand aktiviert etwas. Das ist der Ursprung von Lovables CVE-2025-48757, die Daten aus Hunderten Projekten offenlegte.
using (true)-Policies. Blockiert RLS etwas, „repariert" die KI das mit einer Policy, die alles durchlässt. Das ist abgeschaltetes RLS unter anderem Namen.- service_role im Browser. Eine Abfrage scheitert an Rechten, und die schnelle Lösung ist der Schlüsseltausch. Such jetzt sofort nach
service_rolein deinem Bundle. - Security-Definer-Funktionen, um Fehler zu umgehen. Gleiches Muster: Statt die richtige Policy zu schreiben, wird die Abfrage in eine Funktion gepackt, die als Superuser läuft.
- Öffentliche Buckets für Nutzerdateien. Avatare, in Ordnung; Rechnungen, Dokumente oder Exporte, nein.
- Vom Nutzer editierbare Privilegfelder. Ein
roleoderis_adminin der Tabelleprofilesmit einer Update-Policy, die diese Spalten nicht ausschließt. Jeder macht sich aus der Browser-Konsole zum Admin. - E-Mail-Bestätigung oder Limits abschalten, damit Tests schneller laufen, und es in Produktion vergessen.
Drei SQL-Abfragen, um dein Projekt zu prüfen
Füge sie in den SQL-Editor des Dashboards ein. Die erste listet öffentliche Tabellen ohne RLS; idealerweise liefert sie nichts:
select n.nspname as schema, c.relname as table
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where c.relkind = 'r' and n.nspname = 'public' and not c.relrowsecurity;
Die zweite findet Policies, die alles durchlassen:
select tablename, policyname, cmd, roles
from pg_policies
where schemaname = 'public' and (qual = 'true' or with_check = 'true');
So eine Policy auf einer öffentlichen Nur-Lese-Inhaltstabelle kann richtig sein; auf profiles, orders oder messages nicht. Die dritte listet Funktionen, die mit den Rechten ihres Besitzers laufen:
select p.proname, pg_get_function_identity_arguments(p.oid) as args
from pg_proc p
join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public' and p.prosecdef;
Jedes Ergebnis sollte einen Grund haben, den du erklären kannst. Falls nicht, entferne den security definer oder verschieb die Funktion aus public.
Ein Review-Prompt vor dem Deploy
Füge ihn in Lovable, Bolt oder Cursor ein, mit Zugriff auf dein Schema und deinen Code:
Prüfe die Sicherheit dieser Supabase-App und ändere noch nichts. Antworte mit einer Liste:
1. Tabellen im Schema public ohne aktiviertes RLS.
2. Policies mit using (true) oder with check (true), und ob sie gerechtfertigt sind.
3. Privilegspalten (role, is_admin, plan, credits), die ein Nutzer mit den aktuellen Update-Policies ändern könnte.
4. Views ohne security_invoker und Security-Definer-Funktionen, mit dem Grund für jede.
5. Jede Verwendung des service_role-Schlüssels oder des Postgres-Verbindungsstrings außerhalb des Servers.
6. Öffentliche Buckets mit Nutzerdateien.
7. Abgeschaltete Auth-Einstellungen (E-Mail-Bestätigung, Limits, Captcha) und Redirect URLs mit Platzhaltern.
Schlag für jeden Punkt das exakte SQL oder die exakte Änderung vor und warte auf meine Bestätigung.
Prüf das Ergebnis von außen
Der endgültige Test ist nicht das Dashboard, sondern was die API einem Fremden antwortet. Mit deiner Projekt-URL und dem anon-Schlüssel (beide stehen im Browser-Code, ein Angreifer hat sie also auch):
curl -s "https://DEIN_PROJEKT.supabase.co/rest/v1/profiles?select=*" -H "apikey: DEIN_ANON_KEY" -H "Authorization: Bearer DEIN_ANON_KEY"
Kommen Zeilen zurück, ist diese Tabelle offen. Kommt [] oder ein Rechtefehler, funktioniert RLS. Wiederhole das für jede sensible Tabelle und mit einem normalen angemeldeten Nutzer, um zu bestätigen, dass er nur seine eigenen Daten sieht. Scanaris übernimmt diese Prüfung für dich: Es erkennt die Supabase-URL und den Schlüssel in deinem Bundle, testet die üblichen Tabellen ohne Sitzung, sucht den service_role-Schlüssel und den Verbindungsstring im JavaScript, prüft, wo deine App das Sitzungstoken speichert, und gibt dir für jeden Fund einen Fix-Prompt. Hat Lovable deine App erzeugt, lies auch den Sicherheits-Guide für Lovable; sind die Schlüssel im Client das Problem, Geheimnisse, die deine App im Browser preisgibt.
Häufige Fragen
Ist es ein Problem, dass der anon-Schlüssel im Browser liegt?
Nein. Er ist dafür gedacht und identifiziert nur das Projekt. Die Daten schützt RLS. Was nie in den Browser darf, ist service_role (oder der neue secret-Schlüssel).
Ändern die neuen publishable- und secret-Schlüssel etwas?
Sie ändern das Format und lassen sich rotieren, ohne das JWT anzufassen, aber das Modell bleibt gleich: publishable ist öffentlich und läuft durch RLS; secret umgeht es und lebt auf dem Server. Die alten Schlüssel funktionieren weiter, bis du sie deaktivierst.
Wenn Lovable oder Bolt meine App gebaut hat, muss ich trotzdem ins Supabase-Dashboard?
Ja. Das Tool erzeugt das Schema, aber RLS, Policies, Buckets und Auth-Einstellungen gehören dir. Zehn Minuten mit den drei Abfragen oben und dem Security Advisor sagen dir, in welchem Zustand das Projekt ist.