TL;DR
RLS otetaan käyttöön Supabasessa yhdellä lauseella taulua kohden, alter table public.todos enable row level security;, tai hallintapaneelin valinnalla “Enable RLS”. SQL-editorissa tai migraatiolla luodut taulut eivät saa sitä oletuksena. Heti käyttöönoton jälkeen taulu hiljenee eikä huuda. Paikallisessa testissäni julkinen avain ja kaksi kirjautunutta käyttäjää saivat 200 [] selectiin, updateen ja deleteen, ja vain insert epäonnistui virheellä 42501. Salainen avain näki yhä kaikki 3 riviä. Pelkkä näkymä (view) taulun päällä antoi yhä kaikki 3 riviä julkiselle avaimelle. 30. lokakuuta 2026 alkaen uusi taulu tarvitsee myös granttin ennen kuin mikään pääsee siihen, joten ota RLS käyttöön ja kirjoita politiikat samassa migraatiossa kuin grant.
Ylläpidän ilmaista Supabasen tietoturvatarkistusta, ja “RLS disabled” on löytö, jonka selitetään huonoimmin. Useimmat oppaat sanovat, että RLS:n käyttöönoton jälkeen “taulu muuttuu saavuttamattomaksi”. Se pitää paikkansa lukemisen osalta ja johtaa harhaan kirjoittamisen osalta. Siksi tein yhden taulun paikalliseen Supabase-stackiin ja lähetin siihen kaikenlaisia pyyntöjä: julkisella avaimella, kahtena kirjautuneena käyttäjänä ja salaisella avaimella, jokaisen alla olevan vaiheen jälkeen.
Mikä RLS on Supabasessa?
RLS (row-level security) on Postgresin ominaisuus, joka suodattaa rivejä pyyntökohtaisesti. Supabase muuttaa jokaisen supabase-js-pyynnön tietokantakyselyksi, joka ajetaan roolina: anon julkiselle (anon, publishable) avaimelle ja authenticated kirjautuneelle käyttäjälle. RLS päällä Postgres tarkistaa politiikkasi jokaiselle riville ja palauttaa, muuttaa tai poistaa vain ne rivit, jotka jokin politiikka sallii. RLS pois päältä mikä tahansa rooli, jolla on taulussa grant, pääsee kaikkiin riveihin.
Salainen (service role) avain on erilainen. Sen roolilla on BYPASSRLS, joten RLS ei koskaan koske siihen. Siksi se kuuluu vain palvelimelle. Ero näiden kahden avaimen välillä on artikkelissa Supabase anon key vs service role key: mitä kumpikin avaa.
Miten enable RLS in Supabase onnistuu?
Kolmella tavalla sen mukaan, miten taulu tehtiin:
- Kun luot taulun Table Editorissa, jätä “Enable Row Level Security (RLS)” valituksi. Supabasen hallintapaneelin lähdekoodissa se on merkitty “Recommended”, ja sen poistaminen näyttää varoituksen, että taulu on julkisesti luettavissa ja kirjoitettavissa.
- Olemassa olevalle taululle Supabasen API-turvaopas neuvoo: mene kohtaan Database, sitten Policies, ja valitse taulusta “Enable RLS”.
- SQL:llä SQL-editorissa tai migraatiossa:
alter table public.todos enable row level security;
Supabasen opas sanoo selvästi, että hallintapaneelissa luoduissa tauluissa RLS on oletuksena päällä, mutta SQL-editorissa tai muulla työkalulla luoduissa ei ole. Jos rakennat skeeman migraatioilla tai tekoälytyökalulla, tarkista jokainen taulu.
Mitä tapahtuu heti RLS:n käyttöönoton jälkeen?
Mikään ei anna virhettä paitsi insert. Loin taulun public.todos omistajasarakkeella (user_id uuid default auth.uid()), annoin siihen granttit API-rooleille niin kuin vanhemmissa projekteissa, ja lisäsin kolme riviä: kaksi käyttäjän A ja yhden käyttäjän B. Sitten otin RLS:n käyttöön ilman politiikkaa:
| Pyyntö | Julkinen avain | Käyttäjä A | Käyttäjä B | Salainen avain |
|---|---|---|---|---|
| Select | 200, 0 riviä | 200, 0 riviä | 200, 0 riviä | 200, 3 riviä |
| Insert omalla rivillä | 401, virhe 42501 | 403, virhe 42501 | 403, virhe 42501 | 201, rivi lisätty |
| Update riville | 200, 0 riviä | 200, 0 riviä | 200, 0 riviä | 200, 1 rivi |
| Delete riville | 200, 0 riviä | 200, 0 riviä | 200, 0 riviä | 200, 1 rivi |
Insert-virhe kuuluu new row violates row-level security policy. Update ja delete vastaavat 200 ja tyhjällä listalla, joten sovellus, joka ei tarkista palautettuja rivejä, ilmoittaa “tallennettu”, vaikka mitään ei tallennettu. Lue kirjoituksesi takaisin RLS:n käyttöönoton jälkeen tai pyydä lukumäärä otsakkeella Prefer: count=exact.
Ennen RLS:ää samoilla granteilla julkinen avain pystyi lukemaan kaikki 3 riviä, lisäämään rivejä sekä päivittämään tai poistamaan mitä tahansa riviä. Käyttäjä A pystyi muokkaamaan ja poistamaan käyttäjän B rivin ja antamaan omat rivinsä B:lle.
Miten taulu saadaan taas käyttökelpoiseksi?
Kirjoita politiikka jokaiselle komennolle. Tavallinen omien rivien sarja:
create policy "own select" on public.todos for select to authenticated
using ((select auth.uid()) = user_id);
create policy "own insert" on public.todos for insert to authenticated
with check ((select auth.uid()) = user_id);
create policy "own update" on public.todos for update to authenticated
using ((select auth.uid()) = user_id) with check ((select auth.uid()) = user_id);
create policy "own delete" on public.todos for delete to authenticated
using ((select auth.uid()) = user_id);
Näillä käyttäjä A näki 2 riviään ja käyttäjä B 1. Julkinen avain näki yhä 0. Kun A yritti lisätä rivin, jonka omistaja on B, tai siirtää oman rivinsä B:lle, vastaus oli 403, 42501. Kieltäytyminen tulee updaten with check -ehdosta. Kun A yritti päivittää tai poistaa B:n rivin, vastaus oli 200, 0 riviä. Lisää malleja (julkiset profiilit, tiimit, MFA) on artikkelissa Supabase RLS policy -esimerkit testattuna API:n kautta.
Yksi havainto salaisesta avaimesta: sen lisäämä rivi sai user_id = null, koska auth.uid():lla ei ole käyttäjää tämän avaimen takana. RLS ohitettiin, mutta oletusarvo ei tehnyt sitä, mitä sarakkeen nimi antaa ymmärtää.
Mitä julkinen avain luki kunkin vaiheen jälkeen?
Kaksi vaihetta vuotaa kaiken: grant ilman RLS:ää ja pelkkä näkymä taulun päällä, jonka politiikat ovat kunnossa. Seuraavat kaksi osiota selittävät molemmat.
Tarvitaanko RLS:ää yhä 30. lokakuun grant-muutoksen jälkeen?
Kyllä. 30. lokakuuta 2026 alkaen Supabase ei enää anna uusille tauluille skeemassa public automaattisia granteja API-rooleille, uusissa ja olemassa olevissa projekteissa. Paikallinen stackini toimii jo niin. Juuri luotu taulu, jolla RLS oli pois päältä, vastasi 42501 kaikille rooleille, salainen avain mukaan lukien, joten mikään ei vuotanut. Yksityiskohdat ovat artikkelissa Supabasen grant-muutos 30. lokakuuta: mikä hajoaa.
Mutta grant palaa heti, kun sovellus tarvitsee taulun, ja siitä lähtien RLS on ainoa asia julkisen avaimen ja jokaisen rivin välissä. Laita siis kaikki yhteen migraatioon tässä järjestyksessä:
create table public.todos (...);
alter table public.todos enable row level security;
create policy "own select" on public.todos ...;
grant select, insert, update, delete on table public.todos to authenticated;
Anna grant vain rooleille, jotka taulua tarvitsevat. Kun peruutin kaiken roolilta anon, julkisen avaimen select muuttui vastauksesta 200 [] vastaukseksi 401, 42501. Grant on ensimmäinen lukko, RLS toinen.
Miksi näkymä silti vuotaa rivejä?
Koska näkymä ajetaan omistajansa oikeuksilla, ja Supabasessa se on postgres, joka ohittaa RLS:n. Kun todos-taulussa oli omien rivien politiikat, pelkkä create view public.todos_view as select * from public.todos palautti kaikki 3 riviä julkiselle avaimelle ja käyttäjälle B.
Kun sama näkymä luotiin uudelleen with (security_invoker = true), se palautti julkiselle avaimelle 0 riviä, A:lle 2 ja B:lle 1, täsmälleen kuten taulu:
create view public.todos_view with (security_invoker = true) as
select * from public.todos;
Supabasen Advisor liputtaa pelkän näkymän tunnuksella security_definer_view; mitä se tarkoittaa ja miten sen korjaa, on muistiinpanossa Supabase security definer view: miksi se ohittaa RLS:n.
Miten RLS otetaan käyttöön automaattisesti jokaiselle uudelle taululle?
Tapahtumatriggerillä. Supabasen event triggers -opas antaa funktion rls_auto_enable() ja triggerin ensure_rls, joka ajetaan create table-, create table as- ja select into -lauseiden jälkeen skeemassa public. Asensin sen sellaisenaan julkaistuna ja sain:
- Myöhemmin lauseella
create tableluodussa taulussa RLS oli päällä ilmanalter-lausetta. Julkinen avain sai200 []selectiin ja401insertiin. - Lauseella
create table as selectluodussa taulussa RLS oli myös päällä. - Ennen triggeriä luotu taulu pysyi pois päältä. Trigger ei toimi taaksepäin.
Ilman triggeriä suojatun taulun viereen luodussa toisessa taulussa RLS oli pois päältä, ja kun siihen annettiin grant, julkinen avain luki ja kirjoitti sitä vapaasti. Trigger kattaa myös vain skeeman public; muut julkaistut skeemat tarvitsevat oman rivinsä funktioon.
Auttaako FORCE ROW LEVEL SECURITY?
Ei niille rooleille, joista kannattaa olla huolissaan. alter table ... force row level security panee RLS:n koskemaan myös taulun omistajaa. Supabasessa sekä postgres että service_role ovat BYPASSRLS, joten forcen jälkeen omistaja laski yhä 3 riviä, salainen avain näki yhä kaiken ja postgresin omistama näkymä vuoti yhä. Se muutti jotain vain omistajaroolille, jonka loin ilman BYPASSRLS: 2 riviä ennen, 0 jälkeen.
Miten tarkistat oman projektisi?
- Listaa SQL-editorissa skeeman
publictaulut, joilla RLS on pois päältä:
select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
and c.relkind in ('r', 'p')
and not c.relrowsecurity
order by 1;
- Hallintapaneelin Advisor raportoi saman tunnuksella
rls_disabled_in_public; mitä tämä virhe tarkoittaa, on muistiinpanossa Supabase “RLS disabled in public”: mitä se tarkoittaa. - Ilmainen Supabasen tietoturvatarkistus lukee projektisi vain luku -tilassa ja listaa taulut, joilla RLS on pois päältä ja joihin API yltää, RLS:n ohittavat näkymät ja politiikat, jotka sallivat kaikille, kullekin korjaus-SQL:llä.
- Skanneri ei voi tietää, mitkä rivit kuuluvat kenellekin. Sen todistamiseksi lähetä pyyntöjä kahtena käyttäjänä, kuten yllä. Koko menetelmä rooleja käyttävälle sovellukselle on artikkelissa Supabase RLS:n testaus: 30 oikeutta 87:stä ei tehnyt mitään.
Miten testasin
- Supabase CLI 2.109.1 kannettavalla: Postgres 17.6, PostgREST 14.14, Auth 2.192.0. Kaksi käyttäjää kirjautui salasanalla, lisäksi paikalliset publishable- ja secret-avaimet.
- Yksi taulu,
public.todos, alustettu samoilla 3 rivillä ennen jokaista pyyntöä. Jokainen vaihe ajettiin SQL:nä ja jokainen pyyntö kulki REST API:n kautta, tilakoodi ja runko tallennettuna. - Vaiheiden 2–5 granttit lisättiin käsin vastaamaan projektia, joka on luotu ennen 30. lokakuuta 2026.
Rajoitukset. Yksi paikallinen stack, keksittyä dataa, 28. lokakuuta 2026. Hallintapaneelin vaiheet on lainattu Supabasen dokumentaatiosta, ei klikattu läpi. Hallintapaneelin oman roolin luomilla tauluilla voi olla toiset oletusgranttit. En testannut sitä enkä Storagea, Realtimea tai GraphQL:ää.
Etkö ole varma, ovatko taulusi lukittu?
Katson mielelläni skeemaa kanssasi. Ei myyntipuhetta.
Ota yhteyttä