Skip to content

[Tracking] Supabase prod security and performance review, 2026-09-28 #327

Open
11 of 16 issues completed
Open

Description

@HMarzban

Key: SPR-00 · Type: tracking · Blocked by: none · Checked against: 3bb26de

Purpose

This issue tracks the review of the Supabase prod project on 2026-09-28. The review covered security and performance. It lists the public issues that the review filed, and holds one flag.

On 2026-09-28 at 09:29Z, the Supabase security linter flagged 7 ERROR items on prod (Postgres 15.8.1.073). The root cause is in the repo. The sweeps in packages/supabase/scripts/29-lint-hardening.sql §3, §4, §5 and §8 have no paired migration, so prod never ran them.

Two defaults keep each new prod object open:

  • Prod pg_default_acl (role postgres, schema public) gives anon and authenticated ALL on new tables and EXECUTE on new functions.
  • CREATE FUNCTION also grants EXECUTE to PUBLIC. A REVOKE ... FROM anon does not remove that grant, and anon inherits it.

The source reports are the maintainer's local reports. Each child issue copies the facts it needs, so an agent can work from the repo and the issue alone.

What was applied to prod on 2026-09-28

At about 13:38Z, the HoE (the Head of Engineering agent) applied an emergency fix to prod through the SQL connector. The maintainer approved it. It changed grants and RLS only. It changed no function body.

  • Transaction 1 (tables):
    • RLS was off on document_views_2026_03, _04, _05 and PushSentLog. Now RLS is on for every document_views partition, the parent, document_view_stats, document_views_daily, email_bounces and PushSentLog.
    • anon and authenticated lost every grant on those tables. service_role keeps its access.
    • anon lost every grant on admin_users, so the is_admin revoke below breaks no guest read.
    • anon lost table-level SELECT on public.users. It got a column grant back without email, so guests can still read username.
  • Transaction 2 (functions):
    • 27 functions became service_role only. Examples: purge_document_footprint, consume_push_queue, get_email_bounces, and the four prod-only functions.
    • is_admin was revoked from anon. authenticated keeps it.

Results after the change:

  • Security linter: 0 ERROR (was 7). Lint 0028 fell from 72 to 47 rows.
  • The built-in asserts in both transactions passed. Cron jobs 2, 8, 17, 25 and 28 ran and succeeded afterwards.
  • get_channel_aggregate_data was not touched, so guest chat still works.

Prod now differs from the repo. A fresh db reset or a self-hosted install still starts open. Any later migration that drops and re-creates one of these functions resets its grants to the open defaults. #314 ports the slice into the repo.

Other facts at the time of the review:

  • Migration history: prod history ends at 20260921120000. The repo has three later migrations: 20260922120000 (commit f6ed0a22c), 20260928120000 (commit e4113fd9d) and 20260928130000 (commit 3ef541d8a). Ship the three migrations that prod's history lacks #313 ships them.
  • Performance: all SQL together uses 35.0 s of DB time per hour, 0.97% of one core, in a 69.8-minute prod window. Realtime list_changes takes 78% of that. The database is 62 MB. Supabase bills compute by instance size, so query work saves $0.
  • Next partition: the monthly cron (1st of the month, 00:05 UTC) runs create_document_views_partitions. On 1 October it creates document_views_2027_01 with the open default grants.
    • On prod, the ensure_rls event trigger turns RLS on, so the new partition stays closed. The repo has no such trigger.
    • In the repo, create_document_views_partitions in packages/supabase/scripts/09-document-views.sql (~105) turns RLS on but revokes no grant. The first DO block (~66) does neither. Make new document_views partitions start closed #315 owns both gaps.

The flag

Flag: red. Every issue in the red list is still open.

The red list is #313 and #314. #314 covers a prod state the repo cannot rebuild, and #313 must land before it.

Update the flag when an issue closes. Move to amber only when every issue in the red list is closed.

The issues

Urgent decisions

Security repo fix

Security rulings

Platform

Performance

Records

Tracked privately

Five further security items (SPR-01, SPR-08, SPR-11, SPR-12, SPR-13) follow SECURITY.md. Each is filed here after it is fixed on prod.

Order and dependencies

Dated checks. Each is a read-only [maintainer] step:

Order:

Not yet owned

No issue owns these yet. Green needs a ruling or an owner for each:

What we decided not to do

The review measured and rejected 14 query ideas and parked 4 more. Examples: moving chat postgres_changes to broadcast, the advisor's channel_members (channel_id) index, and a Micro downgrade before memory data exists. The numbers and the reopen triggers are in #326. Do not re-propose one of these ideas without new numbers.

Activity

  1. added this to the v2.2.0 milestone on Oct 18, 2026
  2. HMarzban commented on Oct 19, 2026

    @HMarzban
    CollaboratorAuthor

    Prod check after the 2026-10-19 db push (read-only)

    All 9 migrations are recorded in supabase_migrations.schema_migrations (20261014120100 through 20261014121500, and 20261019120000).

    packages/supabase/scripts/check-grants.sql returns 4 rows. All are default_acl_*, which the guard expects until #316 lands:

    kind obj
    default_acl_anon S:SELECT,UPDATE,USAGE
    default_acl_anon f:EXECUTE
    default_acl_anon r:DELETE,INSERT,REFERENCES,SELECT,TRIGGER,TRUNCATE,UPDATE
    default_acl_authenticated_exec f:EXECUTE

    There are no rls_off, anon_write, anon_read_not_allowed, anon_hidden_column or definer_exec rows. The guard passes.

  3. HMarzban commented on Oct 21, 2026

    @HMarzban
    CollaboratorAuthor

    Status 2026-10-21: the #316 and #320 production reads are done and posted there. Both migrations (20261021120000, 20261021120100) are built, tested locally and committed on main, not pushed yet. db push follows the next push. check-grants.sql also gains a row for the global function default.

  4. HMarzban commented on Oct 21, 2026

    @HMarzban
    CollaboratorAuthor

    Status, 2026-10-21

    Every child planned for v2.2.0 is done. #316 and #320 were the last two. Their migrations went out in today's db push, and packages/supabase/scripts/check-grants.sql returns 0 rows on production.

    Done: #313, #314, #315, #316, #317, #318, #319, #320, #321.

    Open, all in milestone v2.3.0:

    So this tracker moves to milestone v2.3.0 with them. One new finding from today is filed as #570: pgmq and storage still give anon default rights on new objects.

  5. modified the milestones: v2.2.0, v2.3.0 on Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DevOpsSecuritySecurity, access control, and data exposure

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions