Repository navigation
Carry the production function revokes in the Supabase migrations #397
Description
Activity
- added a parent issue
on Oct 6, 2026 - added a commit that references this issue
on Oct 6, 2026 Fixed in
335138donclaude/youthful-lovelace-3nvsc6: explicit revokes frompublic, anon, authenticatedfor every listed function, and the three script-28 functions moved into migration20261006130000_close_client_write_and_grant_gaps.sql. Their return tables were checked against production and match exactly, socreate or replaceis safe there.Deliberately not done:
- Port the §5–§8 sweep. A blanket revoke on production would hit every DEFINER function outside a hard-coded allowlist.
packages/supabase/CLAUDE.mdrecords that this sweep already brokeget_document_member_previewsandget_document_memberslocally. The explicit list is safer. - CI grant check. CI runs no Supabase stack, so it needs new infrastructure. Run the Verify SQL above by hand after deploy instead.
Also removed
create_direct_message_channelfromuser_facing_namesin29-lint-hardening.sql, so a local reset matches production. Its webapp wrapperapps/webapp/src/api/rpc/createDirectMessageChannel.tsis now unused; that is a separate cleanup.
Generated by Claude Code
- Port the §5–§8 sweep. A blanket revoke on production would hit every DEFINER function outside a hard-coded allowlist.
- added a commit that references this issue
on Oct 6, 2026 - changed the title
[-][Security] Migrations do not carry the function revokes that production has[/-][+]Carry the production function revokes in the Supabase migrations[/+]on Oct 6, 2026 - addedSecuritySecurity, access control, and data exposureSecurity, access control, and data exposureand removedbugSomething isn't workingSomething isn't working
on Oct 6, 2026 - added a commit that references this issue
on Oct 9, 2026 Reopened: the board automation closed this issue before the push. The code is on
mainthrough merge083f37d84(migration20261006130000_close_client_write_and_grant_gaps.sql). It stays open until that migration is applied on prod. Fix-plan steps 3 and 4 were cut by the implementing agent, and that cut still needs a maintainer ruling.Decision, 2026-10-09 (HoE, delegated by the maintainer for today's delivery)
The scope cut is accepted. Fix-plan steps 3 (port the
29-lint-hardening.sql§5–§8 sweep into a migration) and 4 (a CI grant check) are not done. Reasons:- A blanket revoke sweep on prod is risky. It can remove a grant that a live client path needs, and nothing in CI would catch it.
- CI runs no Supabase stack, so a grant check there has nothing to check against.
What shipped instead:
- Migration
20261006130000revokes the explicit list of 14 functions. It was applied to prod on 2026-10-09. packages/supabase/CLAUDE.mdnow has a rule: every new service-roleSECURITY DEFINERfunction revokes its own EXECUTE in its own migration.
The remaining gaps are tracked in #469 (the
internalschema helpers) and #470 (anon default table privileges).
Summary
Production has the correct EXECUTE revokes on sensitive
SECURITY DEFINERfunctions. The repo migrations do not. A fresh deploy frompackages/supabase/migrations/(a self-hoster, a new staging project, or a production rebuild) would expose these functions toanonor to any signed-in user.This issue merges three review items:
get_inactive_users, the push queue functions, andadmin_get_document_member_counts.Production check (2026-10-06, read-only)
has_function_privilegeon production returnsfalsefor bothanonandauthenticatedon every function below. So production is not exploitable today.The gap in the repo
The broad revoke lives only in
packages/supabase/scripts/29-lint-hardening.sql§5–§8. That file runs on a local seed only;packages/supabase/CLAUDE.mdsays "no migration carries it". Hosted Supabase grants EXECUTE on new public functions toanonandauthenticatedby default. So a migration that revokes onlyfrom public, anonleavesauthenticatedopen.purge_document_footprintmigrations/20260715082400_fix_purge_document_views_keying.sql:9-55public,anononlyconsume_push_queue,ack_push_messagemigrations/20260519200000_scripts_functions_triggers_parity.sql:577-624get_inactive_usersscripts/28-ghost-accounts-audit.sql:18-50only (no migration)get_user_deletion_impact,get_ghost_summary_publicscripts/28-ghost-accounts-audit.sql:58,88onlyadmin_get_document_member_countsmigrations/20260519200000_...sql:4478-4500anononly, notpublic; body skips its check whenauth.uid()is nullcreate_direct_message_channelmigrations/20260519200000_...sql:5504-5603authenticatedenqueue_document_view,update_view_durationmigrations/20260610135024_revoke_view_tracking_rpcs_from_browser.sqlanon, authenticatedbut notpublicprocess_document_views_queuemigrations/20260726181000_*.sql:32get_*_media_storage_*(3)migrations/20260623120000_chat_media_attachments.sql:1108,1150,1184publiconlyScript 28 is not in any migration, but the production admin server calls two of its functions (
apps/hocuspocus.server/src/api/services/adminGhostAccounts.service.ts:182,253). So it was applied to production by hand.Fix plan
revoke all on function ... from public, anon, authenticated;andgrant execute on function ... to service_role;.create or replace, so production and the repo match.scripts/29-lint-hardening.sql§5–§8 into a migration. The same rule must then apply to every future DEFINER function, not only this list.db reset: query everyprosecdeffunction inpublicand fail whenanonorauthenticatedholds EXECUTE on a function that is not in theuser_facing_namesallowlist.packages/supabase/scripts/. Then runbun run --filter @docs.plus/supabase_back types.Before you start, check every caller. Each function above is called only with the service role (hocuspocus worker or admin service). Confirm with
grep -rn "<function name>" apps/.Acceptance criteria
db resetfrom migrations only (skip29-lint-hardening.sql), every function in the table returnsfalseforanonandauthenticatedinhas_function_privilege.falsefor all of them after the migration.Verify
Every row with
truemust be a browser RPC that checksauth.uid()in its body.Generated by Claude Code