---
title: "Supabasen grant-muutos 30. lokakuuta: mikä hajoaa"
description: "30.10.2026 Supabase lopettaa uusien public-taulujen API-grantit myös vanhoissa projekteissa. Mikä hajoaa, mikä toimii ja neljä tarkistusta."
url: "https://madsalt.tech/fi/blog/supabase-october-30-grant-change/"
author: "Denys Zaichenko"
language: "fi"
published: 2026-10-26
---

# Supabasen grant-muutos 30. lokakuuta: mikä hajoaa

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ä](https://madsalt.tech/fi/tools/supabase-security-check/), 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](https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically) 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:

```sql
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.toml`iin `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.

![Mikä lakkaa vastaamasta 30. lokakuuta tehtävän vaihdon jälkeen. Paikallinen Supabase, objektit luotu changelogin revoken jälkeen, julkinen avain ellei toisin mainita. Uusi taulu RLS:llä ja policyllä: create table → policy, ei grantia → 42501, HTTP 401; Sama taulu secret keyllä: create table → policy, ei grantia → 42501, HTTP 403; Uusi näkymä taulun päällä, joka vielä toimii: create view → 42501, HTTP 401, ei hintiä; Vanha taulu, jonka migraatio poisti ja loi uudelleen: drop + create table → grantit poissa → 42501, HTTP 401.](https://madsalt.tech/charts/fi/supabase-october-30-grant-change.svg)

*Oma toistoni paikallisella Supabase-stackilla (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26.10.2026.*

![Mikä toimii edelleen 30. lokakuuta tehtävän vaihdon jälkeen. Sama paikallinen stack, sama revoke, julkinen avain. Ennen vaihtoa luotu taulu: olemassa oleva taulu → grantit säilyvät → 2 riviä, HTTP 200; Uusi funktio kutsuttuna RPC:llä: create function → execute PUBLICin kautta → HTTP 200; Uusi serial-taulu, annettu vain select ja insert: id serial → sekvenssille jää UPDATE → insert, HTTP 201.](https://madsalt.tech/charts/fi/supabase-october-30-grant-change-works.svg)

*Oma toistoni paikallisella Supabase-stackilla (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26.10.2026.*

| 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 table`in ja uuden `create table`n jälkeen              | julkinen | HTTP 401, 42501                                                    |
| Uusi funktio `public.ping()` osoitteessa `/rest/v1/rpc`                  | julkinen | HTTP 200                                                           |
| Uusi taulu `id serial`illa, `anon`ille 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 policy`llä, 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 `execute`n jokaiselle uudelle funktiolle roolille `PUBLIC` eikä skeematason revoke voi poistaa sitä, kuten [Supabasen API-turvallisuusopas](https://supabase.com/docs/guides/api/securing-your-api) 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 `usage`n ja `select`in, mutta `update` jää, ja `update` riittää `nextval`ille. 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 `TRUNCATE`a. Kävin sen läpi [42501-muistiinpanossa](https://madsalt.tech/fi/notes/supabase-42501-permission-denied/).

## 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.toml`iin 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 `grant`ia, on siellä jo tavoittamaton. [GitHub-keskustelussa](https://github.com/orgs/supabase/discussions/45329) 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ä `postgres`in `public`-skeemaan luomat uudet objektit saavat:

```sql
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:

```sql
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 reset`iä 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ä:

```bash
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ää:

```sql
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 `anon`ille oikeudet vain kirjautumattomille kävijöille tarkoitettuun dataan ja `service_role`lle vain jos palvelinkoodi käyttää secret keytä tässä taulussa. Miten grantit ja policyt sopivat yhteen koko sovelluksessa, selviää [Lovable-sovelluksen tietoturvatarkistuslistastani](https://madsalt.tech/fi/blog/lovable-app-security-checklist/).

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](https://madsalt.tech/fi/notes/supabase-42501-permission-denied/) 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](https://madsalt.tech/fi/blog/supabase-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 `PUBLIC`in 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](https://madsalt.tech/fi/blog/how-to-test-supabase-rls/) käy sen läpi, ja [avoimet skeemat -muistiinpano](https://madsalt.tech/fi/notes/supabase-exposed-schemas/) kattaa muut skeemat kuin `public`, joihin tämä muutos ei koske.
