> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ascendgtm.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Security Model

> Defense-in-depth security for the V5 Gateway.

# 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-key` header with SHA-256 hash comparison
* Admin routes restricted to gateway origin via CORS
* Every admin KV write logged to `kv_audit` D1 table

### OAuth 2.1 (MCP clients)

* Dynamic Client Registration with 90-day TTL and IP rate limiting (5/hr)
* PKCE mandatory — only S256, `plain` method 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_api` body

## 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-email` without JWT validation
* Use wildcard CORS (`cors()` without origin restriction)
* Make fetch calls without AbortController timeout
