---
title: "Supabase-Änderung am 30. Oktober: Was bricht, wie prüfen"
description: "Ab 30. Oktober 2026 gibt Supabase neue public-Tabellen nicht mehr automatisch für die API frei. Was bricht, was läuft, vier Checks, lokal getestet."
url: "https://madsalt.tech/de/blog/supabase-october-30-grant-change/"
author: "Denys Zaichenko"
language: "de"
published: 2026-10-26
---

# Supabase-Änderung am 30. Oktober: Was bricht, wie prüfen

Kurz gesagt

Am 30. Oktober 2026 vergibt Supabase auf jedem bestehenden Projekt keine automatischen Grants für `anon`, `authenticated` und `service_role` mehr an neue Tabellen in `public`. Tabellen, die Sie schon haben, laufen weiter. Jede Tabelle, die danach entsteht, antwortet `supabase-js` und Ihrem Secret Key mit `42501 permission denied`, bis eine Migration sie freigibt. Ich habe die Umstellung lokal nachgestellt: neue Tabellen, neue Views und alte Tabellen, die eine Migration löscht und neu anlegt, scheitern alle; bestehende Tabellen, RPC-Funktionen und `serial`-Inserts laufen weiter. Geprüft wird mit zwei SQL-Abfragen und einem grep weiter unten, behoben mit Grant, RLS und Policy in derselben Migration, nie mit `grant all on all tables`.

Ich betreibe einen kostenlosen [Supabase Security Checker](https://madsalt.tech/de/tools/supabase-security-check/), der die Grants jeder exponierten Tabelle liest. Deshalb wollte ich genau sehen, was diese Änderung bewirkt, bevor Leute darauf stoßen. Ich habe einen lokalen Supabase-Stack genommen, ihm die alten Defaults gegeben, die Umstellung so ausgeführt, wie Supabase sie beschreibt, und an jede Art von Objekt, die mir einfiel, eine Anfrage geschickt. Dieser Beitrag zeigt, was zurückkam und wie Sie Ihr eigenes Projekt vor dem 30. prüfen.

## Was ändert sich am 30. Oktober 2026?

Bisher bekam jede Tabelle im Schema `public` über die Default Privileges von Postgres `select`, `insert`, `update` und `delete` für `anon`, `authenticated` und `service_role`. Deshalb war eine neue Tabelle vom Browser aus erreichbar, sobald es sie gab, ob RLS sie schützte oder nicht. Die [Breaking-Change-Meldung](https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically) von Supabase entfernt diese Defaults in drei Schritten:

| Datum            | Was passiert                                                                          |
| ---------------- | ------------------------------------------------------------------------------------- |
| 28. April 2026   | Neue Projekte können bei der Erstellung „Automatically expose new tables“ abwählen    |
| 30. Mai 2026     | Das neue Verhalten wird für neue Projekte zum Standard, über einige Wochen ausgerollt |
| 30. Oktober 2026 | Das neue Verhalten gilt für alle bestehenden Projekte                                 |

Auf Ihrem Projekt sind es zwei Statements, dieselben, die Supabase für den frühen Opt-in nennt:

```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 gelten nur für Objekte, die später angelegt werden. Bestehende Grants bleiben unberührt, deshalb laufen Ihre aktuellen Tabellen weiter. Nicht betroffen sind laut Meldung: andere Schemas (`auth`, `storage`, `realtime`, eigene), Apps, die über eine direkte Verbindung mit Postgres sprechen (psql, ein ORM, ein Server mit Connection String), und RLS selbst. Die Änderung betrifft die Data API: REST, GraphQL und jede Client-Bibliothek darüber.

## Was bricht und was läuft weiter?

Mein Setup: Supabase CLI 2.109.1, Postgres 17.6, PostgREST 14.14, am 26. Oktober 2026. Ich habe in `config.toml` `auto_expose_new_tables = true` gesetzt, um die alten Defaults zu bekommen, eine Tabelle mit zwei Zeilen, aktivem RLS und einer Lese-Policy angelegt und dann die beiden Statements von oben ausgeführt. Danach habe ich neue Objekte angelegt und jedes mit dem lokalen öffentlichen Key (`anon`) und dem Secret Key (`service_role`) aufgerufen, je eine Anfrage.

![Was nach der Umstellung am 30. Oktober nicht mehr antwortet. Lokales Supabase, Objekte nach dem Revoke aus dem Changelog, öffentlicher Key, sofern nicht anders angegeben. Neue Tabelle mit RLS und einer Policy: create table → Policy, kein Grant → 42501, HTTP 401; Dieselbe Tabelle mit dem Secret Key: create table → Policy, kein Grant → 42501, HTTP 403; Neue View über einer Tabelle, die noch läuft: create view → 42501, HTTP 401, kein Hint; Alte Tabelle von einer Migration gelöscht und neu angelegt: drop + create table → Grants weg → 42501, HTTP 401.](https://madsalt.tech/charts/de/supabase-october-30-grant-change.svg)

*Meine Reproduktion auf einem lokalen Supabase-Stack (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26. Oktober 2026.*

![Was nach der Umstellung am 30. Oktober weiterläuft. Derselbe lokale Stack, dasselbe Revoke, öffentlicher Key. Tabelle vor der Umstellung angelegt: bestehende Tabelle → Grants bleiben → 2 Zeilen, HTTP 200; Neue Funktion über RPC aufgerufen: create function → execute via PUBLIC → HTTP 200; Neue serial-Tabelle, nur select und insert: id serial → Sequenz-UPDATE bleibt → insert, HTTP 201.](https://madsalt.tech/charts/de/supabase-october-30-grant-change-works.svg)

*Meine Reproduktion auf einem lokalen Supabase-Stack (CLI 2.109.1, Postgres 17.6, PostgREST 14.14), 26. Oktober 2026.*

| Objekt, nach der Umstellung angelegt, sofern nicht anders angegeben   | Key        | Ergebnis                                                           |
| --------------------------------------------------------------------- | ---------- | ------------------------------------------------------------------ |
| Tabelle, vor der Umstellung angelegt                                  | öffentlich | HTTP 200, 2 Zeilen                                                 |
| Neue Tabelle mit RLS und einer `to anon, authenticated`-Policy        | öffentlich | HTTP 401, 42501, Hint `GRANT SELECT ON public.new_orders TO anon;` |
| Dieselbe neue Tabelle                                                 | secret     | HTTP 403, 42501, Hint `… TO service_role;`                         |
| Neue View (`security_invoker`) über der alten Tabelle                 | öffentlich | HTTP 401, 42501, `permission denied for view`, Hint `null`         |
| Kopie per `create table … (like old_orders including all)`            | öffentlich | HTTP 401, 42501                                                    |
| Die alte Tabelle nach `drop table` und erneutem `create table`        | öffentlich | HTTP 401, 42501                                                    |
| Neue Funktion `public.ping()` über `/rest/v1/rpc`                     | öffentlich | HTTP 200                                                           |
| Neue Tabelle mit `id serial`, nur `select, insert` an `anon` vergeben | öffentlich | Insert HTTP 201                                                    |

Fünf Dinge in dieser Tabelle sollten Sie vor der Umstellung kennen.

**„Bestehende Tabellen behalten ihre Grants“ gilt nur, solange die Tabelle existiert.** Eine Migration, die eine Tabelle löscht und neu anlegt, eine Kopie per `create table … (like …)` oder eine Wiederherstellung aus einem Dump erzeugt eine neue Tabelle, und eine neue Tabelle bekommt nichts. Die Policy kam mit meinem `create policy` zurück, die Grants nicht. Alles in Ihrer Pipeline, das Tabellen neu aufbaut statt sie zu ändern (alter), bricht beim ersten Lauf nach der Umstellung.

**Auch der Secret Key bricht.** `service_role` überspringt RLS, braucht aber trotzdem einen Grant. Eine Edge Function, ein Cron-Job oder ein Webhook-Handler, der in eine neue Tabelle schreibt, scheitert also mit 403, nicht nur der Browser. Der Hint nennt `service_role`, daran erkennen Sie es.

**Views liefern keinen Hint.** Bei Tabellen gibt PostgREST den genauen `GRANT` zurück, den Sie brauchen. Bei meiner neuen View war der Hint `null`, die Meldung lautete `permission denied for view v_old_orders`. Ein KI-Tool, das 42501 anhand des Hints behebt, hat hier nichts zum Kopieren; der Grant lautet `grant select on public.v_old_orders to authenticated;`, und die View sollte `security_invoker = true` behalten, damit das RLS der Tabelle weiter greift.

**Funktionen sind nicht Teil dieser Änderung.** Die beiden Statements betreffen Tabellen und Sequenzen. Meine neue Funktion blieb für `anon`, `authenticated` und `service_role` aufrufbar, weil Postgres `execute` für jede neue Funktion an `PUBLIC` vergibt und ein Revoke auf Schema-Ebene das nicht entfernen kann, wie der [API-Security-Leitfaden von Supabase](https://supabase.com/docs/guides/api/securing-your-api) erklärt. RPC-Aufrufe laufen nach dem 30. Oktober weiter; das heißt auch, dass eine neue `security definer`-Funktion mit Ihrem öffentlichen Key aufrufbar ist, sobald es sie gibt.

**`serial`-Inserts liefen ohne Sequenz-Grant.** Viele Ratschläge sagen, eine `serial`-ID brauche nach der Umstellung `grant usage on sequence … to authenticated`. In meinem Lauf ging der Insert auch ohne durch: Das Revoke nimmt `usage` und `select` auf neuen Sequenzen, aber `update` bleibt, und `update` reicht für `nextval`. Wenn Sie alle Default Privileges auf Sequenzen entziehen, wie es das vollständige Revoke im API-Security-Leitfaden tut, brauchen Sie den `usage`-Grant. Eine Identity-Spalte (`generated by default as identity`) braucht in beiden Fällen nichts.

Eines lässt das Revoke noch zurück: Die neue Tabelle kam weiterhin mit `TRUNCATE`, `REFERENCES` und `TRIGGER` für alle drei Rollen, und auf Postgres 17 enthalten die Defaults zusätzlich `MAINTAIN`. Auf `TRUNCATE` greift keine RLS-Policy. Das habe ich in der [42501-Notiz](https://madsalt.tech/de/notes/supabase-42501-permission-denied/) behandelt.

## Wo Sie es schon vor dem 30. Oktober treffen

Die Umstellung Ihres gehosteten Projekts ist am 30., aber drei Stellen verhalten sich schon wie danach.

**`supabase db reset` mit aktueller CLI.** CLI v2.102.0 hat `[api] auto_expose_new_tables` in `config.toml` eingeführt. Auf 2.109.1 hatte mein frischer Stack ohne diesen Key bereits die neuen Defaults: Tabellen bekamen nur `MAINTAIN`, `REFERENCES`, `TRIGGER` und `TRUNCATE`, Sequenzen nur `UPDATE`. Auf `true` gesetzt kommen die alten Grants zurück, und es erscheint `api.auto_expose_new_tables is deprecated and will be removed on 2026-10-30`. Wenn Ihre lokalen Tabellen nach einem Reset also noch laufen, prüfen Sie zuerst diesen Key. Es ist auch die günstigste Stelle zum Testen: entfernen, Reset, App starten.

**Preview Branches und Projekte, die seit dem 30. Mai entstanden sind.** Sie spielen Ihre Migrationen mit den neuen Defaults ein. Jede Tabelle, deren Migration kein `grant` enthält, ist dort schon jetzt unerreichbar. In der [GitHub-Diskussion](https://github.com/orgs/supabase/discussions/45329) berichten Leute mit einem Projekt pro Kunde genau das: Die laufenden Projekte sind in Ordnung, jedes neue Deployment aus denselben Migrationen ist kaputt.

**Eine Baseline-Migration, die den alten Default zurückbringt.** Ein Kommentator in derselben Diskussion weist darauf hin, dass eine erste Migration, die mit `supabase db pull` erzeugt wurde, `alter default privileges … grant all on tables to anon, authenticated, service_role` enthalten kann. Ich habe dieses Statement nach der Umstellung ausgeführt: Die nächste Tabelle, die ich anlegte, war wieder erreichbar, HTTP 200. Lokal verdeckt das das Problem. Auf Ihrem gehosteten Projekt macht es die Änderung stillschweigend rückgängig, und jede künftige Tabelle ist für den öffentlichen Key wieder offen, mit oder ohne RLS. Suchen Sie in Ihren Migrationen nach `alter default privileges` und lesen Sie jeden Treffer.

## So prüfen Sie Ihr Projekt vor dem 30. Oktober

Vier Checks, vom schnellsten zum gründlichsten. Die ersten beiden laufen im SQL Editor des gehosteten Projekts.

### 1. Ist Ihr Projekt schon umgestellt?

Das ist die Abfrage aus dem API-Security-Leitfaden von Supabase. Sie zeigt, was neue Objekte bekommen, die `postgres` in `public` anlegt:

```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;
```

Enthalten die `tables`-Zeilen `SELECT, INSERT, UPDATE, DELETE`, steht das Projekt noch auf dem alten Default und wird am 30. umgestellt. Zeigen sie nur `MAINTAIN, REFERENCES, TRIGGER, TRUNCATE` (oder keine Zeilen), sind neue Tabellen schon geschlossen. Sie sehen es auch im Dashboard unter den Data-API-Einstellungen, „Automatically expose new tables“.

### 2. Welche Tabellen haben eine Policy, aber keinen Grant?

Eine Policy für eine Rolle, die für diesen Befehl keinen Grant hat, läuft nie: Die Anfrage scheitert vor RLS. Diese Abfrage listet solche Paare auf. In meinem Labor fand sie die neue Tabelle und die neu angelegte alte, sonst nichts:

```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;
```

Führen Sie sie nach jeder Migration aus, in der CI gegen einen frischen `db reset` oder auf Staging. Auf einem gesunden Projekt liefert sie nichts; nach dem 30. ist jede Zeile eine Tabelle, die Ihre App nicht erreicht. Sie prüft Grants auf Tabellenebene, eine Tabelle, die nur über Spalten-Grants geöffnet wurde (`grant select (id, name) on …`), taucht hier also auf, obwohl diese Spalten lesbar sind.

### 3. Welche Migrationen legen eine Tabelle ohne Grant an?

Die Datenbank zeigt nur Tabellen, die existieren. Eine Migration, die Sie noch nicht eingespielt haben, sieht man nur in Ihrem Repo. Dieses grobe grep listet jedes `create table` in `supabase/migrations` ohne `grant … on` für denselben Namen:

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

Das ist Textabgleich, kein SQL-Parsing: Ein Grant, der mehrere Tabellen in einem Statement nennt, oder eine Tabelle, die an einer Stelle ohne Schema und an anderer mit Schema steht, ergibt einen falschen Treffer. Lesen Sie jede ausgegebene Zeile. Tabellen, die nur Servercode über eine direkte Verbindung erreicht, brauchen gar keinen Grant.

### 4. Alles mit den neuen Defaults neu einspielen

Entfernen Sie `auto_expose_new_tables` aus `config.toml`, führen Sie `supabase db reset` aus und lassen Sie Ihre App oder deren Tests dagegen laufen. Das ist Ihr Projekt nach dem 30., plus eine frische Kopie jeder Tabelle, der härtere Fall. Supabase listet betroffene Tabellen auch im Security Advisor und markiert die Data-API-Exposition im Table Editor.

## So beheben Sie es, ohne jede Tabelle zu öffnen

Schreiben Sie Grant, RLS und Policies in die Migration, die die Tabelle anlegt, und geben Sie jeder Rolle nur das, was die App nutzt:

```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);
```

Ich habe diesen Block auf demselben lokalen Stack ausgeführt: Ein angemeldeter Testnutzer fügte eine Zeile ein und las sie zurück (HTTP 201, dann 200), der öffentliche Key ohne Session bekam 42501, und Check 2 von oben listete die Tabelle nicht mehr auf. Das einleitende `revoke all` räumt `TRUNCATE`, `TRIGGER` und `REFERENCES` ab, die mit einer neuen Tabelle kommen, und liefert auf einem noch nicht umgestellten Projekt dasselbe Ergebnis. Geben Sie `anon` nur bei Daten einen Grant, die für nicht angemeldete Besucher gedacht sind, und `service_role` nur, wenn Servercode den Secret Key auf dieser Tabelle nutzt. Wie Grants und Policies in einer ganzen App zusammenpassen, steht in meiner [Sicherheits-Checkliste für Lovable-Apps](https://madsalt.tech/de/blog/lovable-app-security-checklist/).

Was Sie beim ersten 42501 nicht tun sollten:

- **Führen Sie nicht `grant all on all tables in schema public to anon, authenticated` aus.** Das stellt den alten Default für alle Tabellen auf einmal wieder her, auch für jede Tabelle, bei der RLS nie aktiviert wurde, und die werden für jeden lesbar, der Ihren öffentlichen Key hat. Die [42501-Notiz](https://madsalt.tech/de/notes/supabase-42501-permission-denied/) zeigt, warum der Grant das erste Schloss ist und RLS das zweite.
- **Fügen Sie `alter default privileges … grant` nicht wieder hinzu.** Das ist der Rollback der ganzen Änderung.
- **Schalten Sie RLS nicht zum „Testen“ ab und legen Sie den Secret Key nicht ins Frontend.** Der Secret Key überspringt RLS für jeden, der Ihr Bundle öffnet; der Unterschied zwischen den Keys steht in [anon key vs service role key](https://madsalt.tech/de/blog/supabase-anon-key-vs-service-role-key/).

Wenn ein KI-Tool Ihre Migrationen schreibt, geben Sie ihm eine Zeile mit: „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“. Der Hint im Fehler bringt ihm den Grant bei, nie das RLS, das dazugehört.

## Fragen, die Leute zur Änderung stellen

**Funktionieren meine bestehenden Tabellen am 30. Oktober nicht mehr?** Doch. Default Privileges gelten für später angelegte Objekte, und in meinem Lauf antwortete die alte Tabelle nach der Umstellung mit HTTP 200. Die Ausnahme ist eine Tabelle, die Ihre Migrationen löschen und neu anlegen.

**Muss ich etwas tun, wenn ich nie Tabellen anlege?** Nicht am 30. Prüfen Sie, wenn Sie, ein Teammitglied oder ein KI-Tool das nächste Mal eine hinzufügt, und prüfen Sie jede Pipeline, die aus Ihren Migrationen Projekte oder Branches erzeugt.

**Betrifft es Edge Functions?** Ja, wenn sie `supabase-js` mit dem Secret Key auf einer neuen Tabelle nutzen: Auch `service_role` braucht einen Grant. Funktionen, die direkt mit Postgres verbinden, sind nicht betroffen.

**Betrifft es RPC-Funktionen, Storage oder Auth?** Nicht in dieser Änderung. Neue Funktionen bleiben über `PUBLIC` aufrufbar, und `storage` und `auth` sind nicht das Schema `public`.

**Kann ich früher umstellen?** Ja. Die beiden Statements ganz oben sind die Umstellung; führen Sie sie erst auf Staging aus, dann in Produktion. Bestehende Tabellen behalten ihre Grants, eine laufende App merkt also nichts, und die nächste neue Tabelle zeigt sofort, ob Ihre Migrationen bereit sind.

## Wie der Checker das sieht

Der Checker verbindet sich nur lesend und liest die Grants jeder Tabelle in Ihren exponierten Schemas: in welche Tabellen der öffentliche Key ohne Policy schreiben kann, welche Tabellen RLS aus haben und welche Default Privileges noch jeder neuen Tabelle `TRUNCATE`, `REFERENCES` oder `TRIGGER` mitgeben. Außerdem listet er jede Policy auf, deren Rolle für diesen Befehl keinen Grant hat, also die Tabellen, die Check 2 findet, außer denen mit ausgeschaltetem RLS oder mit Spalten-Grants. Migrationsdateien, die Sie nicht eingespielt haben, sieht er nicht, Check 3 von oben bleibt also Ihre Aufgabe. Wenn Sie bei RLS unsicher sind, führt [wie man Supabase RLS testet](https://madsalt.tech/de/blog/how-to-test-supabase-rls/) durch, und die [Notiz zu exponierten Schemas](https://madsalt.tech/de/notes/supabase-exposed-schemas/) deckt andere Schemas als `public` ab, die diese Änderung in Ruhe lässt.
