„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 rolianon, 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_rolew 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.
rolealbois_adminw tabeliprofilesz 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.