fix(automations): scope meta-send contact lookup by user_id

The engine runs with the service-role client (bypassing RLS) and the
public POST /api/automations/engine endpoint accepts contact_id from
the request body. Without a user_id filter on the contacts lookup,
an authenticated user could fire their own automations against
another tenant's contact UUID and send via their own WhatsApp config
to that contact's phone. UUIDs are unguessable so practical risk is
low, but this is cheap defense-in-depth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arnas Donauskas 2026-04-17 21:39:33 +03:00
parent 59d0ea6e17
commit 4afa9bea32
No known key found for this signature in database

View File

@ -52,13 +52,22 @@ type SendInput =
async function sendViaMeta(input: SendInput): Promise<{ whatsapp_message_id: string }> {
const db = supabaseAdmin()
// Scope the contact lookup by user_id. The engine uses the
// service-role client (bypassing RLS), and the public
// /api/automations/engine endpoint accepts contact_id from the
// request body — without this filter, an authenticated user could
// fire their own automations against another tenant's contact UUID
// and send via their own WhatsApp config to that contact's phone.
// Practical risk is low (UUIDs are unguessable) but the check is
// cheap defense-in-depth.
const { data: contact, error: contactErr } = await db
.from('contacts')
.select('id, phone')
.eq('id', input.contactId)
.eq('user_id', input.userId)
.maybeSingle()
if (contactErr || !contact?.phone) {
throw new Error('contact phone missing')
throw new Error('contact not found for this user')
}
const sanitized = sanitizePhoneForMeta(contact.phone)