Skip to main content

Security Overview

Kinetic Email implements defense-in-depth security across all layers of the stack.

Security Layers

┌─────────────────────────────────────────────┐
│ 1. CORS Restriction │
│ Only kinetic.email + localhost origins │
├─────────────────────────────────────────────┤
│ 2. JWT Authentication │
│ Supabase JWT verified on every request │
├─────────────────────────────────────────────┤
│ 3. Admin Authorization │
│ admin_users table whitelist │
├─────────────────────────────────────────────┤
│ 4. Token Economy │
│ Server-side atomic token spending │
├─────────────────────────────────────────────┤
│ 5. Row-Level Security (RLS) │
│ Database enforces user isolation │
├─────────────────────────────────────────────┤
│ 6. Input Sanitization │
│ XSS prevention on user inputs │
├─────────────────────────────────────────────┤
│ 7. Security Headers │
│ X-Content-Type-Options, X-Frame-Options │
└─────────────────────────────────────────────┘

Key Protections

CORS Enforcement

API endpoints only accept requests from:

  • https://kinetic.email
  • https://www.kinetic.email
  • http://localhost:5173 (development)
  • http://localhost:3000 (development)

Configured in vercel.json headers and setCorsHeaders() function.

Authentication

All API endpoints (except public tracking pixels and blog OG images) require a valid Supabase JWT in the Authorization: Bearer <token> header.

Two auth levels:

  • verifyUser() — Any authenticated user
  • verifyAdmin() — Authenticated user + admin_users table match

Server-Side Token Spending

Token deduction happens atomically on the server via the spend_tokens RPC function. The frontend only performs read-only balance checks for UX feedback. This prevents users from bypassing token costs by calling APIs directly.

XSS Prevention

User-supplied content (like email addresses) is HTML-escaped before insertion into email HTML:

function escapeHtml(str: string): string {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}

Security Headers

Set via vercel.json:

  • X-Content-Type-Options: nosniff — Prevents MIME type sniffing
  • X-Frame-Options: DENY — Prevents clickjacking
  • Cache-Control — Appropriate caching for static vs dynamic content

Secret Management

  • Environment variables stored in Vercel project settings
  • SUPABASE_SERVICE_ROLE_KEY never exposed to frontend
  • VITE_ prefixed vars are safe to expose (anon keys only)
  • API keys rotated through Vercel env var updates

Reporting Security Issues

If you discover a security vulnerability, please report it responsibly via email rather than opening a public issue.