57d757192e
* fix(security): revoke anon EXECUTE on SECURITY DEFINER write RPCs
Supabase's bootstrap ALTER DEFAULT PRIVILEGES grants EXECUTE on every
public-schema function to PUBLIC, anon, authenticated and service_role, and
PostgREST publishes each one at /rest/v1/rpc/<name>. Seven SECURITY DEFINER
functions were therefore unauthenticated cross-tenant primitives that bypass
RLS for anyone holding the public anon key:
sync_team_to_company INSERTs into company_members, the table
user_company_ids() and every RLS policy read
claim_due_webhook_deliveries returns every tenant's webhook payloads
generate_delivery_note_number UPDATEs company_settings, needs only a company id
generate_article_number UPDATEs company_settings and articles
generate_invoice_number UPDATEs company_settings and invoices
peek_next_invoice_number leaks another tenant's prefix and next number
get_next_arrival_number leaks another tenant's ankomstnummer series
The last three carried a guard, and it did not hold. Its shape is
"IF auth.uid() IS NOT NULL AND NOT EXISTS (membership) THEN RAISE", with a
comment explaining that a NULL auth.uid() means service role or cron and is
trusted. The anon key's JWT carries no sub claim, so auth.uid() is NULL for
role anon as well, and the guard short-circuits straight into the trusted
branch.
Revokes EXECUTE from PUBLIC and anon on all seven, and from authenticated on
the two with no user-session caller. PUBLIC is mandatory: proacl carries
"=X/postgres", so revoking from anon alone leaves has_function_privilege true.
The five numbering RPCs the app calls on the user's session client are
re-granted to authenticated only after their guard was replaced with a
fail-closed one. A sweep then revokes PUBLIC and anon from every remaining
definer writer in public; all 20 carry explicit authenticated and service_role
grants, verified against prod, so no signed-in or service path changes.
tests/pg/definer-function-grants.pg.test.ts generates its assertion from that
same sweep rather than a hand list, because hand-listing is exactly how the
three guarded numbering RPCs came to be declared safe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ifKg6Ec67A39oxfGPU1yc
* fix(security): keep procedures out of the definer-writer sweep
Review finding (CodeRabbit, Major): the sweep predicates excluded only trigger
return types, so a SECURITY DEFINER PROCEDURE would match and the migration
would then run REVOKE ... ON FUNCTION against it, which Postgres rejects,
aborting the whole migration. The pg test's offender query had the same gap and
would have reported a procedure the migration could not fix.
public holds no procedures today (verified against prod 2026-09-01), so this is
a guard against the first one anyone adds rather than a live bug.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016ifKg6Ec67A39oxfGPU1yc
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>