// docs / baas security / supabase storage
Lista kontrolna bezpieczeństwa kosza magazynu Supabase: 22 punkty
Supabase Storage to cienka warstwa wokół kosza zgodnego z S3 plus ten sam model zabezpieczeń na poziomie wierszy co baza danych. Oznacza to, że te same pułapki RLS, które dotyczą tabel, dotyczą dostępu do plików — i kilka specyficznych dla magazynu, które pojawiają się, gdy narzędzia kodowania AI podłączają przesyłanie. Ta lista kontrolna ma 22 punkty w pięciu sekcjach: konfiguracja kosza, polityki RLS, walidacja przesyłania, podpisane URL i higiena operacyjna. Każdy można zweryfikować w mniej niż 15 minut.
Każdy element poniżej jest niezbędny. Dla podstawowej mechaniki RLS zobacz Skaner Supabase RLS. Dla klasy ujawniania kluczy sąsiadującej z magazynem zobacz Klucz roli serwisowej Supabase ujawniony w JavaScript.
Konfiguracja kosza
Zacznij od właściwych domyślnych. Niewłaściwie skonfigurowany kosz wycieka pliki niezależnie od tego, czy Twój RLS jest poprawny, czy nie.
- Ustaw każdy kosz domyślnie na prywatny. W panelu Supabase → Storage → Buckets ustaw przełącznik Public bucket na wyłączony, chyba że masz wyraźny powód (zasoby marketingowe, publiczne awatary bez PII). Publiczne kosze omijają RLS dla operacji odczytu — każdy z nazwą kosza może wymieniać i pobierać.
- Ustaw twardy limit rozmiaru pliku dla każdego kosza. Panel → Ustawienia kosza → Limit rozmiaru pliku. 50 MB to rozsądna domyślna dla przesyłania przez użytkowników; podnoś celowo dla przypadków użycia wideo / dużych plików. Bez limitu pojedyncze złośliwe przesłanie może wyczerpać Twoją kwotę magazynu lub miesięczną przepustowość.
- Ogranicz dozwolone typy MIME na kosz. Lista dozwolonych typów MIME — jawna lista dozwolonych, nie lista zablokowanych.
image/jpeg,image/png,image/webpdla koszy tylko z obrazami. Nigdy nie zezwalaj natext/html,application/javascriptaniimage/svg+xmlw koszu z treścią użytkownika — wykonują się w przeglądarce po podaniu przez podpisany URL. - Użyj jednego kosza na typ treści, nie jednego wspólnego kosza. Ustawienia na kosz (rozmiar, typy MIME, polityki RLS) to ziarnistość, którą masz. Kosz
user-avatars, koszdocument-uploadsi koszpublic-assetssą łatwiejsze do zablokowania niż jeden mieszany kosz. - Zweryfikuj konfigurację CORS, jeśli frontend przesyła. Jeśli użytkownicy przesyłają bezpośrednio z przeglądarki na podpisany URL, CORS kosza musi wymieniać Twoje produkcyjne pochodzenie.
*jest akceptowalne tylko dla publicznych koszy — nigdy dla koszy zawierających PII użytkowników.
Polityki RLS na storage.objects
Supabase Storage przechowuje metadane plików w tabeli storage.objects. RLS na tej tabeli kontroluje, kto może odczytywać, przesyłać, aktualizować lub usuwać pliki. Bez RLS flaga publiczna/prywatna kosza jest Twoją jedyną ochroną.
- Potwierdź, że RLS jest włączony na storage.objects.
SELECT rowsecurity FROM pg_tables WHERE schemaname = 'storage' AND tablename = 'objects';musi zwrócićtrue. Supabase włącza go domyślnie w nowych projektach; zweryfikuj, że nie został wyłączony. - Napisz politykę SELECT z zakresem
auth.uid()dla prywatnych koszy.CREATE POLICY "users_read_own_files" ON storage.objects FOR SELECT USING (auth.uid()::text = (storage.foldername(name))[1]);. Konwencja to przechowywanie plików pod[user-id]/[filename]i używaniestorage.foldername()do wyodrębnienia właściciela ze ścieżki. - Napisz politykę INSERT, która wymusza tę samą konwencję ścieżki.
CREATE POLICY "users_upload_own" ON storage.objects FOR INSERT WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);. Bez WITH CHECK uwierzytelniony użytkownik może przesyłać do folderu innego użytkownika. - Dodaj polityki UPDATE i DELETE, jeśli Twoja aplikacja obsługuje edycję lub usuwanie plików. Każde polecenie potrzebuje własnej polityki. Pominięcie DELETE oznacza, że uwierzytelnieni użytkownicy nie mogą usuwać własnych plików; pominięcie UPDATE oznacza, że nadpisywanie plików po cichu się nie udaje.
- Przetestuj dostęp międzyużytkownikowy w dwóch sesjach przeglądarki. Zaloguj się jako Użytkownik A, prześlij plik, skopiuj ścieżkę. Zaloguj się jako Użytkownik B w innej przeglądarce, spróbuj pobrać plik przez REST API. Odpowiedź musi być
403lub404, nigdy200.
-- 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]);Walidacja przesyłania
Waliduj każde przesłanie po stronie serwera, nawet gdy kosz ma ograniczenia MIME i rozmiaru. Narzędzia kodowania AI generują domyślnie walidację tylko po stronie klienta; to nic nie chroni.
- Ponownie sprawdź typ MIME po stronie serwera z rzeczywistych bajtów pliku, a nie nagłówka
Content-Type. Użyj biblioteki takiej jakfile-type(Node) lub wąchania magic byte. Atakujący może żądaćContent-Type: image/jpegna pliku, który w rzeczywistości jest poliglotem HTML / JavaScript. - Usuń metadane EXIF z przesłanych obrazów. EXIF może zawierać współrzędne GPS, numery seryjne urządzeń i znaczniki czasu. Użyj
sharpz.withMetadata(false)lubexif-parser, aby usunąć przed przechowywaniem. - Odrzucaj SVG zawierające tagi
scriptlub procedury obsługionload. SVG to XML — i wiele aplikacji generowanych przez AI pozwala na przesyłanie SVG jako „tylko obraz". UżyjDOMPurifypo stronie serwera lub całkowicie odmawiaj przesyłania SVG. - Użyj deterministycznych, niezgadywalnych nazw plików. Nie zachowuj oryginalnej nazwy pliku. Użyj UUID lub skrótu zawartości pliku. Oryginalne nazwy plików wyciekają informacje ("
passport_scan_2024_01_15.jpg"), a przewidywalne nazwy umożliwiają wyliczanie.
Podpisane URL
Podpisane URL to sposób, w jaki klienci uzyskują dostęp do prywatnych koszy. Wygaśnięcie, zakres kosza i to, co jest logowane, mają znaczenie.
- Domyślnie wygaśnięcie podpisanego URL na 1 godzinę lub mniej.
createSignedUrl(path, expiresIn)w Supabase JS SDK przyjmuje sekundy. Nigdy nie używaj wartości takich jak31536000(jeden rok) — URL staje się trwałym częściowo publicznym linkiem. - Nigdy nie przechowuj podpisanych URL w swojej bazie danych. Generuj świeże po stronie serwera przy każdym żądaniu. Przechowywany podpisany URL z rocznym wygaśnięciem, który wycieknie przez zrzut bazy danych, daje długoterminowy dostęp.
- Loguj generowanie podpisanych URL, nie tylko przesyłanie plików. Jeśli później podejrzewasz kompromitację, musisz wiedzieć, kto wygenerował który URL kiedy. Loguj
auth.uid()+ kosz + ścieżkę obiektu + znacznik czasu. - Użyj opcji
downloadAspodczas obsługi plików przesłanych przez użytkowników.createSignedUrl(path, expiresIn, { download: '.jpg' })wymusza nagłówekContent-Disposition: attachment, więc plik się pobiera zamiast renderować — pokonuje klasę wykonywania HTML / SVG / HTML-w-PDF.
Higiena operacyjna
Konfiguracja magazynu z czasem dryfuje. Te cztery elementy operacyjne utrzymują powierzchnię szczelną.
- Sprawdzaj kosze kwartalnie. Panel → Storage → Buckets. Potwierdź, że stan publiczny/prywatny i listy typów MIME pasują do tego, czego oczekuje aplikacja. Kosze utworzone „tymczasowo" stają się trwałe, jeśli nikt ich nie usunie.
- Monitoruj operacje anonimowego listowania. Logi magazynu (Panel → Logs → Storage) rejestrują żądania
LIST. Skok anonimowych żądań listowania wobec prywatnego kosza oznacza, że ktoś go sondy z zewnątrz. - Ustaw politykę przechowywania dla efemerycznych przesyłań. Tymczasowe kosze (podgląd obrazu, robocze przesyłania) powinny automatycznie usuwać się po 24-72 godzinach przez zaplanowaną funkcję. Nieokreślone przechowywanie jest odpowiedzialnością zgodnie z obowiązkami minimalizacji danych GDPR / CCPA.
- Uruchamiaj skanowanie FixVibe co miesiąc. Kontrola
baas.supabase-storage-publicsondy kosze, które odpowiadają na anonimoweGET+LIST. Nowe kosze są dodawane; stare zmieniają widoczność — tylko ciągłe skanowanie wyłapuje dryf.
Następne kroki
Uruchom skanowanie FixVibe wobec swojego produkcyjnego URL — anonimowe listy magazynu pojawiają się pod baas.supabase-storage-public. Połącz tę listę kontrolną ze Skanerem Supabase RLS dla warstwy tabel i Kluczem roli serwisowej Supabase ujawnionym w JavaScript dla sąsiedztwa ujawniania kluczy. Dla błędnych konfiguracji magazynu u innych dostawców BaaS zobacz Skaner błędnych konfiguracji BaaS.
