TL;DR
Žemiau pateiktos RLS politikos, kurių reikia daugumai Supabase programų. Kiekvieną paleidau per REST API kaip svečias, kaip eilutės savininkas ir kaip kitas prisijungęs vartotojas. Trys rezultatai verti dėmesio prieš ką nors kopijuojant: svetimos eilutės update grąžina 200 su tuščiu sąrašu, o ne klaidą; nekaltai atrodanti insert politika narystės lentelėje leido vartotojui prisijungti prie kitos įmonės ir skaityti jos projektus; o užrašius (select auth.uid()) vietoj auth.uid(), viena užklausa 200 000 eilučių lentelėje pagreitėjo nuo 156 ms iki 7,4 ms.
Esu Denys, full-stack ir AI inžinierius, ir dauguma programų, kurias kuriu ar taisau, veikia ant Supabase. Pavyzdžių, kaip parašyti Supabase RLS policy, yra visur; ką jos daro, kai pasiekia tikra užklausa, parodoma retai. Todėl jas paleidau.
Kaip testavau. Dauguma užklausų ėjo per vietinį Supabase stack (CLI 2.109.1, Postgres 17.6, PostgREST 14.14, Auth 2.192.0) su publishable key ir dviem tikrais prisijungusiais vartotojais, A ir B. Atvejai, kuriems HTTP nereikia, pavyzdžiui, sintaksės klaidos ir laikai, veikė ant paprasto vietinio Postgres 17.10 su Supabase rolėmis anon ir authenticated bei iš naujo sukurta funkcija auth.uid(). Viskas 2026 m. spalio 24 d., su išgalvotomis eilutėmis.
Prieš bet kokią politiką: ar lentelė turi teisę (grant)?
Politika nusprendžia, kurias eilutes rolė mato. Teisė (grant) nusprendžia, ar rolė apskritai pasiekia lentelę, o dabartiniuose Supabase projektuose naujos lentelės Data API teisių automatiškai nebegauna (nuo 2026 m. spalio 30 d. visuose projektuose). Mano stack lentelė su įjungtu RLS ir be teisės atsakė taip:
| Užklausa | Statusas | Turinys |
|---|---|---|
Svečias GET /invoices |
401 |
permission denied for table invoices, su užuomina GRANT SELECT ... TO anon |
Vartotojas A GET /invoices |
403 |
tas pats, su užuomina authenticated |
Taigi pirmas žingsnis kiekvienai lentelei, kurią programa skaito iš naršyklės:
alter table public.todos enable row level security;
grant select, insert, update, delete on public.todos to authenticated;
Suteikite tik tas komandas, kurių reikia naršyklei, ir tik rolei authenticated, nebent lentelę turi skaityti ir svečiai.
Kaip parašyti Supabase RLS policy, kad vartotojai matytų tik savo eilutes?
Viena politika kiekvienai komandai, kiekviena apribota prisijungusiais vartotojais:
create policy "read own" on public.todos
for select to authenticated using ((select auth.uid()) = user_id);
create policy "add own" on public.todos
for insert to authenticated with check ((select auth.uid()) = user_id);
create policy "edit own" on public.todos
for update to authenticated
using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
create policy "delete own" on public.todos
for delete to authenticated using ((select auth.uid()) = user_id);
A turi dvi eilutes, B vieną. Kas grįžo:
| Užklausos autorius | Ką daro | Statusas | Turinys |
|---|---|---|---|
| Svečias | skaityti viską | 401 |
permission denied (anon neturi teisės) |
| A | skaityti viską | 200 |
2 vartotojo A eilutės |
| A | įterpti eilutę, priklausančią B | 403 |
new row violates row-level security policy |
| A | atnaujinti vartotojo B eilutę | 200 |
[] |
| A | ištrinti vartotojo B eilutę | 200 |
[] |
| A | pakeisti savo eilutės savininką į B | 403 |
new row violates row-level security policy |
Du 200 [] įrašai yra tie, kuriuos verta įsiminti. RLS neatmeta update ar delete eilučių, kurių nematote; jis jas išfiltruoja, o nulis pakeistų eilučių yra sėkmė. Jei jūsų programa „nėra klaidos“ laiko „išsaugota“, ji pasakys vartotojui, kad pakeitimas pavyko, nors nieko neįvyko. Paprašykite pakeistų eilučių (.select() supabase-js, Prefer: return=representation per HTTP) ir patikrinkite, ar viena grįžo.
Kodėl update politikai reikia WITH CHECK?
using parenka, kurių esamų eilučių vartotojas gali liesti; with check nusprendžia, kaip tos eilutės gali atrodyti po to. Palyginau tris tos pačios update politikos versijas:
| Update politika | A atiduoda savo eilutę vartotojui B |
|---|---|
using (uid = user_id) with check (uid = user_id) |
403, atmesta |
using (uid = user_id), be with check |
atmesta (Postgres naudoja using kaip patikrą) |
using (uid = user_id) with check (true) |
200, eilutė dabar priklauso B |
Praleisti with check saugiau, nei atrodo, nes Postgres pasitelkia using išraišką. with check (true), kurį kai kurie generatoriai rašo, kad „leistų update“, būtent ir leidžia vartotojui perkelti eilutę į svetimą paskyrą. Parašykite patikrą pilnai.
Kaip padaryti profilius viešus, bet redaguojamus tik savininko?
create policy "public read" on public.profiles
for select to anon, authenticated using (true);
create policy "own edit" on public.profiles
for update to authenticated
using ((select auth.uid()) = id) with check ((select auth.uid()) = id);
Paleidimas ant paprasto Postgres: svečias perskaitė abu profilius, svečio update pakeitė 0 eilučių, o A update profiles set display_name = 'x' be filtro pakeitė lygiai 1 eilutę, paties A. using (true) tinka select duomenims, kurie ir turi būti vieši. Bet kurioje kitoje lentelėje tai tas pats, kas jokio RLS, o politika, parašyta be to sąlygos, galioja ir svečiams: mano svečias perskaitė „privačią“ eilutę per using (true) politiką, kuri neturėjo to.
Kaip dalytis eilutėmis komandos viduje?
Įprasta forma: lentelė memberships ir politika duomenyse, kuri joje ieško vartotojo komandų.
create policy "members read" on public.projects
for select to authenticated
using (org_id in (select org_id from public.memberships
where user_id = (select auth.uid())));
Ta politika tvarkinga. Nutekėjimas buvo šalia. Lentelei memberships daviau politiką, kurią siūlo daug pamokų, „vartotojai gali įterpti savo narystę“:
create policy "join any org" on public.memberships
for insert to authenticated with check (user_id = (select auth.uid()));
Ji tikrina, kam eilutė skirta, o ne kuriai komandai. A, 1 įmonės narys, išsiuntė vieną užklausą:
| A užklausa | Statusas | Turinys |
|---|---|---|
| skaityti projektus | 200 |
A roadmap |
įterpti narystę {user_id: A, org_id: 2} |
201 |
sukurta |
| vėl skaityti projektus | 200 |
A roadmap, B payroll |
Sprendimas: memberships nedaryti jokios insert politikos, kad naršyklė negalėtų į ją rašyti (ant Postgres paleidimo tas pats insert tada buvo atmestas su 42501), o narius pridėti per kvietimų srautą, veikiantį serveryje arba funkcijoje, tikrinančioje kvietimą. Vartotojas, priklausantis dviem įmonėms, yra dar vieni spąstai; apie tai rašiau straipsnyje Supabase RLS: kodėl dviejų org. narys mato svetimus duomenis.
Ką daryti, jei politikai reikia skaityti savo lentelę?
„Nariai mato savo komandos draugus“ reiškia politiką lentelėje memberships, kuri skaito memberships. Postgres ją atmeta vykdant užklausą:
ERROR 42P17: infinite recursion detected in policy for relation "members2"
Paieškos veiksmą perkelkite į funkciją, kuri veikia kaip jos savininkas, schemoje, kurios API neatveria, su fiksuotu search_path:
create schema private;
create function private.user_org_ids() returns setof int
language sql stable security definer set search_path = ''
as $$ select org_id from public.members2 where user_id = (select auth.uid()) $$;
revoke all on function private.user_org_ids() from public;
grant usage on schema private to authenticated;
grant execute on function private.user_org_ids() to authenticated;
create policy "see teammates" on public.members2
for select to authenticated using (org_id in (select private.user_org_ids()));
Po to A matė abu 1 įmonės narius, o B matė tik save. Funkciją laikykite ne public: viską, kas ten, galima iškviesti per API.
Kaip reikalauti MFA jautrioms eilutėms?
Prie įprastos politikos pridėkite ribojančią (restrictive) politiką. Ribojančios politikos jungiamos per and, todėl turi praeiti abi:
create policy "mfa only" on public.payouts as restrictive
for select to authenticated using ((select auth.jwt() ->> 'aal') = 'aal2');
Su tuo pačiu vartotoju aal1 sesija perskaitė 0 išmokų, o aal2 sesija perskaitė 1.
Kurios lentelės turi turėti įjungtą RLS ir nė vienos politikos?
Lentelės, kurias liečia tik jūsų serveris. Įjungtas RLS be politikos viską uždraudžia naršyklės rolėms: ant Postgres paleidimo svečias ir A perskaitė 0 eilučių, o A insert buvo atmestas su 42501. Palikti RLS išjungtą yra priešingybė. Lentelė su išjungtu RLS ir numatytosiomis teisėmis, skaitoma su publishable key ir be prisijungimo:
| Svečio užklausa | Statusas | Turinys |
|---|---|---|
GET /leads |
200 |
abi eilutės |
DELETE /leads?id=gt.0 |
200 |
abi eilutės, ištrintos |
Būtent apie tai ir yra Advisor įspėjimas „RLS disabled in public“.
Ar viena politika gali apimti kelias komandas?
Ne sąrašu. for update, delete yra sintaksės klaida (42601: syntax error at or near ","), nors vis dar pasitaiko paskelbtose pavyzdžių rinkiniuose. for all galioja ir taiko vieną using bei vieną with check kiekvienai komandai, ko retai norite. Insert politika su using taip pat nepavyksta: only WITH CHECK expression allowed for INSERT.
Ar politikos lėtina užklausas?
Gali. Politikos išraiška vykdoma kiekvienai eilutei, kurią svarsto Postgres. Išmatavau select * from big kaip vartotojas A, 200 000 eilučių, priklausančių 1 000 vartotojų, A turėjo 200 (7 paleidimų mediana, Postgres 17.10):
| Politika ir indeksas | Laiko mediana |
|---|---|
auth.uid() = user_id, be indekso |
156,3 ms |
(select auth.uid()) = user_id, be indekso |
7,4 ms |
auth.uid() = user_id, indeksas ties user_id |
0,21 ms |
(select auth.uid()) = user_id, indeksas |
0,05 ms |
Apvyniojus iškvietimą select, Postgres apskaičiuoja reikšmę vieną kartą per užklausą, o ne vieną kartą per eilutę; likusį darbą padaro indeksas. Supabase RLS našumo vadovas nurodo tokio paties dydžio pokytį savo 100 000 eilučių lentelėje (nuo 179 ms iki 9 ms). Daug tinklaraščių tuos skaičius cituoja kaip savo; aukščiau pateikti yra iš mano paleidimo.
Iš kur turi būti imamos rolės?
Iš app_metadata arba savo lentelės, niekada iš user_metadata. Prisijungęs vartotojas gali pakeisti savo user_metadata per supabase.auth.updateUser(), todėl politika, tikrinanti auth.jwt() -> 'user_metadata' ->> 'role', leidžia jam pačiam suteikti sau rolę. Tą patį daro registracijos trigeris, kuris kopijuoja rolę iš registracijos metaduomenų; tas atvejis aprašytas straipsnyje Supabase raw_user_meta_data: kaip registracija tapo Owner.
Kaip patikrinti savo politikas?
- Nemokamas Supabase saugumo patikrinimas skaito jūsų projektą tik skaitymo režimu ir pažymi visada teisingas politikas, rašymo politikas svečiams, politikas, kurios pasitiki vartotojo metaduomenimis, ir lenteles su išjungtu RLS, kiekvienai su taisymo SQL.
- Joks skeneris nežino, kurios eilutės kam priklauso. Tam siųskite užklausas kaip du vartotojai, kaip šiame straipsnyje; visas metodas programai su rolėmis aprašytas straipsnyje Kaip testuoti Supabase RLS: 30 iš 87 leidimų nieko nedarė.
- Ką Supabase užtikrina už jus, o ką palieka jums, aprašyta straipsnyje Ar Supabase saugus? Ką jis apima, o ką turite jūs.
- Tie patys savininko ir komandos testai, paleisti ir ant Appwrite, aprašyti straipsnyje Appwrite vs Supabase sauga: tos pačios atakos abiem.
Apribojimai. Vienas vietinis stack ir vienas nešiojamasis kompiuteris, išgalvoti duomenys, 2026 m. spalio 24 d. Laikai priklauso nuo aparatūros ir lentelės formos; svarbiau santykiai nei milisekundės. Storage politikos ir Realtime čia neapimti.
Nesate tikri, ar jūsų politikos laikosi?
Mielai kartu pažiūrėsiu į schemą. Be pardavimų kalbų.
Susisiekti