Spaces:
Runtime error
Runtime error
File size: 36,005 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 | # Threat Model β OmniRoute (2026-06-18)
**Status**: Authoritative. Supersedes any inline threat notes in code comments.
**Methodology**: STRIDE (Spoofing, Tampering, Repudiation, Information
Disclosure, Denial of Service, Elevation of Privilege) per endpoint.
**Scope**: v1 client API + management surface + agent dispatch + relay.
**Re-evaluation cadence**: every 90 days OR on any new public route
addition / new auth tier change.
**Owner**: security-circle lead.
**Related**: `SECURITY.md` (disclosure policy), `authz-inventory` (live
tier classification), `docs/openapi.yaml` (API surface), `docs/architecture/`
(security boundaries).
---
## 1. Trust boundaries
```
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Public Internet β
β βββ TLS termination (Caddy / cloud LB) β
β β β
β βΌ βββ Trust boundary 1: edge β application β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Next.js App Router (3 replicas behind Caddy LB) β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β per-route authz guards (routeGuard.ts) β β β
β β β ββββββββββ ββββββββββ ββββββββββ ββββββββ β β β
β β β β PUBLIC β βCLIENT β βMANAGE β βALWAYSβ β β β
β β β β /healthβ β /v1/* β β/settn β βPROT β β β β
β β β ββββββββββ ββββββ¬ββββ ββββββ¬ββββ ββββββββ β β β
β β ββββββββββββββββββββΌββββββββββΌβββββββββββββββ β β
β β βΌ βΌ β β
β β βββββββββββββββββββββββββββ ββββββββββββββββββββββββββββ β β
β β β src/lib/sse/handlers β β src/lib/localDb β β β
β β β (translator + chat) β β (sql.js encrypted store)β β β
β β ββββββββββββ¬βββββββββββββββ ββββββββββββββ¬ββββββββββββββ β β
β β β β β β
β β βΌ βββ Trust boundary 2: app β upstream β β
β βββββββββββββββββΌβββββββββββββββββββββββββββββββΌββββββββββββββββββ β
β βΌ βΌ β
β ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ β
β β Provider APIs β β Filesystem (DB + secrets) β β
β β (OpenAI, Anthropic, β¦) β β + Redis (session/quota) β β
β ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
```
**Boundary 1** (edge β app): Mitigated by TLS at the edge + per-route auth gates
(`src/server/authz/routeGuard.ts` + `extractApiKey`/`isValidApiKey` in each
`route.ts`; there is no global Next.js middleware). Anything past this boundary is fully authenticated
unless explicitly in the PUBLIC tier.
**Boundary 2** (app β upstream): Mitigated by per-provider key storage
in `src/lib/vault/` and the per-(token,IP) relay rate limit.
**Local-only boundary** (loopback-only routes): documented in
`src/server/authz/routeGuard.ts` as `LOCAL_ONLY_API_PREFIXES`; known
CVE class is "spawn capable route exposed to non-local traffic"
(GHSA-fhh6-4qxv-rpqj).
---
## 2. STRIDE legend
| Letter | Threat | Question it answers |
| ------ | ---------------------- | ----------------------------------------------------- |
| **S** | Spoofing | Can an attacker pretend to be a legitimate principal? |
| **T** | Tampering | Can an attacker modify data in transit or at rest? |
| **R** | Repudiation | Can a principal deny an action they took? |
| **I** | Information disclosure | Can an attacker read data they shouldn't? |
| **D** | Denial of service | Can an attacker degrade or block service? |
| **E** | Elevation of privilege | Can an attacker gain more access than intended? |
Severity scale: **L**ow (paper), **M**edium (real exploit possible), **H**igh
(active exploitation likely or already seen in the wild).
---
## 3. Per-endpoint STRIDE analysis (top-20 highest-risk routes)
The 50 v1 route handlers are scored below. Top-20 highest-risk routes
get a full STRIDE row; the remaining 30 are covered by a tier-level
summary in Β§ 4.
### 3.1 `/api/v1/responses` β POST (primary inference)
| STRIDE | Risk | Mitigation |
| ------ | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **S** | M β Bearer key stolen β caller identity spoofed | Keys hashed (SHA-256) at rest in `src/lib/db/apiKeys.ts`; revocation list in `src/lib/db/apiKeys.ts#revokeKey` |
| **T** | M β Request body modified in transit | TLS at edge; body re-validated against `ResponsesRequest` schema in `open-sse/handlers/responseSanitizer.ts` |
| **R** | L β Caller denies a request | `src/lib/audit/` writes append-only row with `key_id`, `request_hash`, `model`, `tokens`; hash-chain integrity (DEBT-051 follow-up) |
| **I** | M β Provider API key leaked via response | `open-sse/handlers/responseTranslator.ts` strips `x-api-key` headers before proxying; response redaction middleware |
| **D** | H β High-cost model invocation = $$ DoS | Per-key rate limit (`open-sse/services/rateLimitManager.ts`); per-tenant quota pool (`src/lib/db/quotaPools.ts`); DEBT-001 TPM/TPD reservoir is **not** yet enforced |
| **E** | L β Bearer scope escalation | API keys carry explicit `scopes` array; routes require specific scopes; `requireManagementAuth` enforces management tier |
### 3.2 `/api/v1/relay/chat/completions` β POST (serverless relay)
| STRIDE | Risk | Mitigation |
| ------ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| **S** | M β Relay token theft | Tokens hashed with SHA-256 + per-token ID column; per-(token,IP) rate limit in `src/app/api/v1/relay/chat/completions/route.ts:38-58` |
| **T** | M β Token rotation bypass | Token records have `revoked_at`; check happens before each request |
| **R** | L β Forensic headers sanitized | `sanitizeForensicHeader` strips CR/LF; length cap 256; IP/UA never echoed back to client |
| **I** | H β IP-bucket map leaks | In-memory only, per-instance, capped at 10,000 entries; not persisted; see DEBT-038 (in-memory state loss on restart) |
| **D** | M β Per-(token,IP) abuse | `RELAY_IP_PER_MINUTE=30` default; attacker rotates IPs to spread, hits per-token wall |
| **E** | M β Token scope creep | Tokens carry `allowedModels[]`; reject on model-not-in-list |
### 3.3 `/api/v1/embeddings` β POST
| STRIDE | Risk | Mitigation |
| ------ | ----------------------------------------------------- | ------------------------------------------------------------------------------- |
| **S** | L β Same as 3.1 | Same auth + key model |
| **T** | L β Inputs are vectors; tampered embedding not useful | Re-encode on receive; reject dimension mismatch |
| **R** | L | `src/lib/audit/` row written |
| **I** | M β Embedding vectors can leak training data | Reject requests with PII patterns (regex) before forwarding; DEBT-052 follow-up |
| **D** | L β Embeddings cheaper than chat | Quota pool cap |
| **E** | L | Bearer scope |
### 3.4 `/api/v1/rerank` β POST
| STRIDE | Risk | Mitigation |
| ------ | ---------------------------------- | ------------------------------------------------------------------ |
| **S** | L | Same auth |
| **T** | L | |
| **R** | L | |
| **I** | M β Documents contain user content | Per-tenant key isolation; audit log captures doc hash, not content |
| **D** | L | |
| **E** | L | |
### 3.5 `/api/v1/moderations` β POST
| STRIDE | Risk | Mitigation |
| ------ | ------------------------------------------------ | -------------------------------------------------------------------- |
| **S** | L | |
| **T** | L | |
| **R** | L | |
| **I** | H β Moderation inputs are sensitive user content | Encrypted in transit (TLS); not logged; flag results cached with TTL |
| **D** | L | |
| **E** | L | |
### 3.6 `/api/v1/audio/speech` β POST (TTS)
| STRIDE | Risk | Mitigation |
| ------ | ----------------------------------- | ------------------------------------------ |
| **S** | L | |
| **T** | L | |
| **R** | L | |
| **I** | L β Audio is synthesised from input | Same as 3.1 |
| **D** | M β Long TTS = bandwidth burn | `input` cap 4096 chars; per-key rate limit |
| **E** | L | |
### 3.7 `/api/v1/audio/transcriptions` β POST (STT, multipart)
| STRIDE | Risk | Mitigation |
| ------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **S** | L | |
| **T** | M β Multipart body tampered | `multipart-parser` validates Content-Length + boundary; size cap 25 MB |
| **R** | L | |
| **I** | H β Audio contains user PII / secrets | TTL cache; never written to disk by default; redaction of credit-card-like sequences in `open-sse/executors/chatgpt-web.ts` (per-file TODO, see DEBT-019) |
| **D** | M β Large audio = bandwidth + provider cost | 25 MB cap; rate limit |
| **E** | L | |
### 3.8 `/api/v1/images/generations` β POST
| STRIDE | Risk | Mitigation |
| ------ | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| **S** | L | |
| **T** | L | |
| **R** | L | |
| **I** | L β Generated images are public URLs | URLs are signed with TTL; provider-controlled CDN |
| **D** | M β Image gen is $expensive | Per-key USD spend limits enforced in the request pipeline (`src/lib/db/tokenLimits.ts`, surfaced via `src/app/api/usage/budget/route.ts`) |
| **E** | L | |
### 3.9 `/api/v1/videos/generations` β POST (async)
| STRIDE | Risk | Mitigation |
| ------ | --------------------------------------------- | --------------------------------------------------------------------------------------------- |
| **S** | L | |
| **T** | L | |
| **R** | M β Long-running async job, hard to attribute | Job records include `key_id`, `request_id`, `created_at`; result includes signed URL with TTL |
| **I** | M β Generated video may contain PII | Same as 3.8 |
| **D** | M β Video gen is very $expensive | Cost-budget middleware; max 10-second durations |
| **E** | L | |
### 3.10 `/api/v1/files` β POST (multipart upload)
| STRIDE | Risk | Mitigation |
| ------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------ |
| **S** | M β Uploaded file attributed to wrong key | `key_id` captured at upload; immutable record |
| **T** | H β Malicious file (zip bomb, polyglot) | Size cap 25 MB; MIME sniff + magic-byte check; AV scan is **not** performed (DEBT-053 follow-up) |
| **R** | L | |
| **I** | H β Files contain user PII | Encrypted at rest in `src/lib/vault/`; per-tenant access control |
| **D** | M β Large uploads | Size cap + rate limit |
| **E** | L | |
### 3.11 `/api/v1/files/{id}/content` β GET
| STRIDE | Risk | Mitigation |
| ------ | ----------------------------------------- | -------------------------------------------------------------------------- |
| **S** | H β IDOR (file belonging to other tenant) | Per-tenant scope check; `requireTenantScope` middleware (added in v3.8.25) |
| **T** | L | |
| **R** | L | |
| **I** | H β Tenant isolation breach = data leak | Per-tenant prefix in file storage; cross-tenant read attempt logged + 403 |
| **D** | L | |
| **E** | L | |
### 3.12 `/api/v1/batches` β POST (create batch)
| STRIDE | Risk | Mitigation |
| ------ | ------------------------------------------ | -------------------------------------------------------- |
| **S** | L | |
| **T** | M β Batch input file modified after upload | Content hash stored at upload; mismatch on read = reject |
| **R** | L | |
| **I** | M β Input file contains PII | Same as 3.10 |
| **D** | M β Large batch | 50k row cap per batch |
| **E** | L | |
### 3.13 `/api/v1/batches/delete-completed` β POST
| STRIDE | Risk | Mitigation |
| ------ | --------------------------------------------- | ----------------------------------------------------------------------------------------- |
| **S** | L | |
| **T** | H β Mass delete | Requires manage-scope auth; `requireManagementAuth` enforced; audit log captures `key_id` |
| **R** | L | |
| **I** | M β Output files may leak | Soft-delete with 7-day retention before GC |
| **D** | L | |
| **E** | M β A misconfigured client could trigger this | Auth gate prevents; no anonymous access |
### 3.14 `/api/v1/agents/tasks` β POST (cloud-agent dispatch)
| STRIDE | Risk | Mitigation |
| ------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **S** | L | |
| **T** | H β Prompt injection in task `prompt` | `withInjectionGuard` middleware (per `src/app/api/v1/responses/route.ts:6`); pattern blocklist + sandboxed tool execution |
| **R** | M β Long-running agent task, attribution | Task ID + `key_id` + `created_at` recorded; agent result signed by provider |
| **I** | H β Agent reads user repo / credentials | Provider-scoped credentials; least-privilege OAuth; agent result sandboxed before return |
| **D** | M β Long-running agent = $$ burn | 10-minute default timeout; configurable `timeoutSeconds` |
| **E** | M β Agent tool calls could exceed scope | Tool allowlist per provider; rejection in `open-sse/executors/` |
### 3.15 `/api/v1/agents/credentials` β GET (list stored creds)
| STRIDE | Risk | Mitigation |
| ------ | -------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| **S** | L | |
| **T** | L | |
| **R** | L | |
| **I** | H β Leaked credential metadata enables targeted phishing | Returns only `provider`, `hasValue`, `updatedAt` (NEVER the value); requires manage-scope auth |
| **D** | L | |
| **E** | L | |
### 3.16 `/api/v1/me/status` β GET (caller introspection)
| STRIDE | Risk | Mitigation |
| ------ | ---------------------------------------------------- | ---------------------------------------------- |
| **S** | L | |
| **T** | L | |
| **R** | L | |
| **I** | M β Reveals quota usage; could enable timing attacks | Rate-limited; per-key only; no cross-key reads |
| **D** | L | |
| **E** | L | |
### 3.17 `/api/v1/providers/{provider}/models` β GET
| STRIDE | Risk | Mitigation |
| ------ | ------------------------------ | ------------------------------------- |
| **S** | L | |
| **T** | L | |
| **R** | L | |
| **I** | M β Reveals provider allowlist | Required for client UX; per-key scope |
| **D** | L | |
| **E** | L | |
### 3.18 `/api/v1/web/fetch` β POST (server-side fetch)
| STRIDE | Risk | Mitigation |
| ------ | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| **S** | L | |
| **T** | L | |
| **R** | L | |
| **I** | H β Fetched content could be exfiltrated via prompt injection | Content goes through `withInjectionGuard`; explicit extract modes; URL allowlist (default-deny, see DEBT-054) |
| **D** | M β Recursive fetch = bandwidth burn | Max redirects 3; max 5 MB response; timeout 10s |
| **E** | L | |
### 3.19 `/api/v1/vscode/{token}/v1/chat/completions` β POST (VSCode shim)
| STRIDE | Risk | Mitigation |
| ------ | ------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| **S** | M β VSCode token theft = long-lived impersonation | Tokens are 256-bit, single-purpose, scoped to VSCode-CLI; revokable from `/api/keys/{id}` |
| **T** | L | |
| **R** | L | |
| **I** | M β Token in URL = logged in access logs | Token treated as secret; logs hash + last-4 only |
| **D** | M β Per-token rate limit (lower than API key) | `VSCODECLI_RATE_LIMIT=10` req/min default |
| **E** | L | |
### 3.20 `/api/v1/ws` β WebSocket
| STRIDE | Risk | Mitigation |
| ------ | ----------------------------------------------- | --------------------------------------------------------------------------------------------- |
| **S** | M β WS upgrade lacks standard Bearer header | Subprotocol-based auth: `Sec-WebSocket-Protocol: bearer, <token>`; per-connection scope check |
| **T** | M β WS message frames tampered | TLS-only (`wss://`); reject `ws://` in prod |
| **R** | L | Connection ID + auth principal recorded at handshake |
| **I** | H β Long-lived connection = larger blast radius | 1-hour max connection; heartbeat every 30s; auto-disconnect on auth revoke |
| **D** | H β Many WS = connection table exhaustion | Per-IP WS cap 5; per-key cap 20; rate limit per message |
| **E** | L | |
---
## 4. Tier-level summary (remaining 30 routes)
Routes in the same tier inherit the same baseline mitigations. Per-route
deltas are documented in `docs/openapi.yaml` (request schema constraints).
| Tier | Count | Baseline mitigations | Residual risks |
| ----------------------------------------------------------------------------------------------------------------------------------- | ----- | ---------------------------------------------------------- | ------------------------------------ |
| PUBLIC (`/api/health`, `/api/monitoring/health`, `/_next/*`) | 3 | No auth; rate limit; static response | DoS via flooding (mitigated at LB) |
| CLIENT_API inference (`/v1/audio`, `/v1/moderations`, `/v1/rerank`, `/v1/search`, `/v1/embeddings`, `/v1/responses`, `/v1/relay/*`) | 8 | Bearer auth + per-key rate limit + audit log + cost budget | TPM/TPD fairness (DEBT-001) |
| CLIENT_API files (`/v1/files`, `/v1/files/{id}`, `/v1/batches/*`) | 5 | Bearer auth + per-tenant prefix + AV scan absent | DEBT-053 (AV) |
| CLIENT_API combos/me/providers | 3 | Bearer auth + cached | None material |
| CLIENT_API vscode shim | 13 | Token-scoped + per-token rate limit | Token in URL log noise |
| MANAGEMENT (`/api/settings/*`, `/api/keys/*`, `/api/quota/*`, `/api/usage/*`) | ~60 | Manage-scope + dashboard session + 2FA suggested | See SECURITY.md for P0 items |
| ALWAYS_PROTECTED (shutdown, DB settings) | ~5 | Auth + re-auth for security-impacting changes | None material |
| LOCAL_ONLY (loopback spawn routes) | ~8 | Loopback-only + bypass-constant gated by manage-scope | GHSA-fhh6-4qxv-rpqj class if exposed |
---
## 5. Mitigations to add (next quarter)
| ID | Mitigation | Pillar target | Effort |
| ----- | ---------------------------------------------------------------- | ------------- | ------ |
| TM-01 | DEBT-001: TPM/TPD token-bucket fairness for relay + inference | D, E (cost) | M |
| TM-02 | DEBT-051: Hash-chain integrity for `src/lib/audit/` rows | R | M |
| TM-03 | DEBT-053: AV scan on `/v1/files` upload (ClamAV or external API) | T, I | L |
| TM-04 | DEBT-054: URL allowlist for `/v1/web/fetch` | I, D | M |
| TM-05 | OIDC/SAML SSO for management tier (audit L46) | S | L |
| TM-06 | TOTP MFA for manage-scope users (audit L49) | S, E | M |
| TM-07 | Field-level encryption for PII columns (audit L47) | I | L |
---
## 6. Review log
| Date | Reviewer | Change |
| -------------------- | ------------------------- | ---------------------------------------------------------------------- |
| 2026-06-18 | @KooshaPari/core (L5-118) | Initial STRIDE per endpoint, top-20 highest-risk routes + tier summary |
| 2026-06-25 (planned) | security-circle | Re-score after DEBT-001, DEBT-051 close-outs |
| 2026-09-18 (planned) | security-circle | Quarterly full re-review; refresh mitigations list |
|