RLS w PostgreSQL dla SaaS pomaga wymusić izolację tenantów w bazie danych. Dowiedz się, kiedy go używać, jak pisać polityki i czego unikać.

W aplikacji SaaS najgroźniejszy błąd bezpieczeństwa pojawia się często dopiero po skalowaniu. Zaczynasz od prostej zasady: „użytkownicy widzą tylko dane swojego tenanta”, a potem szybko dodajesz nowy endpoint, zapytanie do raportu albo join, który cicho omija ten warunek.
Autoryzacja realizowana wyłącznie w aplikacji psuje się pod presją, bo reguły rozpraszają się po kodzie. Jeden kontroler sprawdza tenant_id, inny — członkostwo, zadanie background zapomina, a ścieżka „eksport admina” pozostaje „tymczasowa” przez miesiące. Nawet uważne zespoły gdzieś przegapią fragment.
Row-level security (RLS) w PostgreSQL rozwiązuje konkretny problem: wymusza w bazie, które wiersze są widoczne dla danego żądania. Model mentalny jest prosty: każde SELECT, UPDATE i DELETE jest automatycznie filtrowane przez polityki, podobnie jak każde żądanie jest filtrowane przez middleware uwierzytelniające.
Zwróć uwagę na „wiersze”. RLS nie chroni wszystkiego:
Konkretny przykład: dodajesz endpoint, który listuje projekty z joinem do faktur na pulpicie nawigacyjnym. Przy autoryzacji tylko po stronie aplikacji łatwo jest przefiltrować projects po tenant_id, ale zapomnieć o invoices, albo dołączyć po kluczu, który krzyżuje tenantów. Z RLS obie tabele mogą wymuszać izolację tenantów, więc zapytanie zawiedzie bezpiecznie zamiast wyciekać dane.
Koszt jest realny. Piszesz mniej powtarzalnego kodu autoryzacji i zmniejszasz liczbę miejsc, które mogą wyciec. Ale bierzesz na siebie nową pracę: musisz zaprojektować polityki ostrożnie, testować je wcześnie i zaakceptować, że polityka może zablokować zapytanie, którego się spodziewałeś.
RLS może wydawać się dodatkową pracą, dopóki aplikacja nie przekroczy kilku endpointów. Jeśli masz wyraźne granice tenantów i wiele ścieżek zapytań (ekrany list, wyszukiwanie, eksporty, narzędzia administracyjne), umieszczenie reguły w bazie oznacza, że nie musisz pamiętać, by dodać ten sam filtr wszędzie.
RLS pasuje najbardziej, gdy reguła jest nudna i uniwersalna: „użytkownik widzi tylko wiersze swojego tenanta” albo „użytkownik widzi tylko projekty, których jest członkiem”. W takich przypadkach polityki redukują błędy, ponieważ każde SELECT, UPDATE i DELETE przechodzi przez tę samą bramkę, nawet gdy zapytanie zostanie dodane później.
Pomaga też w aplikacjach nastawionych na odczyt, gdzie logika filtrowania jest spójna. Jeśli API ma 15 różnych sposobów ładowania faktur (po statusie, dacie, kliencie, wyszukiwaniu), RLS pozwala przestać ponownie implementować filtrowanie tenantów przy każdym zapytaniu i skupić się na funkcji.
RLS dokłada bólu, gdy reguły nie dotyczą wiersza. Reguły na poziomie pola, jak „możesz zobaczyć pensję, ale nie premię” albo „zamaskuj tę kolumnę, chyba że jesteś z HR”, często zamieniają się w niezgrabny SQL i ciężkie do utrzymania wyjątki.
To też słabe dopasowanie do ciężkiego raportowania, które naprawdę potrzebuje szerokiego dostępu. Zespoły często tworzą role z pominięciem ograniczeń dla „tego jednego zadania” i tam zbierają się błędy.
Zanim się zaangażujesz, zdecyduj, czy chcesz, by baza była ostateczną bramką. Jeśli tak — zaplanuj dyscyplinę: testuj zachowanie bazy (nie tylko odpowiedzi API), traktuj migracje jak zmiany bezpieczeństwa, unikaj szybkich obejść, ustal jak zadania background się uwierzytelniają i utrzymuj polityki małe i powtarzalne.
Jeśli używasz narzędzi generujących backendy, mogą one przyspieszyć dostawę, ale nie usuwają potrzeby wyraźnych ról, testów i prostego modelu tenantów. (Na przykład Koder.ai używa Go i PostgreSQL do generowanych backendów — nadal warto zaprojektować RLS świadomie zamiast „dorzucać później”.)
RLS jest najłatwiejsze, gdy schemat już jasno określa, kto co posiada. Jeśli zaczynasz od mglistych relacji i próbujesz to „poprawić politykami”, zwykle dostaniesz wolne zapytania i mylące błędy.
Wybierz jeden klucz tenanta (np. org_id) i używaj go konsekwentnie. Większość tabel należących do tenanta powinna go mieć, nawet jeśli odnoszą się do innej tabeli, która go też posiada. To unika joinów wewnątrz polityk i utrzymuje proste warunki USING.
Praktyczna reguła: jeśli wiersz powinien zniknąć, gdy klient anuluje konto, prawdopodobnie potrzebuje org_id.
Polityki RLS zazwyczaj odpowiadają na jedno pytanie: „Czy ten użytkownik jest członkiem tej organizacji i co może robić?” To trudno wywnioskować z doraźnych kolumn.
Utrzymuj podstawowe tabele małe i proste:
users (po jednym wierszu na osobę)orgs (po jednym wierszu na tenanta)org_memberships (user_id, org_id, role, status)project_memberships dla dostępu per-projektDzięki temu polityki mogą sprawdzić członkostwo jednym indeksowanym zapytaniem.
Nie wszystko musi mieć org_id. Tabele referencyjne, takie jak kraje, kategorie produktów czy typy planów, często są współdzielone między tenantami. Uczyń je w większości ról tylko do odczytu i nie wiąż ich z konkretnym orgiem.
Dane należące do tenantów (projekty, faktury, zgłoszenia) powinny unikać wprowadzania szczegółów tenantowych przez tabele współdzielone. Trzymaj tabele współdzielone minimalne i stabilne.
Klucze obce dalej działają z RLS, ale usuwanie może zaskakiwać, jeśli rola usuwająca nie „widzi” zależnych wierszy. Planuj kaskady ostrożnie i testuj rzeczywiste przepływy usuwania.
Zindeksuj kolumny, według których polityki filtrują, zwłaszcza org_id i klucze członkostw. Polityka wyglądająca jak WHERE org_id = ... nie powinna zamienić się w skan całej tabeli przy milionach wierszy.
RLS to przełącznik na poziomie tabeli. Po włączeniu PostgreSQL przestaje ufać kodowi aplikacji w kwestii filtru tenantów. Każde SELECT, UPDATE i DELETE jest filtrowane przez polityki, a każde INSERT i UPDATE jest weryfikowane przez polityki.
Największa zmiana mentalna: z włączonym RLS zapytania, które kiedyś zwracały dane, mogą zacząć zwracać zero wierszy bez błędów. To PostgreSQL wykonuje kontrolę dostępu.
Polityki to małe reguły przypięte do tabeli. Używają dwóch rodzajów warunków:
USING to filtr do odczytu. Jeśli wiersz nie pasuje do USING, jest niewidoczny dla SELECT i nie może być celem UPDATE lub DELETE.WITH CHECK to bramka zapisu. Decyduje, jakie nowe lub zmienione wiersze są dozwolone przy INSERT lub UPDATE.Typowy wzorzec w SaaS: USING zapewnia, że widzisz tylko wiersze swojego tenanta, a WITH CHECK zapewnia, że nie możesz dodać wiersza do cudzego tenanta przez odgadnięcie identyfikatora tenanta.
Gdy dodajesz kolejne polityki, to ma znaczenie:
PERMISSIVE (domyślnie): wiersz jest dozwolony, jeśli którakolwiek polityka go dopuszcza.RESTRICTIVE: wiersz jest dozwolony tylko wtedy, gdy wszystkie polityki o charakterze restrictive go dopuszczają (na dodatek do zachowania permissive).Jeśli planujesz nakładać reguły jak dopasowanie tenant + sprawdzenie roli + członkostwo w projekcie, polityki restrictive mogą uczynić intencję jaśniejszą, ale też łatwiej zablokować dostęp, jeśli zapomnisz jednego warunku.
RLS potrzebuje wiarygodnej wartości „kto wywołuje”. Typowe opcje:
app.user_id i app.tenant_id).SET ROLE ...) per żądanie, co może działać, ale dodaje obciążenie operacyjne.Wybierz jedno podejście i stosuj je wszędzie. Mieszanie źródeł tożsamości między serwisami to szybka droga do mylących błędów.
Użyj przewidywalnej konwencji, żeby zrzuty schematu i logi były czytelne. Na przykład: {table}__{action}__{rule}, jak projects__select__tenant_match.
Jeśli dopiero zaczynasz z RLS, zacznij od jednej tabeli i małego dowodu. Celem nie jest pełne pokrycie od razu. Celem jest sprawić, by baza odmawiała dostępu między tenantami nawet, gdy aplikacja ma błąd.
Załóżmy prostą tabelę projects. Najpierw dodaj tenant_id w sposób, który nie złamie zapisu.
ALTER TABLE projects ADD COLUMN tenant_id uuid;
-- Backfill existing rows (example: everyone belongs to a default tenant)
UPDATE projects SET tenant_id = '11111111-1111-1111-1111-111111111111'::uuid
WHERE tenant_id IS NULL;
ALTER TABLE projects ALTER COLUMN tenant_id SET NOT NULL;
Następnie oddziel własność od dostępu. Często jedna rola jest właścicielem tabel (app_owner), a inna jest używana przez API (app_user). Rola API nie powinna być właścicielem tabel, bo wtedy może obejść polityki.
ALTER TABLE projects OWNER TO app_owner;
REVOKE ALL ON projects FROM PUBLIC;
GRANT SELECT, INSERT, UPDATE, DELETE ON projects TO app_user;
Teraz zdecyduj, jak żądanie powie Postgresowi, którego tenanta obsługuje. Jednym prostym podejściem jest ustawienie wartości ograniczonej do żądania (request-scoped setting). Aplikacja ustawia ją zaraz po otwarciu transakcji.
-- inside the same transaction as the request
SELECT set_config('app.current_tenant', '22222222-2222-2222-2222-222222222222', true);
Włącz RLS i zacznij od dostępu do odczytu.
ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
CREATE POLICY projects_tenant_select
ON projects
FOR SELECT
TO app_user
USING (tenant_id = current_setting('app.current_tenant')::uuid);
Udowodnij, że działa, próbując dwóch różnych tenantów i sprawdzając, że liczba wierszy się zmienia.
Polityki odczytu nie chronią zapisów. Dodaj WITH CHECK, aby insert i update nie mogły przemycić wierszy do niewłaściwego tenanta.
CREATE POLICY projects_tenant_write
ON projects
FOR INSERT, UPDATE
TO app_user
WITH CHECK (tenant_id = current_setting('app.current_tenant')::uuid);
Szybki sposób weryfikacji zachowania (w tym porażek) to trzymanie małego skryptu SQL, który możesz uruchomić po każdej migracji:
BEGIN; SET LOCAL ROLE app_user;SELECT set_config('app.current_tenant', '\u003ctenant A\u003e', true); SELECT count(*) FROM projects;INSERT INTO projects(id, tenant_id, name) VALUES (gen_random_uuid(), '\u003ctenant B\u003e', 'bad'); (powinno się nie udać)UPDATE projects SET tenant_id = '\u003ctenant B\u003e' WHERE ...; (powinno się nie udać)ROLLBACK;Jeśli ten skrypt daje za każdym razem takie same wyniki, masz wiarygodną bazę testową przed rozszerzeniem RLS na inne tabele.
Większość zespołów sięga po RLS, gdy ma dość powtarzania tych samych sprawdzeń autoryzacji w każdym zapytaniu. Dobra wiadomość: kształty polityk, których potrzebujesz, zazwyczaj są spójne.
Niektóre tabele są naturalnie własnością jednego użytkownika (notatki, tokeny API). Inne należą do tenanta i dostęp zależy od członkostwa. Traktuj te przypadki jako różne wzorce.
Dla danych należących do właściciela polityki często sprawdzają created_by = app_user_id(). Dla danych tenantowych polityki często sprawdzają, czy użytkownik ma wiersz członkostwa dla organizacji.
Praktyczny sposób utrzymania czytelności polityk to centralizacja tożsamości w małych helperach SQL i ich ponowne użycie:
-- Example helpers
create function app_user_id() returns uuid
language sql stable as $$
select current_setting('app.user_id', true)::uuid
$$;
create function app_is_admin() returns boolean
language sql stable as $$
select current_setting('app.is_admin', true) = 'true'
$$;
Odczyty są często szersze niż zapisy. Na przykład każdy członek organizacji może SELECT projekty, lecz tylko edytorzy mogą UPDATE, a tylko właściciele mogą DELETE.
Utrzymuj to jawnie: jedna polityka dla SELECT (członkostwo), jedna dla INSERT/UPDATE z WITH CHECK (rola) i jedna dla DELETE (często surowsza niż update).
Unikaj „wyłącz RLS dla adminów”. Zamiast tego dodaj wyjście awaryjne w politykach, np. app_is_admin(), żeby nie przyznawać przypadkowo pełnego dostępu współdzielonej roli serwisowej.
Jeśli używasz deleted_at lub status, uwzględnij to w polityce SELECT (deleted_at is null). W przeciwnym razie ktoś może „wskrzeszać” wiersze, których aplikacja uznała za ostateczne.
WITH CHECKINSERT ... ON CONFLICT DO UPDATE musi spełnić WITH CHECK dla stanu wiersza po zapisie. Jeśli twoja polityka wymaga created_by = app_user_id(), upewnij się, że upsert ustawia created_by przy wstawieniu i nie nadpisuje go przy aktualizacji.
Jeśli generujesz kod backendu, warto te wzory przekształcić w wewnętrzne szablony, aby nowe tabele zaczynały z bezpiecznymi domyślnymi ustawieniami, zamiast pustej karty.
RLS jest świetne, dopóki jeden szczegół nie sprawi, że PostgreSQL zaczyna „losowo” ukrywać lub pokazywać dane. Poniższe błędy zabierają najwięcej czasu.
Pierwsza pułapka to brak WITH CHECK przy insertach i update'ach. USING kontroluje, co widzisz, a nie co możesz stworzyć. Bez WITH CHECK błąd aplikacji może zapisać wiersz do niewłaściwego tenanta, a możesz tego nie zauważyć, bo ten sam użytkownik nie może go odczytać.
Inny częsty wyciek to „leaky join”. Prawidłowo filtrujesz projects, a potem łączysz z invoices, notes czy files, które nie są chronione tak samo. Naprawa jest surowa, ale prosta: każda tabela, która może ujawnić dane tenantów, potrzebuje własnej polityki, a widoki nie powinny polegać na tym, że tylko jedna tabela jest bezpieczna.
Typowe wzorce błędów pojawiają się wcześnie:
WITH CHECK dla zapisu.Polityki, które referują tę samą tabelę (bezpośrednio lub przez widok), mogą stworzyć niespodzianki rekurencyjne. Polityka może sprawdzać członkostwo przez zapytanie do widoku, który znowu czyta chronioną tabelę — prowadząc do błędów, wolnych zapytań lub polityki, która nigdy nie pasuje.
Konfiguracja ról to kolejne źródło nieporozumień. Właściciele tabel i role z podwyższonymi uprawnieniami mogą omijać RLS, więc twoje testy przechodzą, podczas gdy prawdziwi użytkownicy zawodzą (albo odwrotnie). Zawsze testuj z tą samą nisko-uprawnioną rolą, której używa twoja aplikacja.
Bądź ostrożny z funkcjami SECURITY DEFINER. Uruchamiają się z uprawnieniami właściciela funkcji, więc helper taki jak current_tenant_id() może być w porządku, ale „wygodna” funkcja, która czyta dane, może przypadkowo odczytać między tenantami, jeśli nie zaprojektujesz jej tak, by respektowała RLS.
Ustaw też bezpieczny search_path wewnątrz funkcji security definer. Jeśli tego nie zrobisz, funkcja może znaleźć inny obiekt o tej samej nazwie, a logika polityki może cicho wskazywać na niewłaściwy obiekt w zależności od stanu sesji.
Błędy RLS zwykle wynikają z brakującego kontekstu, a nie z „złego SQL”. Polityka może być poprawna na papierze, a i tak zawodzić, bo rola sesji jest inna niż myślisz albo dlatego, że żądanie w ogóle nie ustawiło wartości tenanta i usera, na których opiera się polityka.
Niezawodny sposób odtworzenia raportu produkcyjnego to odzwierciedlenie tego samego ustawienia sesji lokalnie i uruchomienie tego samego zapytania. Zazwyczaj oznacza to:
SET ROLE app_user; (lub prawdziwa rola API)SELECT set_config('app.tenant_id', 't_123', true); i SELECT set_config('app.user_id', 'u_456', true);SELECT current_user, current_setting('app.tenant_id', true), current_setting('app.user_id', true);Gdy nie jesteś pewien, która polityka jest stosowana, sprawdź katalog zamiast zgadywać. pg_policies pokazuje każdą politykę, komendę i wyrażenia USING oraz WITH CHECK. Połącz to z pg_class, by potwierdzić, że RLS jest włączone na tabeli i nie jest omijane.
Problemy z wydajnością mogą wyglądać jak problemy z autoryzacją. Polityka, która robi join do tabeli członkostw lub wywołuje funkcję, może być poprawna, ale wolna po wzroście danych. Użyj EXPLAIN (ANALYZE, BUFFERS) na odtworzonym zapytaniu i szukaj skanów sekwencyjnych, nieoczekiwanych nested loopów lub filtrów stosowanych późno. Brakujące indeksy na (tenant_id, user_id) i tabelach członkostw są częstą przyczyną.
Przydatne jest też logowanie trzech wartości na żądanie po stronie aplikacji: tenant ID, user ID i rola bazy używana dla żądania. Gdy te nie zgadzają się z tym, co myślisz, RLS będzie działać „źle”, bo wejścia są złe.
Dla testów utrzymuj kilka zainicjowanych tenantów i czynność, która robi porażki jawne. Mały zestaw testów zwykle zawiera: „Tenant A nie może czytać Tenant B”, „użytkownik bez członkostwa nie widzi projektu”, „właściciel może aktualizować, viewer nie może”, „insert zablokowany, jeśli tenant_id nie pasuje do kontekstu” oraz „nadpisanie administratora tylko tam, gdzie zamierzone”.
Traktuj RLS jak pas bezpieczeństwa, a nie przełącznik funkcji. Małe przeoczenia prowadzą do „wszyscy widzą dane wszystkich” albo „wszystko zwraca zero wierszy”.
Upewnij się, że projekt tabel i reguły polityk odpowiadają twojemu modelowi tenantów.
tenant_id). Jeśli go nie ma, zapisz dlaczego (np. globalne tabele referencyjne).FORCE ROW LEVEL SECURITY na tych tabelach.USING. Zapisy muszą mieć WITH CHECK, by insert/updates nie przenosiły wierszy do innego tenanta.tenant_id lub robią join przez tabele członkostw, dodaj odpowiednie indeksy.Prosty test sanitarności: użytkownik tenant A może czytać własne faktury, może wstawić fakturę tylko dla tenant A i nie może zaktualizować faktury tak, by zmienić tenant_id.
RLS jest tak silne, jak role, których używa twoja aplikacja.
bypassrls.Wyobraź sobie aplikację B2B, gdzie firmy (orgs) mają projekty, a projekty mają zadania. Użytkownicy mogą należeć do wielu orgów, a użytkownik może być członkiem niektórych projektów, ale nie innych. To dobre dopasowanie dla RLS, bo baza może egzekwować izolację tenantów nawet, gdy endpoint API zapomni filtra.
Prosty model: orgs, users, org_memberships (org_id, user_id, role), projects (id, org_id), project_memberships (project_id, user_id), tasks (id, project_id, org_id, ...). To org_id w tasks jest celowe. Upraszcza polityki i zmniejsza niespodzianki przy joinach.
Klasyczny wyciek: tasks mają tylko project_id, a twoja polityka sprawdza dostęp przez join do projects. Jeden błąd (permissive policy na projects, join, który usuwa warunek albo widok, który zmienia kontekst) może wystawić zadania z innego orga.
Bezpieczniejsza ścieżka migracji unikająca przerwania ruchu produkcyjnego:
org_id do tasks, dodaj tabele członkostw).tasks.org_id z projects.org_id, potem dodaj NOT NULL.FORCE ROW LEVEL SECURITY, i dopiero wtedy usuń stare filtry po stronie aplikacji.Dostęp supportowy najlepiej obsłużyć wąską rolą „break-glass”, a nie przez wyłączanie RLS. Trzymaj ją oddzielnie od normalnych kont wsparcia i zapisuj, kiedy jest używana.
Dokumentuj reguły, żeby polityki nie dryfowały: które zmienne sesji muszą być ustawione (user_id, org_id), które tabele muszą mieć org_id, co znaczy „member” i kilka przykładów SQL, które powinny zwracać 0 wierszy uruchomione jako zły org.
RLS jest łatwiejsze w eksploatacji, gdy traktujesz je jak zmianę produktu. Wdrażaj małymi krokami, udowodnij zachowanie testami i prowadź jasny rejestr, po co każda polityka istnieje.
Plan wdrożenia, który zwykle działa:
projects) i ją zabezpiecz.Po ustabilizowaniu pierwszej tabeli zmiany polityk czynią świadomie. Dodaj krok przeglądu polityk do migracji i dołącz krótką notkę o intencji (kto ma mieć dostęp do czego i dlaczego) oraz odpowiadający test. To zapobiega „dodaj kolejne OR”, które powoli zamieniłyby polityki w dziurę.
Jeśli działasz szybko, narzędzia takie jak Koder.ai (koder.ai) mogą pomóc wygenerować punkt wyjścia Go + PostgreSQL przez rozmowę, a potem możesz dokładnie dodać polityki RLS i testy z tą samą dyscypliną, jak w ręcznie pisanym backendzie.
Na koniec — utrzymuj zabezpieczenia podczas rollout’u. Rób snapshoty przed migracjami polityk, ćwicz rollback tak długo, aż będzie nudny, i miej małą ścieżkę break-glass dla wsparcia, która nie wyłącza RLS w całym systemie.
RLS sprawia, że PostgreSQL egzekwuje, które wiersze są widoczne lub zapisywalne dla danego żądania, dzięki czemu izolacja tenantów nie zależy od tego, czy każdy endpoint pamięta o odpowiednim warunku WHERE tenant_id = .... Główna korzyść to zmniejszenie ryzyka „jednego brakującego sprawdzenia”, gdy aplikacja się rozrasta i zapytań przybywa.
Opłaca się, gdy reguły dostępu są spójne i oparte na wierszach — na przykład izolacja tenantów lub dostęp oparty na członkostwie — oraz gdy masz wiele ścieżek zapytań (wyszukiwanie, eksporty, panele administracyjne, zadania background). Zwykle nie ma sensu, gdy większość reguł dotyczy pojedynczych pól, jest pełna wyjątków albo dominuje szerokie raportowanie wymagające odczytów między tenantami.
Używaj RLS do widoczności wierszy i podstawowej kontroli zapisu; inne potrzeby zabezpiecza się innymi narzędziami. Prywatność kolumn zwykle wymaga widoków i uprawnień do kolumn, a złożone reguły biznesowe (np. właściciel rozliczeń czy procesy zatwierdzające) nadal należą do logiki aplikacji lub specjalnie zaprojektowanych ograniczeń w bazie.
Utwórz rolę o niskich uprawnieniach dla API (nie właściciela tabel), włącz RLS, a następnie dodaj politykę SELECT oraz politykę INSERT/UPDATE z WITH CHECK. Ustaw wartość sesyjną na żądanie (np. app.current_tenant) i zweryfikuj, że jej zmiana wpływa na to, jakie wiersze można zobaczyć i zapisać.
Często używanym rozwiązaniem są zmienne sesji ustawiane na początku transakcji, np. app.tenant_id i app.user_id. Klucz to spójność: każda ścieżka kodu (żądania web, zadania, skrypty) musi ustawiać te same wartości, których oczekują polityki, inaczej zobaczysz mylące zachowanie typu „zero wierszy”.
USING kontroluje, które istniejące wiersze są widoczne i mogą być celem SELECT, UPDATE i DELETE. WITH CHECK kontroluje, które nowe lub zmienione wiersze są dozwolone przy INSERT i , więc zapobiega „wpisywaniu do innego tenanta”, nawet jeśli aplikacja poda zły .
Jeśli dodasz tylko USING, błędny endpoint może nadal wstawić lub zaktualizować wiersze do niewłaściwego tenanta, a możesz tego nie zauważyć, bo ten sam użytkownik nie może odczytać takiego wiersza. Zawsze paruj reguły odczytu z pasującym WITH CHECK dla zapisów, aby złe dane nie mogły powstać.
Unikaj joinów w politykach przez umieszczanie klucza tenant (np. org_id) bezpośrednio w tabelach należących do tenanta, nawet jeśli odwołują się do innej tabeli, która też go ma. Dodaj wyraźne tabele członkostw (org_memberships, opcjonalnie project_memberships), aby polityki mogły wykonać jedno indeksowane zapytanie zamiast skomplikowanej inferencji.
Najpierw odtwórz ten sam kontekst sesji, którego używa aplikacja: ustaw tę samą rolę i ustawienia sesji, a potem uruchom ten sam SQL. Następnie potwierdź, że RLS jest włączone i przejrzyj pg_policies, aby zobaczyć jakie wyrażenia USING i WITH CHECK są stosowane — RLS najczęściej „zawodzi” z powodu brakującego kontekstu tożsamości, a nie przez „zły SQL”.
Tak — traktuj generowany kod jako punkt wyjścia, a nie system bezpieczeństwa. Jeśli używasz Koder.ai do wygenerowania backendu Go + PostgreSQL, nadal musisz zdefiniować model tenantów, konsekwentnie ustawić tożsamość w sesji oraz dodać polityki i testy tak, aby nowe tabele nie trafiały do produkcji bez właściwej ochrony.
UPDATEtenant_id