Skip to content

fix(whatsapp): require a write role on the WhatsApp write routes - #448

Merged
ArnasDon merged 1 commit into
mainfrom
fix/ghsa-8fv4-vgcc-p8vm-whatsapp-role-checks
Jul 30, 2026
Merged

fix(whatsapp): require a write role on the WhatsApp write routes#448
ArnasDon merged 1 commit into
mainfrom
fix/ghsa-8fv4-vgcc-p8vm-whatsapp-role-checks

Conversation

@ArnasDon

Copy link
Copy Markdown
Owner

Addresses GHSA-8fv4-vgcc-p8vm.

The five write handlers under /api/whatsapp resolved the caller's
account_id from their profile but never checked their role, so account
membership alone was enough to reach them. Every other write surface in the
app (account/, ai/, automations/, flows/, quick-replies/) already
goes through requireRole(); this family was missed when GHSA-34q7-fv77-625j
added those guards.

Each handler now calls requireRole() before any outbound call, at the role
its data class already implied in src/lib/auth/roles.ts and the migration
017 policies:

Route Role
send, react, broadcast agent (canSendMessages)
templates/submit, templates/sync admin (canEditSettings)

Authorization failures now return 401/403 via toErrorResponse rather than
being folded into a generic 500. The two template routes keep their existing
message-surfacing 500 for non-auth errors, so a Meta failure still reports
its own reason.

Tests

Adds role-enforcement coverage to the send route: a viewer is refused with
403 and sendTemplateMessage is never called, while an agent passes through.
Verified failing against the pre-fix code.

Full local run against this branch: eslint 39 warnings / 0 errors
(unchanged from main), tsc --noEmit clean, 654 tests pass, next build
exits 0.

Note

Open PR #403 proposes a canManageTemplates permission that would let
agents manage templates. If that lands, the admin gate on the two template
routes here should be relaxed to match. (#403 also numbers its migration
037, which collides with 037_webhook_broadcast_reliability.sql.)

🤖 Generated with Claude Code

The five write handlers under /api/whatsapp resolved the caller's
account_id straight off their profile row, which only proves account
membership — never a role. Every other write surface in the app
(account/, ai/, automations/, flows/, quick-replies/) already goes
through requireRole(); this family was missed.

RLS did not backstop it, because each of these routes calls Meta
*before* it persists anything:

  - broadcast: makes no database write at all, so there was no policy
    involved on any path
  - send / react: the Meta call precedes the insert, so the outbound
    message or reaction reached the customer and only the local mirror
    was refused
  - templates/submit, templates/sync: push to Meta before upserting the
    local catalog

Gate each handler at the role its data class already implies, matching
lib/auth/roles and the migration 017 policies:

  - send, react, broadcast  -> 'agent'  (canSendMessages)
  - templates/submit, sync  -> 'admin'  (canEditSettings, and the
                                        message_templates_insert/update
                                        policies)

Also switches these handlers to toErrorResponse so an authorization
failure returns 401/403 instead of being folded into a generic 500. The
two template routes keep their existing message-surfacing 500 for
non-auth errors, so a Meta failure still reports its own reason.

Adds role-enforcement coverage to the send route's test: a viewer is
refused with 403 and sendTemplateMessage is never called, while an agent
passes through.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@supabase

supabase Bot commented Jul 29, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project crxylxakpapcfziradbb because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@ArnasDon
ArnasDon merged commit d94bea2 into main Jul 30, 2026
2 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