From f90a3d1a8e9028172d331174e5431ee53eeea440 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sun, 16 Aug 2026 03:01:57 +0000 Subject: [PATCH] fix(db): restore message sending, broken by the SECURITY DEFINER revoke sweep Every INSERT into public.messages has been failing with "permission denied for function calculate_message_expiration" since #247. The newest row in `messages` is 2026-08-13 and #247 landed on 2026-08-14: nobody has been able to send a message for three days. `set_message_expiration` is the only trigger on `messages` that is not SECURITY DEFINER, so its inner call ran with the writer's privileges. When the sweep revoked EXECUTE, `authenticated` lost the grant 20260203114500 had given it, the trigger raised, and the raise took the INSERT with it. Making the trigger SECURITY DEFINER matches its sibling `trigger_create_message_status`, which was unaffected for exactly this reason, and fixes the outage without re-exposing calculate_message_expiration to client roles the way a plain GRANT back would have. Already applied to production; verified with a rolled-back INSERT as a simulated authenticated session. Co-Authored-By: Claude Opus 5 --- ..._message_send_broken_by_definer_revoke.sql | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 supabase/migrations/20260816150000_fix_message_send_broken_by_definer_revoke.sql diff --git a/supabase/migrations/20260816150000_fix_message_send_broken_by_definer_revoke.sql b/supabase/migrations/20260816150000_fix_message_send_broken_by_definer_revoke.sql new file mode 100644 index 0000000..2c6625c --- /dev/null +++ b/supabase/migrations/20260816150000_fix_message_send_broken_by_definer_revoke.sql @@ -0,0 +1,35 @@ +-- Hotfix: message sending was returning 500 for every user. +-- +-- Postgres logged "permission denied for function calculate_message_expiration" on every +-- INSERT into public.messages. The BEFORE INSERT trigger `set_message_expiration` is the +-- only trigger on that table which is NOT SECURITY DEFINER, so its inner call ran with +-- the privileges of the invoking role. When #247 swept EXECUTE off the SECURITY DEFINER +-- functions, `authenticated` lost the grant that 20260203114500 had given it, the trigger +-- started raising, and the raise took the whole INSERT down with it. +-- +-- Confirmed by the data: the newest row in `messages` was 2026-08-13, and #247 landed on +-- 2026-08-14. Nobody could send a message for three days. +-- +-- The sibling trigger `trigger_create_message_status` already runs SECURITY DEFINER and +-- was unaffected for precisely this reason, so matching it fixes the break without +-- re-exposing `calculate_message_expiration` to client roles -- which is what a plain +-- GRANT back to `authenticated` would have done. +-- +-- Lesson for the next sweep: revoking EXECUTE is not purely a lock-down. Any function +-- reachable from a non-DEFINER trigger is called with the writer's privileges, so a +-- revoke there is a write outage waiting to happen. Check pg_trigger before revoking. + +CREATE OR REPLACE FUNCTION public.set_message_expiration() +RETURNS trigger +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path TO '' +AS $function$ +BEGIN + NEW.expires_at := public.calculate_message_expiration(NEW.conversation_id, NEW.sender_id); + RETURN NEW; +END; +$function$; + +COMMENT ON FUNCTION public.set_message_expiration() IS + 'SECURITY DEFINER so the trigger can call calculate_message_expiration without granting client roles EXECUTE on it.';