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.emailhttps://www.kinetic.emailhttp://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 userverifyAdmin()— Authenticated user +admin_userstable 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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
Security Headers
Set via vercel.json:
X-Content-Type-Options: nosniff— Prevents MIME type sniffingX-Frame-Options: DENY— Prevents clickjackingCache-Control— Appropriate caching for static vs dynamic content
Secret Management
- Environment variables stored in Vercel project settings
SUPABASE_SERVICE_ROLE_KEYnever exposed to frontendVITE_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.