Security Model
Defense-in-depth security for the V5 Gateway.
Authentication layers
Tenant auth (hot path)
- Bearer token hashed with SHA-256
- Compared against KV via timing-safe equality
- Suspended tenants rejected immediately
Admin auth
x-admin-keyheader with SHA-256 hash comparison- No CF Access header trust (removed 2026-04-09 — headers are trivially forgeable without CF Access proxy)
- Admin routes restricted to gateway origin via CORS
OAuth 2.1 (MCP clients)
- Dynamic Client Registration with 90-day TTL and IP rate limiting (5/hr)
- PKCE mandatory — only S256,
plainmethod rejected - Auth codes are single-use with 60s TTL
- Access tokens valid for 90 days
CORS policy
| Route group | Allowed origins |
|---|---|
/api/*, /mcp/*, /health/* | Any (API clients don't use CORS) |
/admin/* | Gateway origin only |
/oauth/* | Known MCP consumers (Claude, ChatGPT, Cursor) |
Input validation
- All admin endpoints validate with Zod schemas (
.strict()rejects unknown fields) - MCP tools use Zod with
.strict()— unknown parameters rejected - Recipe parameters validated with Zod schemas
- Request body size limited to 1MB
- API path length limited to 2000 characters
- Prompt injection patterns blocked on
call_apibody
Token security
- Tokens stored in KV with no TTL (managed by DO alarms)
- Refresh tokens stored only in DO SQLite storage (never in KV)
- Access tokens in KV contain no secrets — only the bearer token needed for API calls
- Admin API key stored as SHA-256 hash in Wrangler secrets
- OAuth state parameter signed with HMAC
Rate limiting
- Global: 30 requests/minute per IP
- DCR: 5 registrations/hour per IP
- Admin CORS: restricted to single origin
Pre-commit security gate
Check 16 (scripts/checks/16-security-hardening.ts) blocks commits that:
- Trust
cf-access-authenticated-user-emailwithout JWT validation - Use wildcard CORS (
cors()without origin restriction) - Make fetch calls without AbortController timeout