Skip to Content

v0.1.98 — 2 Oct 2026

This release contains security fixes. Upgrade is recommended for every deployment. Two of them change behavior:

  • The anon key no longer reaches any data. A client that calls PostgREST directly with the anon key now gets 401 or 403. The Studio and the API are not affected.
  • Administrator access comes only from grants at the root. A user whose administrator access came only from a grant at a tenant loses it. See Administrator access granted at a tenant applied everywhere.

Security — the anon key was able to disable row-level security and read any table

Who is affected: Every deployment. The anon key is public, so no account was needed.

Every browser gets the anon key. Supabase gives the anon role EXECUTE on each new database function, and PostgreSQL gives it to PUBLIC. The DaaS migrations did not revoke these grants. With the anon key only, a caller was able to:

  • call remove_rls_from_collection on any table. The function drops all policies of the table and disables row-level security. On daas_users, that opened every row to the anon key.
  • call execute_aggregate on any table. The function skips row-level security, and a group_by on a column returned all distinct values of that column, for example every email address in daas_users.
  • call change_table_owner, apply_rls_to_collection, and list_rls_policies, which change table ownership, change policies, or show policy definitions.
  • read, change, and delete the rows of daas_collections, the collection metadata.

Now only the service role can execute these functions, and the application calls them only from the server. Signed-in users can read daas_collections, and only administrators can change it.

If your deployment allowed self sign-up before this release, check your database logs for calls to /rpc/remove_rls_from_collection and /rpc/execute_aggregate.


Security — the anon key was able to read through public grants

Who is affected: Deployments with a public policy grant, that is, a policy attached with no role and no user.

Many policies on system tables applied to every database role, anon included. When a public grant exists, the permission functions that those policies call answer “yes” for a caller with no user. The anon key was therefore able to read rows through a public grant, for example from daas_files, daas_settings, or daas_activity.

DaaS serves no data to a caller that is not signed in. A public grant means “all signed-in users”, and that does not change. Every policy now applies to signed-in users only. The anon role can execute no function and holds no privileges on the tables, views, and sequences of the public schema, and new tables and views do not give it any.


Security — aggregates ignored tenant scope, field permissions, and concealed fields

Who is affected: Deployments that use tenant scopes, field-level read permissions, or concealed fields such as static tokens.

GET /api/items/:collection?aggregate=… and the aggregate action of the MCP items tool checked the read permission for the collection and applied the item-level permission filter. They missed three checks:

RequestBeforeNow
Count at tenant ACounted the rows of every tenantCounts tenant A only (and its children with downward inheritance)
groupBy or max on a field the user cannot readReturned the values403
groupBy on a concealed field, such as a static tokenReturned the values unmasked, also to administrators403 for every user

Aggregates now apply the same scope filter as list reads, including missing_uri_mode = reject.


Security — administrator access granted at a tenant applied everywhere

Who is affected: Deployments that assign the Administrator role, or an admin policy, to a user or role at a tenant.

The functions that decide administrator and delegate access ignored the resource URI of a grant. An admin policy that reached a user through a grant at one tenant made that user an administrator at the root and in every other tenant, and the application then skipped the tenant check for that user. Two ordinary Studio actions caused it:

  • the Administrator role assigned to a user at a tenant;
  • an admin policy granted to a role or a user at a tenant.

Administrator access now needs every link of the grant to be at the root. Delegate access follows the same rule. The users list and GET /api/roles/me use the same rule.

A user whose administrator access came only from a grant at a tenant loses it. To let a user manage one tenant, grant a non-admin policy, with permissions on the collections of that tenant, at that tenant.


Security — importing a file from a URL reached private addresses

Who is affected: Every deployment where users can create files.

POST /api/files/import fetched any http or https URL and followed redirects. Any caller who can create a file was able to make the server read a private address, such as the loopback interface, a private network, or the cloud metadata address 169.254.169.254. The answer was stored as a file that the same caller can download.

The import now refuses loopback, private, link-local, carrier-grade NAT, multicast, and reserved addresses with 400 IMPORT_URL_REFUSED. The address is judged at the moment the socket connects, so DNS rebinding cannot swap it. Redirects are followed by hand, at most five, and every hop is judged again.

If your deployment imports files from an internal host on purpose, set STORAGE_IMPORT_ALLOWED_NETWORKS to the addresses or CIDR prefixes to allow:

STORAGE_IMPORT_ALLOWED_NETWORKS=10.20.0.0/16,192.168.5.10

The variable is not listed in docker-compose.yml. Add it to the environment list. See environment variables.


Security — database functions have a fixed search path

Who is affected: Every deployment. No action is needed.

25 database functions had no search_path setting, and eight of them run with the rights of their owner. The Supabase Security Advisor showed them as “Function Search Path Mutable”. Every function in the public schema now has search_path = public, pg_temp. The advisor warnings clear after the upgrade.


Fixed — a new column or table answered PGRST204 right after it was created

Who is affected: Anyone who creates a field, a collection, or a relation and uses it in the next request.

The database commits a schema change at once, but PostgREST rebuilds its schema cache a moment later. A request that named the new column in that window failed with PGRST204, “Could not find the column … in the schema cache”, although the column existed. The API now waits until PostgREST can serve the change, for at most 3 seconds, before it answers. On a six-table schema, a new table became readable in 120 to 270 ms.


Who is affected: Many-to-many relations where both foreign keys of the junction table point to the same collection.

Such a read failed with “Could not embed because more than one relationship was found”. Embedded selects now name the foreign key column in both directions, for every many-to-many relation, and a filter on the alias no longer adds a second embed.


Fixed — a field created through MCP now gets its relation metadata

Who is affected: Anyone who creates many-to-one fields through the MCP fields tool.

The tool created the column with its own SQL, so a many-to-one field got no foreign key and no relation metadata. Lookups that read the schema did not find the field, and workflow_instance stayed empty even for an item whose workflow was progressing correctly. The tool now uses the same service as the Studio. It also reuses a foreign key that is already on the column, reports a foreign key that failed instead of reporting success, and treats meta.required as NOT NULL.


Fixed — other

  • Field types are applied on read. The read transforms never ran, so a csv field returned its stored comma string, such as "a,b", instead of ["a", "b"]. cast-json and cast-boolean had the same problem.
  • A csv field stores a comma string. An array is now joined for a field on a plain text column, and kept for a field on an array column. New csv fields get the cast-csv transform automatically.
  • The input-tags and input-map interfaces use the correct default. Interfaces saved under those names fell through to the default for their type.

Changed

  • Next.js 16.3.6. Next.js and eslint-config-next are upgraded to 16.3.6.
  • Buildpad UI components upgraded to registry 2.6.0. Relation dropdowns and the form field interface pick up their upstream fixes. A new BUILDPAD_UI_UPGRADE_GUIDE.md describes how to run these upgrades for this project.

Internal

A new test suite talks to the database with the anon key only and fails if the anon role can execute any function or read any table. It reads status codes and row counts only. It also covers aggregates for tenant scope, concealed fields, and field-level permissions.