TL;DR
30.10.2026 Supabase lopettaa public-skeeman uusien taulujen automaattiset grantit rooleille anon, authenticated ja service_role kaikissa olemassa olevissa projekteissa. Jo olemassa olevat taulut toimivat edelleen. Jokainen sen jälkeen luotu taulu vastaa supabase-js:lle ja secret keyllesi 42501 permission denied, kunnes migraatio antaa sille grantin. Toistin vaihdon paikallisesti: uudet taulut, uudet näkymät ja vanhat taulut, jotka migraatio poistaa ja luo uudelleen, kaatuvat; olemassa olevat taulut, RPC-funktiot ja serial-insertit toimivat edelleen. Tarkista kahdella SQL-kyselyllä ja yhdellä grepillä alla ja korjaa grantilla, RLS:llä ja policyllä samassa migraatiossa, älä koskaan komennolla grant all on all tables.
Ylläpidän ilmaista Supabase security checkeriä, joka lukee jokaisen avoimen taulun grantit, joten halusin nähdä tarkasti, mitä tämä muutos tekee, ennen kuin ihmiset törmäävät siihen. Pystytin paikallisen Supabase-stackin, annoin sille vanhat oletukset, otin vaihdon käyttöön niin kuin Supabase sen kuvaa ja lähetin pyynnön jokaiseen objektityyppiin, jonka keksin. Tämä artikkeli kertoo, mitä takaisin tuli ja miten tarkistat oman projektisi ennen 30. päivää.
Mikä muuttuu 30. lokakuuta 2026?
Tähän asti jokainen public-skeemaan luotu taulu sai Postgresin default privilegesin kautta oikeudet select, insert, update ja delete rooleille anon, authenticated ja service_role. Siksi uusi taulu oli heti olemassaolonsa hetkellä tavoitettavissa selaimesta, oli RLS päällä tai ei. Supabasen breaking change -ilmoitus poistaa nämä oletukset kolmessa vaiheessa:
| Päivämäärä | Mitä tapahtuu |
|---|---|
| 28.4.2026 | Uudet projektit voivat ottaa “Automatically expose new tables” -asetuksen pois luonnin yhteydessä |
| 30.5.2026 | Uusi käytös tulee uusien projektien oletukseksi, käyttöönotto muutaman viikon aikana |
| 30.10.2026 | Uusi käytös otetaan käyttöön kaikissa olemassa olevissa projekteissa |
Projektissasi vaihto on kaksi lausetta, samat, jotka Supabase antaa ennakkoon käyttöönottoa varten:
alter default privileges for role postgres in schema public
revoke select, insert, update, delete on tables from anon, authenticated, service_role;
alter default privileges for role postgres in schema public
revoke usage, select on sequences from anon, authenticated, service_role;
Default privileges koskevat vain myöhemmin luotuja objekteja. Olemassa oleviin granteihin ei kosketa, minkä vuoksi nykyiset taulusi toimivat edelleen. Ilmoituksen mukaan muutos ei koske muita skeemoja (auth, storage, realtime, omasi), sovelluksia, jotka puhuvat Postgresille suoralla yhteydellä (psql, ORM, palvelin connection stringillä) eikä itse RLS:ää. Muutos koskee Data API:a: RESTiä, GraphQL:ää ja kaikkia sen päälle rakennettuja client-kirjastoja.
Mikä hajoaa ja mikä toimii edelleen?
Ympäristöni: Supabase CLI 2.109.1, Postgres 17.6, PostgREST 14.14, 26.10.2026. Asetin config.tomliin auto_expose_new_tables = true saadakseni vanhat oletukset, loin yhden taulun kahdella rivillä, RLS päällä ja lukupolicyllä, ja ajoin yllä olevat kaksi lausetta. Sen jälkeen loin uusia objekteja ja kutsuin jokaista paikallisella julkisella (anon) avaimella ja secret-avaimella (service_role), yksi pyyntö kummallekin.
| Objekti, luotu vaihdon jälkeen ellei toisin mainita | Avain | Tulos |
|---|---|---|
| Ennen vaihtoa luotu taulu | julkinen | HTTP 200, 2 riviä |
Uusi taulu RLS:llä ja policyllä to anon, authenticated |
julkinen | HTTP 401, 42501, hint GRANT SELECT ON public.new_orders TO anon; |
| Sama uusi taulu | secret | HTTP 403, 42501, hint … TO service_role; |
Uusi näkymä (security_invoker) vanhan taulun päällä |
julkinen | HTTP 401, 42501, permission denied for view, hint null |
Kopio, tehty komennolla create table … (like old_orders including all) |
julkinen | HTTP 401, 42501 |
Vanha taulu drop tablein ja uuden create tablen jälkeen |
julkinen | HTTP 401, 42501 |
Uusi funktio public.ping() osoitteessa /rest/v1/rpc |
julkinen | HTTP 200 |
Uusi taulu id serialilla, anonille annettu vain select, insert |
julkinen | insert HTTP 201 |
Tässä taulukossa on viisi asiaa, jotka kannattaa tietää ennen vaihtoa.
“Olemassa olevat taulut säilyttävät grantinsa” pätee vain niin kauan kuin taulu on olemassa. Migraatio, joka poistaa ja luo taulun uudelleen, create table … (like …) -kopio tai palautus dumpista tekee uuden taulun, eikä uusi taulu saa mitään. Policy palasi create policyllä, grantit eivät. Kaikki, mikä putkessasi rakentaa tauluja uudelleen sen sijaan että muuttaisi niitä, hajoaa ensimmäisellä ajolla vaihdon jälkeen.
Secret key hajoaa myös. service_role ohittaa RLS:n, mutta tarvitsee silti grantin. Siksi Edge Function, cron-ajo tai webhook-käsittelijä, joka kirjoittaa uuteen tauluun, kaatuu 403:een, ei pelkästään selain. Hint mainitsee service_role:n, mistä sen erottaa.
Näkymät eivät anna hintiä. Tauluille PostgREST palauttaa täsmälleen tarvittavan GRANT-lauseen. Uuden näkymäni hint oli null ja viesti sanoi permission denied for view v_old_orders. Tekoälytyökalulla, joka korjaa 42501:n hintin perusteella, ei ole tässä mitään kopioitavaa; grant on grant select on public.v_old_orders to authenticated;, ja näkymän pitää säilyttää security_invoker = true, jotta taulun RLS pysyy voimassa.
Funktiot eivät kuulu tähän muutokseen. Kaksi lausetta koskevat tauluja ja sekvenssejä. Uusi funktioni pysyi kutsuttavana rooleilla anon, authenticated ja service_role, koska Postgres antaa executen jokaiselle uudelle funktiolle roolille PUBLIC eikä skeematason revoke voi poistaa sitä, kuten Supabasen API-turvallisuusopas selittää. RPC-kutsut toimivat 30. lokakuuta jälkeenkin; se tarkoittaa myös, että uutta security definer -funktiota voi kutsua julkisella avaimellasi heti, kun se on olemassa.
serial-insertit toimivat ilman sekvenssin grantia. Monessa ohjeessa sanotaan, että serial-id tarvitsee vaihdon jälkeen grant usage on sequence … to authenticated. Omassa ajossani insert meni läpi ilman sitä: revoke ottaa uusilta sekvensseiltä pois usagen ja selectin, mutta update jää, ja update riittää nextvalille. Jos peruutat kaikki sekvenssien default privileges, kuten API-turvallisuusoppaan täysi revoke tekee, usage-grant tarvitaan. Identity-sarake (generated by default as identity) ei tarvitse kummassakaan tapauksessa mitään.
Vielä yksi asia, jonka revoke jättää: uusi taulu tuli silti TRUNCATE-, REFERENCES- ja TRIGGER-oikeuksilla kaikille kolmelle roolille, ja Postgres 17:ssä oletuksiin kuuluu myös MAINTAIN. Mikään RLS-policy ei koske TRUNCATEa. Kävin sen läpi 42501-muistiinpanossa.
Missä törmäät siihen ennen 30. lokakuuta
Vaihto isännöidyssä projektissasi tapahtuu 30. päivä, mutta kolme paikkaa käyttäytyy jo kuin vaihdon jälkeen.
supabase db reset tuoreella CLI:llä. CLI v2.102.0 lisäsi config.tomliin avaimen [api] auto_expose_new_tables. Versiolla 2.109.1 ilman avainta tuoreessa stackissani oli jo uudet oletukset: taulut saivat vain MAINTAIN, REFERENCES, TRIGGER ja TRUNCATE, sekvenssit vain UPDATE. Arvo true tuo vanhat grantit takaisin ja tulostaa api.auto_expose_new_tables is deprecated and will be removed on 2026-10-30. Jos paikalliset taulusi toimivat vielä resetin jälkeen, tarkista ensin tämä avain. Se on myös halvin paikka testata: poista se, aja reset ja käynnistä sovelluksesi.
Preview branchit ja 30. toukokuuta jälkeen luodut projektit. Ne ajavat migraatiosi uusilla oletuksilla. Jokainen taulu, jonka migraatiossa ei ole grantia, on siellä jo tavoittamaton. GitHub-keskustelussa ihmiset, joilla on yksi projekti per asiakas, kertovat juuri tästä: käynnissä olevat projektit ovat kunnossa, jokainen uusi deploy samoista migraatioista on rikki.
Baseline-migraatio, joka tuo vanhan oletuksen takaisin. Saman keskustelun kommentoija huomauttaa, että ensimmäinen komennolla supabase db pull tehty migraatio voi sisältää rivin alter default privileges … grant all on tables to anon, authenticated, service_role. Ajoin tuon lauseen vaihdon jälkeen: seuraava luomani taulu oli taas tavoitettavissa, HTTP 200. Paikallisesti se piilottaa ongelman. Isännöityyn projektiin ajettuna se perii muutoksen hiljaa pois ja jokainen tuleva taulu on taas auki julkiselle avaimelle, onpa RLS päällä tai ei. Hae migraatioistasi alter default privileges ja lue jokainen osuma.
Näin tarkistat projektisi ennen 30. lokakuuta
Neljä tarkistusta, nopeimmasta perusteellisimpaan. Kaksi ensimmäistä ajetaan isännöidyn projektin SQL Editorissa.
1. Onko projektisi jo vaihtanut?
Tämä on kysely Supabasen API-turvallisuusoppaasta. Se listaa, mitä postgresin public-skeemaan luomat uudet objektit saavat:
select
case d.defaclobjtype
when 'r' then 'tables'
when 'S' then 'sequences'
when 'f' then 'functions'
end as object_type,
a.grantee::regrole as role,
string_agg(a.privilege_type, ', ' order by a.privilege_type) as privileges
from pg_default_acl d
cross join lateral aclexplode(d.defaclacl) a
where d.defaclrole = 'postgres'::regrole
and d.defaclnamespace = 'public'::regnamespace
and a.grantee in ('anon'::regrole, 'authenticated'::regrole, 'service_role'::regrole)
group by 1, 2
order by 1, 2;
Jos tables-rivit sisältävät SELECT, INSERT, UPDATE, DELETE, projekti on yhä vanhalla oletuksella ja vaihtaa 30. päivä. Jos ne näyttävät vain MAINTAIN, REFERENCES, TRIGGER, TRUNCATE (tai rivejä ei ole), uudet taulut ovat jo suljettuja. Sen näkee myös Dashboardista Data API -asetuksista, “Automatically expose new tables”.
2. Millä tauluilla on policy mutta ei grantia?
Policy roolille, jolla ei ole grantia kyseiseen komentoon, ei koskaan suoriteta: pyyntö kaatuu ennen RLS:ää. Tämä kysely listaa tällaiset parit. Laboratoriossani se löysi uuden taulun ja uudelleen luodun vanhan taulun, ei mitään muuta:
select p.schemaname || '.' || p.tablename as "table", r.role, p.cmd
from pg_policies p
cross join lateral unnest(case when 'public' = any(p.roles) then array['anon', 'authenticated']::name[] else p.roles end) as r(role)
join pg_class c on c.relname = p.tablename
join pg_namespace n on n.oid = c.relnamespace and n.nspname = p.schemaname
where p.schemaname = 'public'
and r.role in ('anon', 'authenticated')
and not has_table_privilege(r.role::text, c.oid,
case p.cmd when 'ALL' then 'SELECT' else p.cmd end)
order by 1, 2, 3;
Aja se jokaisen migraation jälkeen, CI:ssä tuoretta db resetiä vasten tai stagingissa. Terveessä projektissa se ei palauta mitään; 30. päivän jälkeen jokainen rivi on taulu, johon sovelluksesi ei pääse. Se tarkistaa taulutason grantit, joten taulu, joka on avattu vain sarakegranteilla (grant select (id, name) on …), näkyy tässä, vaikka nuo sarakkeet ovat luettavissa.
3. Mitkä migraatiot luovat taulun ilman grantia?
Tietokanta näyttää vain olemassa olevat taulut. Migraatio, jota et ole vielä ajanut, näkyy vain repossasi. Tämä karkea grep listaa jokaisen create table -lauseen kansiossa supabase/migrations, jolle ei löydy grant … on samalla nimellä:
for t in $(grep -hioE 'create table( if not exists)? +[a-z0-9_."]+' supabase/migrations/*.sql | awk '{print $NF}' | sort -u); do
grep -qiF "on $t " supabase/migrations/*.sql || grep -qiF "on table $t " supabase/migrations/*.sql || echo "no grant: $t"
done
Se on tekstivertailua, ei SQL:n jäsennystä: grant, joka listaa useita tauluja yhdessä lauseessa, tai taulu, jota nimetään yhdessä paikassa ilman skeemaa ja toisessa skeeman kanssa, antaa väärän osuman. Lue jokainen tulostettu rivi. Taulut, joihin pääsee vain palvelinkoodi suoralla yhteydellä, eivät tarvitse grantia lainkaan.
4. Ajele kaikki uusilla oletuksilla
Poista auto_expose_new_tables tiedostosta config.toml, aja supabase db reset ja käynnistä sovelluksesi tai sen testit sitä vasten. Se on projektisi 30. päivän jälkeen plus tuore kopio jokaisesta taulusta, mikä on vaikeampi tapaus. Supabase listaa vaikutuksen alaiset taulut myös Security Advisorissa ja merkitsee Data API -altistuksen Table Editoriin.
Näin korjaat sen avaamatta jokaista taulua
Kirjoita grant, RLS ja policyt migraatioon, joka luo taulun, ja anna kullekin roolille vain se, mitä sovellus käyttää:
create table public.invoices (
id bigint generated by default as identity primary key,
user_id uuid not null default auth.uid(),
total numeric not null
);
revoke all on table public.invoices from anon, authenticated, service_role;
grant select, insert on table public.invoices to authenticated;
grant select, insert, update, delete on table public.invoices to service_role;
alter table public.invoices enable row level security;
create policy "Owners read their invoices"
on public.invoices for select
to authenticated
using ((select auth.uid()) = user_id);
create policy "Owners add their invoices"
on public.invoices for insert
to authenticated
with check ((select auth.uid()) = user_id);
Ajoin tämän lohkon samalla paikallisella stackilla: sisäänkirjautunut testikäyttäjä lisäsi rivin ja luki sen takaisin (HTTP 201, sitten 200), julkinen avain ilman sessiota sai 42501:n, ja yllä oleva tarkistus 2 ei enää listannut taulua. Ensimmäinen revoke all siivoaa TRUNCATE-, TRIGGER- ja REFERENCES-oikeudet, jotka tulevat uuden taulun mukana, ja antaa saman tuloksen projektissa, joka ei ole vielä vaihtanut. Anna anonille oikeudet vain kirjautumattomille kävijöille tarkoitettuun dataan ja service_rolelle vain jos palvelinkoodi käyttää secret keytä tässä taulussa. Miten grantit ja policyt sopivat yhteen koko sovelluksessa, selviää Lovable-sovelluksen tietoturvatarkistuslistastani.
Mitä ei pidä tehdä, kun ensimmäinen 42501 tulee:
- Älä aja
grant all on all tables in schema public to anon, authenticated. Se palauttaa vanhan oletuksen kaikille tauluille kerralla, myös jokaiselle taululle, jolla RLS:ää ei ole koskaan otettu käyttöön, ja ne muuttuvat luettaviksi kenelle tahansa, jolla on julkinen avaimesi. 42501-muistiinpano näyttää, miksi grant on ensimmäinen lukko ja RLS toinen. - Älä lisää takaisin
alter default privileges … grant. Se on koko muutoksen rollback. - Älä poista RLS:ää käytöstä “testataksesi” äläkä laita secret keytä frontendiin. Secret key ohittaa RLS:n jokaiselle, joka avaa bundlesi; avainten ero selviää artikkelista anon key vs service role key.
Jos tekoälytyökalu kirjoittaa migraatiosi, lisää sen ohjeisiin yksi rivi: “every new table in public gets explicit grants per role, enable row level security and policies in the same migration; never grant on all tables in schema public”. Virheen hint opettaa sille grantin, mutta ei RLS:ää, joka siihen kuuluu.
Kysymyksiä muutoksesta
Lakkaavatko olemassa olevat taulut toimimasta 30. lokakuuta? Ei. Default privileges koskevat myöhemmin luotuja objekteja, ja ajossani vanha taulu vastasi vaihdon jälkeen HTTP 200. Poikkeus on taulu, jonka migraatiosi poistavat ja luovat uudelleen.
Pitääkö minun tehdä jotain, jos en koskaan luo tauluja? Ei 30. päivänä. Tarkista seuraavalla kerralla, kun sinä, tiimikaveri tai tekoälytyökalu lisää taulun, ja tarkista jokainen putki, joka luo projekteja tai brancheja migraatioistasi.
Vaikuttaako se Edge Functioneihin? Kyllä, jos ne käyttävät supabase-js:ää secret keyllä uudessa taulussa: service_role tarvitsee myös grantin. Funktioihin, jotka yhdistävät suoraan Postgresiin, se ei vaikuta.
Vaikuttaako se RPC-funktioihin, Storageen tai Authiin? Ei tässä muutoksessa. Uudet funktiot pysyvät kutsuttavina PUBLICin kautta, eivätkä storage ja auth ole public-skeema.
Voinko vaihtaa etuajassa? Kyllä. Alun kaksi lausetta ovat vaihto; aja ne ensin stagingissa, sitten tuotannossa. Olemassa olevat taulut säilyttävät grantinsa, joten käynnissä oleva sovellus ei huomaa mitään, ja seuraava uusi taulu kertoo heti, ovatko migraatiosi valmiit.
Näin checker näkee tämän
Checker yhdistää vain luku -tilassa ja lukee jokaisen taulun grantit avoimissa skeemoissasi: mihin tauluihin julkinen avain voi kirjoittaa ilman policyä, millä tauluilla RLS on pois päältä ja mitkä default privileges antavat yhä jokaiselle uudelle taululle TRUNCATE-, REFERENCES- tai TRIGGER-oikeuden. Se listaa myös jokaisen policyn, jonka roolilla ei ole granttia kyseiseen komentoon: taulut, jotka yllä oleva tarkistus 2 löytää, paitsi ne, joissa RLS on pois päältä tai joilla on sarakekohtaisia grantteja. Se ei näe migraatiotiedostoja, joita et ole ajanut, joten yllä oleva tarkistus 3 jää sinun ajettavaksesi. Jos RLS on se osa, josta et ole varma, miten Supabase RLS testataan käy sen läpi, ja avoimet skeemat -muistiinpano kattaa muut skeemat kuin public, joihin tämä muutos ei koske.