Security

Boundaries

Boundary Enforced by Page

Is this request from the company network?

Vercel WAF rules in vercel.firewall.json: VPN exit IPs only, except machine routes

ops:deployment-and-jobs.adoc#_firewall

Is this a real user?

Supabase Auth session cookies, checked by middleware and every route

Authentication

Is this user allowed here at all?

isAllowedPayabilityEmail() in middleware, plus a Supabase auth hook

Authentication

Which workspace’s data?

resolveSubscription() verifying SubscriptionUser membership

Authorization

Can they do this to the workspace?

Role checks in the members routes

Authorization

Is this machine caller legitimate?

verifyInternalSecret(), MCP_API_SECRET, the Inngest signing key

Secrets and webhook verification

Did this webhook really come from the provider?

Svix, HubSpot HMAC, Nooks secret

Secrets and webhook verification

Non-negotiable rules

Every tenant-scoped query filters on subscriptionId

Isolation is enforced in application code at the repository layer, not by PostgreSQL row-level security. There is no database-level backstop, so a missing filter is a data leak. See Multi-tenancy.

Validate every input with Zod

Route handlers parse bodies and query strings before use. Client-side types are inferred from the same schema, so validation and typing cannot drift.

Never build raw SQL from user input

Prisma parameterises everything. The places that emit raw SQL — fetchAndLockPendingTargets, incrementSendingDomainDailyCount and the BigQuery gate builder — use bound parameters for values, and the BigQuery builder validates identifier names against a strict allowlist because identifiers cannot be parameterised.

The service-role key is server-only

lib/supabase/admin-client.ts is never imported from a client component. Only NEXT_PUBLIC_* variables reach the browser.

Machine secrets fail closed

verifyInternalSecret() rejects every request when INTERNAL_API_SECRET is unset, rather than allowing it through. Scheduled jobs are Inngest cron functions behind the signed /api/inngest endpoint, so there are no cron routes to protect.

Firewall rules change only through vercel.firewall.json

Changes are reviewed in a pull request and applied from main with pnpm firewall:apply. A route that machines call from outside the VPN must be added to the Allow machine routes rule and enforce its own auth, because the bypass makes it reachable from anywhere.

Sanitise user-generated HTML before rendering it

React escapes JSX by default, but email bodies are rendered as HTML. Anything user-authored that reaches dangerouslySetInnerHTML must be sanitised first.

Known gaps

Recorded so they are decided rather than forgotten.

Gap Impact Mitigation today

POST /api/webhooks/event-logger has no signature verification

Anyone who learns the URL can forge Retell call events. The firewall bypasses /api/webhooks/ for all IPs, so the VPN rule does not protect it

The URL is treated as a secret; correlation requires valid campaignId and campaignTargetId metadata, and Call.providerCallId is unique

/api/webhooks/five9 and /api/webhooks/nooks do not verify the caller (the Nooks check is commented out)

Forged events from any IP, because the firewall bypasses /api/webhooks/

None yet; add shared-secret or signature checks

RESEND_WEBHOOK_SECRET unset skips Svix verification

Forged email events in an environment where it is not configured

Set it in every deployed environment; the skip exists for local development

POST /api/webhooks/five9 generates a random eventId

Replays are not idempotent, and rows are stored under source = "Nooks"

The payload is stored only and never dispatched; filter on eventType

No rate limiting on API routes

Brute-force and abuse surface on auth endpoints

Supabase Auth applies its own throttling on sign-in

Configs.key is globally unique, not per workspace

Two workspaces cannot hold different values for the same key

Treat Configs as effectively global until the constraint changes

No PostgreSQL row-level security

A repository method missing its subscriptionId filter leaks across tenants

Review checklist in Multi-tenancy