← Poradniki stacków

Supabase

Bezpieczeństwo dla twojej aplikacji z Supabase

Supabase udostępnia twoją bazę danych przez API od pierwszej minuty. Bez dobrze ustawionego RLS albo z niewłaściwym kluczem we frontendzie każdy może odczytać twoje tabele. Sprawdź podstawy przed startem.

Typowe ryzyka przy Supabase

Tabele bez RLS

Row Level Security to główna bariera. Nowa tabela bez RLS jest otwarta do odczytu i zapisu z przeglądarki przy użyciu klucza publicznego.

service_role we frontendzie

Klucz service_role omija całe zabezpieczenie. Jeśli trafi do JavaScriptu klienta, oddaje pełną kontrolę nad twoją bazą danych.

Otwarte buckety Storage

Publiczny bucket udostępnia dowolny plik każdemu, kto ma URL. Prywatne dokumenty powinny być w bucketach chronionych przez policies.

„Bezpieczeństwo Supabase" w aplikacji tworzonej przez AI oznacza dwie różne rzeczy. Pierwsza to konfiguracja projektu: to, co Supabase wystawia z założenia, i to, co musisz zamknąć sam. Druga to to, co Lovable, Bolt czy Cursor robią z Supabase, gdy generują kod, i stamtąd biorą się niemal wszystkie dziury. Ten przewodnik obejmuje obie, z trzema zapytaniami SQL do audytu projektu w minutę, promptem do przeglądu dla Twojej AI i sposobem sprawdzenia z zewnątrz, że wszystko jest zamknięte.

Część 1: jak działa bezpieczeństwo w Supabase

API danych jest publiczne z założenia

Supabase stawia przed Twoim Postgresem API REST (PostgREST), które przeglądarka wywołuje bezpośrednio kluczem anon (albo nowszym kluczem publishable). Ten klucz trafia do JavaScriptu wszystkich Twoich użytkowników i to jest normalne: identyfikuje projekt, nie daje uprawnień. O tym, kto może czytać lub zapisywać każdy wiersz, decyduje Row Level Security. Drugi klucz, service_role (albo secret), całkowicie omija RLS i może żyć tylko na serwerze. Jeśli rozumiesz ten podział, rozumiesz 80 % bezpieczeństwa Supabase.

RLS i polityki: jedyna prawdziwa bariera

Nowa tabela w schemacie public jest dostępna przez API od chwili utworzenia. Bez włączonego RLS każdy z kluczem anon czyta i zapisuje wszystkie wiersze. Z włączonym RLS i bez polityk nie wchodzi nikt. Z politykami wchodzi ten, na kogo pozwalają. Stąd zasada: RLS na każdej tabeli i jedna jawna polityka na operację (select, insert, update, delete). Konkretne wzorce (każdy użytkownik swoje wiersze, członkostwo w zespole, publiczna treść tylko do odczytu) są w co sprawdzić w Supabase przed startem.

Co omija RLS, zanim to zauważysz

  • Widoki. Domyślnie widok działa z uprawnieniami tego, kto go utworzył, czyli ignoruje RLS tabel, które odpytuje. Twórz je z with (security_invoker = on).
  • Funkcje security definer. Tak oznaczona funkcja RPC działa jako jej właściciel i czyta, co chce. Używaj ich tylko, gdy to niezbędne, z set search_path = '', i odbierz prawo wykonania roli anon, jeśli odwiedzający nie powinien jej wywoływać.
  • Wystawione schematy. Wszystko w public (tabele, widoki i funkcje) jest osiągalne przez API. Jeśli funkcja jest wewnętrzna, przenieś ją do innego schematu.
  • Klucz service_role. We frontendzie, w publicznym repozytorium albo w prompcie wklejonym do AI to pełna kontrola nad bazą danych.

Auth: ustawienia, na które warto spojrzeć

W Authentication masz potwierdzenie e-maila (zostaw włączone), listę dozwolonych Redirect URLs (jeśli jest pusta albo z wildcardami, magic link może trafić na cudzą domenę), limity żądań, captchę dla rejestracji i odzyskiwania, politykę haseł, ochronę przed wyciekłymi hasłami i logowanie anonimowe (wyłączone, chyba że używasz go celowo). Każde to jedno pole; żadne nie jest domyślnie złe, ale AI chętnie je przestawia, żeby „testy przechodziły".

Storage, Realtime i Edge Functions

Publiczne buckety serwują każdy plik każdemu, kto ma URL: do plików użytkowników używaj prywatnych bucketów, polityk na storage.objects i podpisanych URL-i z terminem ważności, plus limitu rozmiaru i dozwolonych typów per bucket. W Realtime zmiany w Postgresie respektują RLS, ale kanały broadcast i presence potrzebują własnych polityk, jeśli przenoszą prywatne dane. W Edge Functions sekrety idą przez supabase secrets set, nigdy w kodzie, a jeśli wyłączasz weryfikację JWT dla webhooka, sam zweryfikuj podpis nadawcy.

Sam projekt

Bezpośredni connection string do Postgresa (z hasłem bazy) nie może nigdy opuścić Twojego serwera ani zmiennych środowiskowych. Włącz wymuszanie SSL, rozważ ograniczenia sieciowe w planie płatnym i co jakiś czas zaglądaj do Security Advisor w panelu (Database, Advisors): wykrywa tabele bez RLS, widoki security definer, funkcje ze zmiennym search_path i wystawione tabele auth.

Część 2: co AI robi źle z Supabase

To błędy, które najczęściej powtarzają się w aplikacjach generowanych w Lovable, Bolt lub Cursorze, według częstości:

  • Tworzenie tabel bez RLS. Generator pisze migrację, aplikacja działa i nikt niczego nie włącza. To źródło CVE-2025-48757 w Lovable, która ujawniła dane setek projektów.
  • Polityki using (true). Gdy RLS coś blokuje, AI „naprawia" to polityką, która przepuszcza wszystko. To RLS wyłączone pod inną nazwą.
  • service_role w przeglądarce. Zapytanie pada na uprawnieniach i szybkie rozwiązanie to zamiana klucza. Poszukaj service_role w swoim bundle'u już teraz.
  • Funkcje security definer do obchodzenia błędów. Ten sam wzorzec: zamiast napisać właściwą politykę, zapytanie opakowuje się w funkcję działającą jako superużytkownik.
  • Publiczne buckety na pliki użytkowników. Awatary, w porządku; faktury, dokumenty czy eksporty, nie.
  • Pola uprawnień edytowalne przez użytkownika. role albo is_admin w tabeli profiles z polityką update, która nie wyklucza tych kolumn. Każdy robi się adminem z konsoli przeglądarki.
  • Wyłączanie potwierdzenia e-maila lub limitów, żeby testy szły szybciej, i zapominanie o tym na produkcji.

Trzy zapytania SQL do audytu projektu

Wklej je do edytora SQL w panelu. Pierwsze wypisuje publiczne tabele bez RLS; najlepiej, żeby nic nie zwróciło:

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;

Drugie znajduje polityki, które przepuszczają wszystko:

select tablename, policyname, cmd, roles
from pg_policies
where schemaname = 'public' and (qual = 'true' or with_check = 'true');

Taka polityka na tabeli z publiczną treścią tylko do odczytu może być poprawna; na profiles, orders czy messages nie. Trzecie wypisuje funkcje działające z uprawnieniami właściciela:

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;

Każdy wynik powinien mieć powód, który potrafisz wyjaśnić. Jeśli go nie ma, usuń security definer albo przenieś funkcję poza public.

Prompt do przeglądu przed wdrożeniem

Wklej go do Lovable, Bolta lub Cursora z dostępem do Twojego schematu i kodu:

Przejrzyj bezpieczeństwo tej aplikacji z Supabase i na razie niczego nie zmieniaj. Odpowiedz listą:
1. Tabele w schemacie public bez włączonego RLS.
2. Polityki z using (true) lub with check (true) i czy są uzasadnione.
3. Kolumny uprawnień (role, is_admin, plan, credits), które użytkownik mógłby zmienić przy obecnych politykach update.
4. Widoki bez security_invoker i funkcje security definer, z powodem dla każdej.
5. Każde użycie klucza service_role lub connection stringa Postgresa poza serwerem.
6. Publiczne buckety zawierające pliki użytkowników.
7. Wyłączone ustawienia Auth (potwierdzenie e-maila, limity, captcha) i redirect URLs z wildcardami.
Dla każdego punktu zaproponuj dokładny SQL lub zmianę i poczekaj na moje potwierdzenie.

Sprawdź wynik z zewnątrz

Ostateczny test to nie panel, tylko to, co API odpowiada obcemu. Z URL-em projektu i kluczem anon (oba są w kodzie przeglądarki, więc atakujący też je ma):

curl -s "https://TWOJ_PROJEKT.supabase.co/rest/v1/profiles?select=*" -H "apikey: TWOJ_KLUCZ_ANON" -H "Authorization: Bearer TWOJ_KLUCZ_ANON"

Jeśli zwraca wiersze, ta tabela jest otwarta. Jeśli zwraca [] albo błąd uprawnień, RLS działa. Powtórz dla każdej wrażliwej tabeli i ze zwykłym zalogowanym użytkownikiem, żeby potwierdzić, że widzi tylko swoje dane. Scanaris robi tę kontrolę za Ciebie: wykrywa URL i klucz Supabase w Twoim bundle'u, odpytuje typowe tabele bez sesji, szuka klucza service_role i connection stringa w JavaScripcie, sprawdza, gdzie Twoja aplikacja trzyma token sesji, i daje fix prompt dla każdego znaleziska. Jeśli Twoją aplikację wygenerował Lovable, zobacz też przewodnik bezpieczeństwa dla Lovable; jeśli problemem są klucze w kliencie, sekrety, które Twoja aplikacja wycieka w przeglądarce.

Często zadawane pytania

Czy to problem, że klucz anon jest w przeglądarce?

Nie. Jest do tego zaprojektowany i tylko identyfikuje projekt. Dane chroni RLS. Tym, który nigdy nie może być w przeglądarce, jest service_role (albo nowy klucz secret).

Czy nowe klucze publishable i secret coś zmieniają?

Zmieniają format i pozwalają je rotować bez dotykania JWT, ale model jest ten sam: publishable jest publiczny i przechodzi przez RLS; secret go omija i żyje na serwerze. Stare klucze działają dalej, dopóki ich nie wyłączysz.

Jeśli moją aplikację zrobił Lovable albo Bolt, czy i tak muszę wejść do panelu Supabase?

Tak. Narzędzie generuje schemat, ale RLS, polityki, buckety i ustawienia Auth są Twoje. Dziesięć minut z trzema zapytaniami powyżej i Security Advisor powiedzą Ci, w jakim stanie jest projekt.

Powiązane testy

Czy twoja aplikacja Supabase przechodzi te testy?

Przeskanuj ją za darmo i otrzymaj prompt naprawczy dla każdego znalezionego problemu.

Przeskanuj moją stronę za darmo