← Powrót do bloga
BezpieczeństwoScanaris Team · 6 min czytania · October 03, 2026

Bezpieczeństwo w Supabase: co sprawdzić przed uruchomieniem

Supabase daje Ci bazę danych i API w kilka minut, i właśnie w tym tkwi niebezpieczeństwo: API jest wystawione na świat od pierwszej chwili. Jeśli nie skonfigurujesz zabezpieczeń, każdy, kto ma Twój klucz publiczny, może czytać i zapisywać Twoje tabele. Ten poradnik przechodzi, w kolejności, w jakiej warto to robić, przez wszystko, co trzeba sprawdzić przed otwarciem aplikacji na świat: RLS i polityki z przykładowym SQL, który klucz gdzie trafia, Storage, widoki, ustawienia Auth i jak na końcu upewnić się, że naprawdę wszystko jest zamknięte.

Jak Supabase wystawia Twoje dane (i dlaczego to dobrze)

Przed Twoim Postgresem stoi warstwa (PostgREST), która zamienia każdą tabelę w schemacie public w endpoint HTTP. Twoja aplikacja w przeglądarce rozmawia z nią bezpośrednio, kluczem publicznym. To oszczędza Ci pisania backendu, ale oznacza, że po drodze nie ma żadnego Twojego serwera, który sprawdzałby uprawnienia: polityki Row Level Security są Twoim backendem. Jeśli są poprawne, projekt jest solidny; jeśli ich brakuje, cała tabela jest o jeden URL od wycieku.

1. RLS włączone na WSZYSTKICH tabelach

Row Level Security to główna bariera Supabase. Bez niej klucz publiczny może odpytywać całe tabele prosto z przeglądarki. Klasyczny błąd vibe codera: szybko utworzyć nową tabelę, albo pozwolić AI ją utworzyć, i zapomnieć włączyć RLS. Dokładnie to było przyczyną luki w Lovable (CVE-2025-48757).

Sprawdź tabela po tabeli w Table Editorze: te z oznaczeniem „RLS disabled" są otwarte dla wszystkich. Żeby nie polegać na oku, to zapytanie w SQL Editorze wypisze brakujące:

select c.relname
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'r' and not c.relrowsecurity;

I dla każdej z nich:

alter table public.nazwa_tabeli enable row level security;

Samo włączenie nie wystarczy: tabela z aktywnym RLS, ale bez polityk odmawia wszystkiego (Twoja aplikacja przestanie działać, dopóki ich nie napiszesz), a tabela ze zbyt liberalną polityką nie chroni niczego.

2. Polityki, które naprawdę odzwierciedlają, kto co może

Polityka using (true) dla wszystkich zostawia tabelę otwartą nawet z włączonym RLS; asystenci AI często ją podpowiadają, „żeby działało". Pisz konkretne polityki i rozdzielaj reguły według operacji, bo czasem chcesz pozwolić na odczyt, ale nie na usuwanie.

Wzorzec A: każdy użytkownik swoje wiersze

create policy "notes: czytaj własne" on public.notes
  for select to authenticated
  using ((select auth.uid()) = user_id);

create policy "notes: twórz własne" on public.notes
  for insert to authenticated
  with check ((select auth.uid()) = user_id);

create policy "notes: edytuj własne" on public.notes
  for update to authenticated
  using ((select auth.uid()) = user_id)
  with check ((select auth.uid()) = user_id);

with check to to, co uniemożliwia komuś wstawienie lub edycję wiersza w imieniu innego użytkownika. Opakowanie auth.uid() w (select …) sprawia, że Postgres oblicza je raz na zapytanie, a nie raz na wiersz; na dużych tabelach różnica jest ogromna. Załóż indeks na user_id.

Wzorzec B: przynależność do zespołu lub organizacji

create policy "projects: członkowie zespołu" on public.projects
  for select to authenticated
  using (
    exists (
      select 1 from public.team_members m
      where m.team_id = projects.team_id
        and m.user_id = (select auth.uid())
    )
  );

Tabela team_members też potrzebuje swoich polityk, a najlepiej, żeby wstawiać do niej mógł tylko administrator.

Wzorzec C: treść publiczna tylko do odczytu

create policy "posts: odczyt publiczny" on public.posts
  for select to anon, authenticated
  using (published = true);

To uzasadnione. Nie może natomiast istnieć otwarta polityka zapisu ani kolumny z danymi osobowymi w publicznie czytelnej tabeli.

3. anon, service_role i nowe klucze

Klucz anon jest publiczny i trafia do frontendu: jest pomyślany tak, by kontrolowało go RLS. Klucz service_role całkowicie omija RLS i ma pełny dostęp. Nigdy, przenigdy nie może pojawić się w przeglądarce ani w zmiennej z prefiksem NEXT_PUBLIC_, VITE_ czy NUXT_PUBLIC_: ten prefiks znaczy „to jest wysyłane do klienta".

Supabase wprowadził też nową parę kluczy o nazwach, które nie pozostawiają wątpliwości: sb_publishable_… (do przeglądarki, podlega RLS) i sb_secret_… (tylko serwer, omija RLS, odwoływalny osobno). Jeśli zaczynasz projekt dzisiaj, użyj ich; jeśli masz starszy, zmigruj, kiedy możesz. Klucza tajnego używaj tylko po stronie serwera, w Edge Functions lub zaufanych backendach, a jeśli podejrzewasz wyciek, od razu zrotuj go w panelu.

4. Storage: buckety też mają reguły

Przechowywanie plików korzysta z własnych polityk, na tabeli storage.objects. Publiczny bucket serwuje każdy plik przez URL każdemu, kto go ma, na zawsze. Zdecyduj świadomie, które buckety są publiczne (awatary, zdjęcia produktów), a resztę chroń. Przykład, żeby każdy użytkownik czytał i wgrywał tylko do własnego folderu:

create policy "documents: własny folder" on storage.objects
  for all to authenticated
  using (
    bucket_id = 'documents'
    and (storage.foldername(name))[1] = (select auth.uid())::text
  )
  with check (
    bucket_id = 'documents'
    and (storage.foldername(name))[1] = (select auth.uid())::text
  );

Dokumenty prywatne wgrywaj do prywatnych bucketów i serwuj je przez krótkotrwałe podpisane URL-e, a nie do publicznego bucketu z trudnymi do zgadnięcia nazwami.

5. Widoki, funkcje i wystawione schematy

Trzy miejsca, którymi dane uciekają, nawet gdy tabele są w porządku:

  • Widoki. W Postgresie widok działa z uprawnieniami tego, kto go utworzył, więc widok na chronionej tabeli może zwrócić wiersze, których polityki by zabroniły. Twórz je z with (security_invoker = true) (Postgres 15 lub nowszy) albo sprawdzaj każdy z osobna.
  • Funkcje RPC. Funkcja security definer działa z uprawnieniami właściciela. Używaj jej tylko wtedy, gdy naprawdę musisz, waliduj parametry w środku i ogranicz, kto może ją wywołać, przez revoke execute … from anon.
  • Wystawione schematy. W Settings → API wybierasz, które schematy publikuje API. To, co nie ma wychodzić przez HTTP, nie powinno być na tej liście.

6. Ustawienia Auth, które warto sprawdzić

  • Potwierdzenie e-maila włączone, żeby nikt nie rejestrował kont na cudze adresy.
  • Limity prób logowania i wysyłki e-maili: są domyślnie aktywne; upewnij się, że nikt ich nie podniósł „do testów".
  • Adresy przekierowań dozwolone tylko do Twoich domen; otwarta lista pozwala wykorzystać Twoje logowanie do przekierowań na strony phishingowe.
  • Dostawcy OAuth: tylko ci, których używasz, z ekranem zgody pod nazwą Twojej marki.
  • Ważność magic linków i OTP krótka; wartość domyślna jest rozsądna, nie wydłużaj jej.

7. Sekrety i klucze poza frontendem

Oprócz tajnego klucza Supabase sprawdź, czy w JavaScripcie klienta nie ma kluczy firm trzecich (Stripe, e-mail, AI). AI czasem je tam wkłada, żeby coś szybko zadziałało, i zostają widoczne na zawsze w bundle'u. Wywołania płatnych usług idą przez Edge Function albo Twój serwer, nigdy z przeglądarki. Zobacz sekrety, które Twoja aplikacja wycieka w przeglądarce.

8. Środowiska: development to nie produkcja

Używaj osobnego projektu Supabase do developmentu, z wymyślonymi danymi. Projekt testowy z kopią prawdziwych danych i politykami zrobionymi na pół jest tak samo wystawiony jak produkcja, a nikt go nie pilnuje.

Jak sprawdzić, że jest zamknięte (test końcowy)

  • Test incognito. Przy otwartej aplikacji skopiuj z F12 → Network wywołanie do /rest/v1/ i otwórz je bez sesji. Musi zwrócić pustą listę albo błąd.
  • Test „proszenia o więcej". Zalogowany, usuń filtr z URL-a (?user_id=eq.…). Mogą wrócić tylko Twoje wiersze.
  • Test dwóch kont. Utwórz dwóch użytkowników i potwierdź, że żaden nie widzi, nie edytuje ani nie usuwa danych drugiego, łącznie z plikami w Storage.
  • Security Advisor. W panelu sekcja Advisors wypisuje tabele bez RLS, liberalne polityki i wystawione funkcje. Utrzymuj ją na zielono.

Scanaris sprawdza Twój Supabase za Ciebie

Scanaris zawiera opcjonalny audyt Supabase, który przy connection stringu tylko do odczytu sprawdza tabela po tabeli, czy RLS jest włączone i czy istnieją polityki, a w zwykłym skanie wykrywa klucze wystawione we frontendzie, liberalny CORS i endpointy serwujące dane bez sesji. Każde znalezisko ma prompt gotowy do wklejenia w Twoje AI. Uruchom go przed startem, a dowiesz się, czy zostawiłeś otwarte drzwi; włącz monitoring, a ostrzeże Cię, jeśli AI znów je otworzy w nowej tabeli.

Najczęściej zadawane pytania

Czy mogę pominąć RLS, stawiając przed bazą własny backend?

Możesz, używając z serwera wyłącznie klucza tajnego i nigdy publicznego po stronie klienta. Ale wtedy Twój backend musi wykonać każdą kontrolę uprawnień, którą zrobiłoby RLS. Dla większości aplikacji RLS to mniej pracy i trudniej je złamać.

Czy RLS obniża wydajność?

Ledwo, jeśli polityki są dobrze napisane: (select auth.uid()) zamiast auth.uid(), indeksy na kolumnach używanych przez polityki i brak ciężkich podzapytań. Źle napisana polityka faktycznie może spowolnić dużą tabelę.

A tabele, które AI tworzy po starcie?

Rodzą się bez RLS. Dodaj „włącz RLS i napisz polityki" do swojej rutyny (albo do promptu dla AI) za każdym razem, gdy pojawia się nowa tabela, i używaj automatycznego ponownego skanu, żeby dowiedzieć się, gdy ktoś zapomni.

Jak radzi sobie twoja strona?

Pozwól Scanaris ją sprawdzić w 30 sekund, za darmo.

Przeskanuj moją stronę

Powiązane testy

Przewodniki bezpieczeństwa dla twojego stacku