fix(supabase): add pgmq_public wrapper migration so local stack starts#717
Conversation
config.toml exposes pgmq_public to PostgREST, but no migration ever created it — it only existed via the hosted Studio "Expose Queues via PostgREST" toggle. A clean `supabase db reset` / newer CLI therefore left PostgREST unable to load its schema cache (schema "pgmq_public" does not exist), aborting the local stack. Capture the canonical Supabase wrapper (schema + pop/send/read/archive/ delete) as a forward-only migration so any fresh checkout works. service_role-only grants per supabase/AGENTS.md deny-by-default; the sole consumer (jobs/src/main.ts) uses the service-role key. Ships a pgTAP test proving anon/authenticated are denied. Prod already has these objects from the dashboard; reconcile prod history with `supabase migration repair --status applied 20260517144641` (do NOT run the SQL on prod).
|
The latest updates on your projects. Learn more about Vercel for GitHub. 7 Skipped Deployments
|
WalkthroughThis PR introduces a new Changespgmq_public API Wrapper with Permissions
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
supabase/tests/pgmq_public_grants_test.sql (1)
45-52: ⚡ Quick winExpand EXECUTE privilege assertions to all granted wrappers.
You grant EXECUTE on five functions in the migration, but this suite validates only two. Add assertions for
pop,send, anddeleteso permission regressions are caught.Suggested test additions
SELECT ok( + has_function_privilege('service_role', 'pgmq_public.pop(text)', 'EXECUTE'), + 'service_role can EXECUTE pgmq_public.pop' +); +SELECT ok( + has_function_privilege('service_role', 'pgmq_public.send(text,jsonb,integer)', 'EXECUTE'), + 'service_role can EXECUTE pgmq_public.send' +); +SELECT ok( has_function_privilege('service_role', 'pgmq_public.read(text,integer,integer)', 'EXECUTE'), 'service_role can EXECUTE pgmq_public.read' ); SELECT ok( has_function_privilege('service_role', 'pgmq_public.archive(text,bigint)', 'EXECUTE'), 'service_role can EXECUTE pgmq_public.archive' ); +SELECT ok( + has_function_privilege('service_role', 'pgmq_public.delete(text,bigint)', 'EXECUTE'), + 'service_role can EXECUTE pgmq_public.delete' +);As per coding guidelines, "Add pgTAP test coverage for every table that alters RLS, permissions, or tenant boundaries...".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@supabase/tests/pgmq_public_grants_test.sql` around lines 45 - 52, The tests only assert EXECUTE for pgmq_public.read and pgmq_public.archive but the migration grants EXECUTE on five wrappers; add pgTAP assertions for the remaining functions by adding SELECT ok(has_function_privilege('service_role', 'pgmq_public.pop(<same signature as in migration>)', 'EXECUTE'), 'service_role can EXECUTE pgmq_public.pop'), SELECT ok(has_function_privilege('service_role', 'pgmq_public.send(<same signature as migration>)', 'EXECUTE'), 'service_role can EXECUTE pgmq_public.send'), and SELECT ok(has_function_privilege('service_role', 'pgmq_public.delete(<same signature as migration>)', 'EXECUTE'), 'service_role can EXECUTE pgmq_public.delete') ensuring you use the exact function signatures used in the migration for pop, send, and delete.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@supabase/migrations/20260517144641_pgmq_public_wrapper.sql`:
- Around line 17-20: Add an explicit revoke for public execute privileges on the
wrapper functions so PUBLIC cannot call them by default: after you finish
creating/granting the wrapper functions in schema pgmq_public (and similarly for
pgmq if wrappers are created there), add a statement to revoke EXECUTE from
PUBLIC, e.g. use REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pgmq_public FROM
PUBLIC to enforce deny-by-default for anon/authenticated; ensure this revoke
comes after the grants that assign service_role privileges so service_role
retains access.
---
Nitpick comments:
In `@supabase/tests/pgmq_public_grants_test.sql`:
- Around line 45-52: The tests only assert EXECUTE for pgmq_public.read and
pgmq_public.archive but the migration grants EXECUTE on five wrappers; add pgTAP
assertions for the remaining functions by adding SELECT
ok(has_function_privilege('service_role', 'pgmq_public.pop(<same signature as in
migration>)', 'EXECUTE'), 'service_role can EXECUTE pgmq_public.pop'), SELECT
ok(has_function_privilege('service_role', 'pgmq_public.send(<same signature as
migration>)', 'EXECUTE'), 'service_role can EXECUTE pgmq_public.send'), and
SELECT ok(has_function_privilege('service_role', 'pgmq_public.delete(<same
signature as migration>)', 'EXECUTE'), 'service_role can EXECUTE
pgmq_public.delete') ensuring you use the exact function signatures used in the
migration for pop, send, and delete.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cd6eeaea-69a1-45b1-a24e-aafd02034e88
📒 Files selected for processing (2)
supabase/migrations/20260517144641_pgmq_public_wrapper.sqlsupabase/tests/pgmq_public_grants_test.sql
| create schema if not exists pgmq_public; | ||
|
|
||
| grant usage on schema pgmq_public to service_role; | ||
| grant usage on schema pgmq to service_role; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether this migration explicitly revokes PUBLIC privileges.
rg -n "revoke\\s+all\\s+on\\s+schema\\s+pgmq_public\\s+from\\s+public|revoke\\s+execute\\s+on\\s+all\\s+functions\\s+in\\s+schema\\s+pgmq_public\\s+from\\s+public|grant\\s+execute\\s+on\\s+function|grant\\s+usage\\s+on\\s+schema\\s+pgmq_public" supabase/migrations/20260517144641_pgmq_public_wrapper.sqlRepository: gridaco/grida
Length of output: 140
🏁 Script executed:
cat -n supabase/migrations/20260517144641_pgmq_public_wrapper.sqlRepository: gridaco/grida
Length of output: 4328
🌐 Web query:
PostgreSQL default permissions schema function PUBLIC EXECUTE
💡 Result:
In PostgreSQL, functions are created with the EXECUTE privilege granted to PUBLIC by default [1]. To change this behavior for future functions, you must use the ALTER DEFAULT PRIVILEGES command [2][3]. Crucially, if you want to revoke this default PUBLIC EXECUTE privilege, you must do so globally (without specifying an IN SCHEMA clause) [1][4][5]. Per-schema default privileges are additive to global default privileges; therefore, attempting to revoke privileges in a specific schema will not override the global default that grants EXECUTE to PUBLIC [4][5]. To revoke the default PUBLIC EXECUTE privilege for functions created by a specific role, use the following command: ALTER DEFAULT PRIVILEGES FOR ROLE role_name REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC; If you omit the FOR ROLE clause, the command applies to the current role [3][6]. Once this global revocation is in place, you can selectively grant EXECUTE privileges to specific roles or schemas as needed [1]. Note that ALTER DEFAULT PRIVILEGES only affects objects created after the command is executed and does not change privileges on existing functions [2][3].
Citations:
- 1: https://www.cybertec-postgresql.com/en/postgresql-alter-default-privileges-permissions-explained/
- 2: https://www.postgresql.org/docs/12/sql-alterdefaultprivileges.html
- 3: https://www.postgresql.org/docs/current/sql-alterdefaultprivileges.html
- 4: https://stackoverflow.com/questions/40294881/how-to-revoke-execute-privileges-on-functions-in-postgresql-by-default
- 5: https://www.postgresql.org/docs/11/sql-alterdefaultprivileges.html
- 6: https://dba.stackexchange.com/questions/117109/how-to-manage-default-privileges-for-users-on-a-database-vs-schema
Add explicit REVOKE statements to prevent PUBLIC access to functions.
Functions in PostgreSQL are created with EXECUTE granted to PUBLIC by default. This migration creates wrapper functions without revoking the default PUBLIC EXECUTE privilege, which contradicts the stated intent ("deny-by-default for anon/authenticated"). Add REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pgmq_public FROM PUBLIC; after the function grants to enforce deny-by-default at the DDL level.
Suggested fix
grant execute on function
pgmq_public.pop(text),
pgmq_public.send(text, jsonb, integer),
pgmq_public.read(text, integer, integer),
pgmq_public.archive(text, bigint),
pgmq_public.delete(text, bigint)
to service_role;
+revoke execute on all functions in schema pgmq_public from public;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@supabase/migrations/20260517144641_pgmq_public_wrapper.sql` around lines 17 -
20, Add an explicit revoke for public execute privileges on the wrapper
functions so PUBLIC cannot call them by default: after you finish
creating/granting the wrapper functions in schema pgmq_public (and similarly for
pgmq if wrappers are created there), add a statement to revoke EXECUTE from
PUBLIC, e.g. use REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA pgmq_public FROM
PUBLIC to enforce deny-by-default for anon/authenticated; ensure this revoke
comes after the grants that assign service_role privileges so service_role
retains access.
Summary
Local
supabase start/db resetaborts withError status 503because PostgREST cannot load its schema cache:supabase/config.tomlexposespgmq_publicto PostgREST, but no migration ever created that schema — it only existed on the hosted project via the Studio "Expose Queues via PostgREST" toggle, and on whoever-set-it-up's old local DB volume. A newer Supabase CLI / clean volume replays repo migrations only, so the always-missing schema is now exposed and blocks every fresh checkout.This adds a forward-only migration capturing the canonical Supabase
pgmq_publicwrapper (schema +pop/send/read/archive/delete), mirroring what the hosted dashboard generates so local == prod.service_role-only persupabase/AGENTS.mddeny-by-default. The sole consumer,jobs/src/main.ts, uses the service-role key;anon/authenticatedget no schemaUSAGE.create schema if not exists,create or replace function) and forward-only.service_roleallowed,anon/authenticateddenied).References
Known upstream Supabase gap:
pgmq_publicis created by the Studio "Expose Queues via PostgREST" toggle, not by migrations, so it is absent on clean local / self-hosted databases.pgmq_publicwrapper SQL this migration mirrorsProd already has these objects (created by the dashboard). Do not let CI run this migration's SQL on prod —
create or replace functionwould overwrite the dashboard-generated definitions and hard-fail on any return-type drift. Instead reconcile history only:This records the version as applied without executing the SQL, closing the drift while leaving prod's working
pgmq_publicuntouched.Test plan
supabase stop && supabase start— full clean migration replay succeeds; stack healthySchema cache loadedwith zeropgmq_public does not existerrorssupabase db test— PASS, 146/146 across 8 suites incl. newpgmq_public_grants_test(no regressions)migration repairabove against prod (do NOTdb pushthe SQL)