Skip to content

fix(db): restore message sending, broken by the SECURITY DEFINER revoke sweep - #255

Merged
ralyodio merged 1 commit into
masterfrom
hotfix/message-send-permission
Aug 16, 2026
Merged

fix(db): restore message sending, broken by the SECURITY DEFINER revoke sweep#255
ralyodio merged 1 commit into
masterfrom
hotfix/message-send-permission

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Message sending has been completely broken in production for three days. Already applied to prod and verified; this commits the migration so the repo matches.

What happened

Every INSERT into public.messages failed with:

permission denied for function calculate_message_expiration

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 #247 swept EXECUTE off the SECURITY DEFINER functions, authenticated lost the grant that 20260203114500 had explicitly given it. The trigger raised, and the raise took the whole INSERT with it.

Evidence

The fix

Make set_message_expiration SECURITY DEFINER, matching its sibling. This restores sending without re-exposing calculate_message_expiration to client roles, which is what a plain GRANT ... TO authenticated would have done.

Verified with a rolled-back INSERT as a simulated authenticated session: insert_ok=t.

Lesson for the next revoke 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 revoking there is a silent write outage. Check pg_trigger before revoking — the failure surfaces as an application 500, not as a security finding.

🤖 Generated with Claude Code

…ke 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 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

91 finding(s)

HIGH/CRITICAL: 5 | MEDIUM: 10 | LOW: 76

Severity Rule Location
HIGH secret-generic-credential scripts/supabase-email.sh:21
HIGH secret-generic-credential scripts/supabase-twilio.sh:32
HIGH secret-generic-credential scripts/supabase-twilio.sh:47
HIGH js-host-header-trust src/lib/websocket/middleware/auth.js:31
HIGH js-cors-origin-reflected src/lib/websocket/server.js:121
MEDIUM manifest-install-lifecycle-script package.json:33
MEDIUM js-shell-exec-interpolation scripts/convert-routes.mjs:9
MEDIUM redos-nested-quantifier src/app/api/profile/update/route.js:73
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:38
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:66
MEDIUM js-unescaped-html-sink src/app/faq/page.jsx:57
MEDIUM js-unescaped-html-sink src/app/layout.jsx:138
MEDIUM js-unescaped-html-sink src/app/layout.jsx:142
MEDIUM js-unescaped-html-sink src/app/page.jsx:47
MEDIUM js-unescaped-html-sink src/lib/components/chat/MessageItem.jsx:60
LOW secret-generic-credential src/app/api/auth/invite-anon/route.test.js:24
LOW secret-generic-credential src/app/api/auth/register-anon/route.test.js:32
LOW secret-jwt src/app/api/auth/upload-avatar/route.test.js:27
LOW secret-generic-credential src/app/api/auth/upload-avatar/route.test.js:27
LOW secret-generic-credential src/app/api/profile/update/route.test.js:63
LOW secret-generic-credential src/lib/websocket/middleware/auth.test.js:47
LOW secret-generic-credential src/lib/websocket/middleware/auth.test.js:64
LOW secret-generic-credential tests/auth-key-generation.test.js:109
LOW secret-generic-credential tests/auth-key-generation.test.js:110
LOW secret-generic-credential tests/auth-key-generation.test.js:157
LOW secret-generic-credential tests/auth-key-generation.test.js:158
LOW secret-generic-credential tests/auth-key-generation.test.js:207
LOW secret-generic-credential tests/auth-key-generation.test.js:208
LOW secret-generic-credential tests/auth-key-generation.test.js:250
LOW secret-generic-credential tests/auth-key-generation.test.js:251
LOW secret-generic-credential tests/auth-key-generation.test.js:293
LOW secret-generic-credential tests/auth-key-generation.test.js:294
LOW secret-generic-credential tests/auth-key-generation.test.js:340
LOW secret-generic-credential tests/auth-key-generation.test.js:341
LOW secret-generic-credential tests/auth-key-generation.test.js:381
LOW secret-generic-credential tests/auth-key-generation.test.js:382
LOW secret-generic-credential tests/chat-archive.test.js:22
LOW secret-jwt tests/debug-sms.js:10
LOW secret-generic-credential tests/gpg-private-key-export.test.js:63
LOW secret-generic-credential tests/gpg-private-key-export.test.js:64
LOW secret-generic-credential tests/nuclear-delete.test.js:19
LOW secret-generic-credential tests/nuclear-delete.test.js:89
LOW secret-generic-credential tests/nuclear-delete.test.js:97
LOW secret-generic-credential tests/password-security-fix-verification.test.js:51
LOW secret-generic-credential tests/password-security-fix-verification.test.js:75
LOW secret-generic-credential tests/password-security-fix-verification.test.js:76
LOW secret-generic-credential tests/password-security-fix-verification.test.js:102
LOW secret-generic-credential tests/password-security-fix-verification.test.js:156
LOW secret-generic-credential tests/password-security-fix-verification.test.js:174
LOW secret-generic-credential tests/phone-auth-fix.test.js:49

…and 41 more. Full results in the Security tab.

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit b3f1c05 into master Aug 16, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant