// docs / baas security / supabase storage
Checklista för Supabase storage-bucket-säkerhet: 22 punkter
Supabase Storage är ett tunt lager runt en S3-kompatibel bucket plus samma row-level security-modell som databasen. Det betyder att samma RLS-fallgropar som drabbar tabeller drabbar filåtkomst — plus några storage-specifika som dyker upp när AI-kodningsverktyg kopplar upp uppladdningar. Den här checklistan har 22 punkter i fem sektioner: bucket-konfiguration, RLS-policys, uppladdningsvalidering, signerade URL:er och operativ hygien. Var och en är verifierbar på under 15 minuter.
Varje punkt nedan är essentiell. För den underliggande RLS-mekaniken, se Supabase RLS-scanner. För nyckel-exponeringsklassen angränsande storage, se Supabase service-role-nyckel exponerad i JavaScript.
Bucket-konfiguration
Börja med rätt defaults. En felkonfigurerad bucket läcker filer oavsett om din RLS är korrekt eller inte.
- Sätt varje bucket som standard till privat. I Supabase-dashboarden → Storage → Buckets, sätt Public bucket-växeln till av om du inte har en explicit anledning (marknadsföringstillgångar, publika avatarer utan PII). Publika buckets kringgår RLS för läsoperationer — vem som helst med bucket-namnet kan lista och ladda ner.
- Sätt en hård filstorleksgräns på varje bucket. Dashboard → Bucket-inställningar → File size limit. 50 MB är en vettig standard för användaruppladdningar; höj den medvetet för video-/storfilsfall. Utan gräns kan en enda skadlig uppladdning förbruka din lagringskvot eller månadsbandbredd.
- Begränsa tillåtna MIME-typer per bucket. Tillåten MIME-typ-lista — explicit allowlist, inte blocklist.
image/jpeg,image/png,image/webpför bild-bara-buckets. Tillåt aldrigtext/html,application/javascriptellerimage/svg+xmli en användarinnehållsbucket — de exekverar i webbläsaren när de serveras via signerad URL. - Använd en bucket per innehållstyp, inte en delad bucket. Per-bucket-inställningar (storlek, MIME-typer, RLS-policys) är den granularitet du har. En
user-avatars-bucket, endocument-uploads-bucket och enpublic-assets-bucket är lättare att låsa än en blandad bucket. - Verifiera CORS-konfigurationen om frontend laddar upp. Om användare laddar upp direkt från webbläsaren till en signerad URL måste bucketens CORS lista din produktionsorigin.
*är acceptabelt endast för publika buckets — aldrig för buckets som innehåller användar-PII.
RLS-policys på storage.objects
Supabase Storage lagrar fil-metadata i tabellen storage.objects. RLS på den tabellen styr vem som kan läsa, ladda upp, uppdatera eller radera filer. Utan RLS är bucketens public/private-flagga ditt enda skydd.
- Bekräfta att RLS är aktiverat på storage.objects.
SELECT rowsecurity FROM pg_tables WHERE schemaname = 'storage' AND tablename = 'objects';måste returneratrue. Supabase aktiverar det som standard på nya projekt; verifiera att det inte har inaktiverats. - Skriv en SELECT-policy begränsad till
auth.uid()för privata buckets.CREATE POLICY "users_read_own_files" ON storage.objects FOR SELECT USING (auth.uid()::text = (storage.foldername(name))[1]);. Konventionen är att lagra filer under[user-id]/[filename]och användastorage.foldername()för att extrahera ägaren ur sökvägen. - Skriv en INSERT-policy som tvingar fram samma sökvägskonvention.
CREATE POLICY "users_upload_own" ON storage.objects FOR INSERT WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);. Utan WITH CHECK kan en autentiserad användare ladda upp i en annan användares mapp. - Lägg till UPDATE- och DELETE-policys om din app stöder filändringar eller -borttagningar. Varje kommando behöver sin egen policy. Att hoppa över DELETE betyder att autentiserade användare inte kan ta bort sina egna filer; att hoppa över UPDATE betyder att filöverskrivningar tyst misslyckas.
- Testa korsanvändaråtkomst i två webbläsarsessioner. Logga in som användare A, ladda upp en fil, kopiera sökvägen. Logga in som användare B i en annan webbläsare, försök hämta filen via REST-API:t. Svaret måste vara
403eller404, aldrig200.
-- 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]);Uppladdningsvalidering
Validera varje uppladdning serverside, även när bucketen har MIME- och storleksrestriktioner. AI-kodningsverktyg genererar som standard endast klientsidesvalidering; det skyddar ingenting.
- Kontrollera MIME-typen serverside på nytt utifrån filens faktiska bytes, inte
Content-Type-headern. Använd ett bibliotek somfile-type(Node) eller magic-byte-sniffing. En angripare kan deklareraContent-Type: image/jpegpå en fil som i själva verket är en polyglott HTML-/JavaScript-payload. - Strippa EXIF-metadata från uppladdade bilder. EXIF kan innehålla GPS-koordinater, enhetens serienummer och tidsstämplar. Använd
sharpmed.withMetadata(false)ellerexif-parserför att strippa innan lagring. - Avvisa SVG:er som innehåller
script-taggar elleronload-handlers. SVG är XML — och många AI-genererade appar tillåter SVG-uppladdningar som "bara en bild". AnvändDOMPurifyserverside eller vägra SVG-uppladdningar helt. - Använd deterministiska, icke gissningsbara filnamn. Behåll inte det ursprungliga filnamnet. Använd en UUID eller en hash av filinnehållet. Ursprungliga filnamn läcker ("
passport_scan_2024_01_15.jpg") och förutsägbara namn möjliggör enumeration.
Signerade URL:er
Signerade URL:er är hur klienter kommer åt privata buckets. Förfallotiden, bucket-scopet och vad som loggas spelar roll.
- Sätt signerad-URL-förfallotid till 1 timme eller mindre som standard. Supabase JS-SDK:ts
createSignedUrl(path, expiresIn)tar sekunder. Använd aldrig värden som31536000(ett år) — URL:en blir en permanent semi-publik länk. - Lagra aldrig signerade URL:er i din databas. Generera färska serverside vid varje förfrågan. En lagrad signerad URL med 1-årsförfall som läcker via en databasdump ger långsiktig åtkomst.
- Logga signerad-URL-generering, inte bara filuppladdningar. Om du misstänker en kompromettering senare behöver du veta vem som genererade vilken URL när. Logga
auth.uid()+ bucket + objektsökväg + tidsstämpel. - Använd
downloadAs-alternativet när du serverar användaruppladdade filer.createSignedUrl(path, expiresIn, { download: '.jpg' })tvingar fram enContent-Disposition: attachment-header så att filen laddas ner istället för att renderas — avväpnar HTML-/SVG-/HTML-i-PDF-exekveringsklassen.
Operativ hygien
Storage-konfiguration driftar över tid. Dessa fyra operativa punkter håller ytan stram.
- Granska buckets kvartalsvis. Dashboard → Storage → Buckets. Bekräfta att public/private-status och MIME-typ-listor matchar vad appen förväntar sig. Buckets som skapats "tillfälligt" blir permanenta om ingen tar bort dem.
- Övervaka anonyma listoperationer. Storage-loggar (Dashboard → Logs → Storage) registrerar
LIST-förfrågningar. En spik av anonyma list-förfrågningar mot en privat bucket betyder att någon sonderar den utifrån. - Sätt en retentionspolicy för efemära uppladdningar. Temp-buckets (förhandsgranskning, utkast-uppladdningar) bör auto-raderas efter 24–72 timmar via en schemalagd funktion. Obegränsad retention är en skyldighet under GDPR/CCPA-dataminimerings-åtaganden.
- Kör en FixVibe-scanning månadsvis.
baas.supabase-storage-public-checken sonderar buckets som svarar på anonymaGET+LIST. Nya buckets tillkommer; gamla ändrar synlighet — bara kontinuerlig skanning fångar driften.
Nästa steg
Kör en FixVibe-scanning mot din produktions-URL — anonyma storage-listningar dyker upp under baas.supabase-storage-public. Kombinera den här checklistan med Supabase RLS-scanner för tabell-lagret och Supabase service-role-nyckel exponerad i JavaScript för den angränsande nyckel-exponeringen. För storage-felkonfigurationer hos andra BaaS-leverantörer, se BaaS-felkonfigurationsscanner.
