// docs / baas security / supabase storage
Supabase-Storage-Bucket-Sicherheits-Checkliste: 22 Punkte
Supabase Storage ist eine dünne Schicht über einem S3-kompatiblen Bucket plus dasselbe Row-Level-Security-Modell wie die Datenbank. Das heißt, die gleichen RLS-Fallstricke, die Tabellen betreffen, betreffen den Dateizugriff — plus ein paar Storage-spezifische, die auftauchen, wenn KI-Coding-Tools Uploads verdrahten. Diese Checkliste umfasst 22 Punkte in fünf Sektionen: Bucket-Konfiguration, RLS-Policies, Upload-Validierung, Signed URLs und operative Hygiene. Jeder ist in unter 15 Minuten verifizierbar.
Jeder Punkt unten ist essenziell. Für die zugrunde liegenden RLS-Mechaniken siehe Supabase-RLS-Scanner. Für die Key-Offenlegungsklasse angrenzend an Storage siehe Supabase-Service-Role-Key in JavaScript offengelegt.
Bucket-Konfiguration
Beginne mit den richtigen Defaults. Ein falsch konfigurierter Bucket leakt Dateien, egal ob dein RLS korrekt ist oder nicht.
- Jeder Bucket standardmäßig privat. Im Supabase-Dashboard → Storage → Buckets, schalte den Public bucket-Toggle aus, sofern du keinen expliziten Grund hast (Marketing-Assets, öffentliche Avatare ohne PII). Public Buckets umgehen RLS für Lesezugriffe — jeder mit dem Bucket-Namen kann auflisten und herunterladen.
- Setze ein hartes Dateigrößen-Limit auf jeden Bucket. Dashboard → Bucket-Einstellungen → Dateigröße-Limit. 50 MB ist ein vernünftiger Default für Nutzer-Uploads; erhöhe es bewusst für Video- / Großdatei-Anwendungsfälle. Ohne Limit kann ein einziger böswilliger Upload deine Storage-Quote oder deine monatliche Bandbreite aufbrauchen.
- Beschränke erlaubte MIME-Types pro Bucket. Erlaubte-MIME-Types-Liste — explizite Allowlist, keine Blocklist.
image/jpeg,image/png,image/webpfür Bild-only-Buckets. Erlaube nietext/html,application/javascriptoderimage/svg+xmlin einem Nutzerinhalt-Bucket — sie führen im Browser aus, wenn sie via Signed URL ausgeliefert werden. - Verwende einen Bucket pro Inhaltstyp, nicht einen geteilten Bucket. Per-Bucket-Einstellungen (Größe, MIME-Types, RLS-Policies) sind die Granularität, die du hast. Ein
user-avatars-Bucket, eindocument-uploads-Bucket und einpublic-assets-Bucket sind einfacher zu sperren als ein gemischter Bucket. - Verifiziere die CORS-Konfiguration bei Frontend-Uploads. Wenn Nutzer direkt aus dem Browser an eine Signed URL hochladen, muss die CORS-Konfiguration des Buckets deinen Produktions-Origin listen.
*ist nur für öffentliche Buckets akzeptabel — niemals für Buckets mit Nutzer-PII.
RLS-Policies auf storage.objects
Supabase Storage speichert Datei-Metadaten in der Tabelle storage.objects. RLS auf dieser Tabelle steuert, wer Dateien lesen, hochladen, aktualisieren oder löschen kann. Ohne RLS ist die Public/Private-Flag des Buckets dein einziger Schutz.
- Bestätige, dass RLS auf storage.objects aktiviert ist.
SELECT rowsecurity FROM pg_tables WHERE schemaname = 'storage' AND tablename = 'objects';musstrueliefern. Supabase aktiviert es standardmäßig bei neuen Projekten; verifiziere, dass es nicht deaktiviert wurde. - Schreibe eine SELECT-Policy auf
auth.uid()für private Buckets eingegrenzt.CREATE POLICY "users_read_own_files" ON storage.objects FOR SELECT USING (auth.uid()::text = (storage.foldername(name))[1]);. Konvention: speichere Dateien unter[user-id]/[filename]und nutzestorage.foldername(), um den Eigentümer aus dem Pfad zu extrahieren. - Scope permitted INSERT operations to the user’s folder.
CREATE POLICY "users_upload_own" ON storage.objects FOR INSERT WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);. With RLS enabled, no applicable INSERT policy means denial; an overly broad applicable policy can permit uploads outside the intended folder. Review all applicable policies and the bucket constraint. - Füge UPDATE- und DELETE-Policies hinzu, wenn deine App Dateiänderungen oder -löschungen unterstützt. Jeder Befehl braucht seine eigene Policy. DELETE auszulassen bedeutet, dass authentifizierte Nutzer ihre eigenen Dateien nicht entfernen können; UPDATE auszulassen bedeutet, dass Datei-Überschreibungen lautlos fehlschlagen.
- Teste den nutzerübergreifenden Zugriff in zwei Browser-Sessions. Melde dich als Nutzer A an, lade eine Datei hoch, kopiere den Pfad. Melde dich als Nutzer B in einem anderen Browser an, versuche, die Datei über die REST-API zu holen. Die Antwort muss
403oder404sein, niemals200.
-- Confirm RLS on storage.objects
SELECT rowsecurity
FROM pg_tables
WHERE schemaname = 'storage' AND tablename = 'objects';
-- SELECT policy: scope reads to the owning user's folder.
CREATE POLICY "users_read_own_files"
ON storage.objects
FOR SELECT
USING (auth.uid()::text = (storage.foldername(name))[1]);
-- INSERT policy: enforce the [user-id]/[filename] path convention.
CREATE POLICY "users_upload_own"
ON storage.objects
FOR INSERT
WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);Upload-Validierung
Validiere jeden Upload serverseitig, selbst wenn der Bucket MIME- und Größenrestriktionen hat. KI-Coding-Tools generieren standardmäßig nur clientseitige Validierung; das schützt nichts.
- Prüfe den MIME-Type serverseitig erneut anhand der tatsächlichen Bytes der Datei, nicht des
Content-Type-Headers. Nutze eine Bibliothek wiefile-type(Node) oder Magic-Byte-Sniffing. Ein Angreifer kannContent-Type: image/jpegfür eine Datei deklarieren, die in Wirklichkeit ein polyglotter HTML-/JavaScript-Payload ist. - Entferne EXIF-Metadaten aus hochgeladenen Bildern. EXIF kann GPS-Koordinaten, Geräte-Seriennummern und Zeitstempel enthalten. Nutze
sharpmit.withMetadata(false)oderexif-parser, um vor der Speicherung zu strippen. - Lehne SVGs ab, die
script-Tags oderonload-Handler enthalten. SVG ist XML — und viele KI-generierte Apps erlauben SVG-Uploads als „einfach ein Bild". NutzeDOMPurifyserverseitig oder lehne SVG-Uploads ganz ab. - Verwende deterministische, nicht erratbare Dateinamen. Behalte nicht den ursprünglichen Dateinamen. Nutze eine UUID oder einen Hash des Dateiinhalts. Ursprüngliche Dateinamen leaken ("
passport_scan_2024_01_15.jpg") und vorhersehbare Namen ermöglichen Enumeration.
Signierte URLs
Signed URLs sind der Weg, wie Clients auf private Buckets zugreifen. Die Ablaufzeit, der Bucket-Scope und was geloggt wird, sind wichtig.
- Setze die Signed-URL-Ablaufzeit standardmäßig auf 1 Stunde oder weniger. Die
createSignedUrl(path, expiresIn)-Funktion des Supabase-JS-SDK nimmt Sekunden. Verwende niemals Werte wie31536000(ein Jahr) — die URL wird zu einem dauerhaft semi-öffentlichen Link. - Speichere niemals Signed URLs in deiner Datenbank. Generiere bei jedem Request frische serverseitig. Eine gespeicherte Signed URL mit 1-Jahres-Ablaufzeit, die über einen DB-Dump leakt, gewährt Langzeitzugriff.
- Logge die Signed-URL-Generierung, nicht nur die Datei-Uploads. Wenn du später Kompromittierung vermutest, musst du wissen, wer welche URL wann generiert hat. Logge
auth.uid()+ Bucket + Objektpfad + Zeitstempel. - Nutze die
downloadAs-Option, wenn du nutzergenerierte Dateien ausspielst.createSignedUrl(path, expiresIn, { download: '.jpg' })erzwingt einenContent-Disposition: attachment-Header, sodass die Datei heruntergeladen statt gerendert wird — schaltet die HTML-/SVG-/HTML-in-PDF-Ausführungsklasse aus.
Operative Hygiene
Die Storage-Konfiguration driftet über die Zeit. Diese vier operativen Punkte halten die Oberfläche eng.
- Auditiere Buckets quartalsweise. Dashboard → Storage → Buckets. Bestätige, dass Public/Private-Zustand und MIME-Type-Listen dem entsprechen, was die App erwartet. „Temporär" angelegte Buckets werden permanent, wenn niemand sie entfernt.
- Überwache anonyme Listen-Operationen. Storage-Logs (Dashboard → Logs → Storage) zeichnen
LIST-Requests auf. Ein Spike anonymer Listen-Requests gegen einen privaten Bucket bedeutet, dass jemand ihn von außen sondiert. - Setze eine Aufbewahrungsrichtlinie für ephemere Uploads. Temp-Buckets (Bildvorschau, Entwurfs-Uploads) sollten nach 24-72 Stunden via Scheduled Function automatisch gelöscht werden. Unbefristete Aufbewahrung ist eine Haftung unter den Datenminimierungs-Verpflichtungen der GDPR / CCPA.
- Run a FixVibe scan monthly. FixVibe reports storage buckets that anonymous visitors can list or read. New buckets get added; old ones change visibility — only continuous scanning catches the drift.
Nächste Schritte
Run a FixVibe scan against your production URL — anonymously listable storage shows up as its own finding. Pair this checklist with Supabase RLS scanner for the table layer and Supabase service role key exposed in JavaScript for the key-exposure adjacency. For storage misconfigurations across other BaaS providers, see BaaS misconfiguration scanner.
