Overview
Every API response must include CORS headers so browsers allow the frontend to read it. Two parallel implementations exist — one for standalone edge functions, one for the sebuf gateway — but they share the same origin allowlist and logic.Allowed Origins
Both files use the same regex patterns:
Requests from any other origin receive a 403 response when the handler calls
isDisallowedOrigin(req). Requests with no Origin header (server-to-server, curl) are allowed through — the isDisallowedOrigin check only blocks when an origin is present and not on the allowlist.
Adding CORS to a New Edge Function
Every standalone edge function inapi/ must handle CORS manually. Follow this pattern:
- Every response must include
...corsin its headers — including errors, rate-limit 429s, and 500s. - Preflight (
OPTIONS) must return204with CORS headers and no body. getCorsHeaders(req, methods)— pass a custom methods string if the endpoint supports more thanGET, OPTIONS(e.g.,'POST, OPTIONS').
Sebuf Gateway (RPC Endpoints)
RPC endpoints defined in.proto files do not need manual CORS handling. The gateway (server/gateway.ts) calls getCorsHeaders() and isDisallowedOrigin() from server/cors.ts automatically for every request. CORS headers are injected into all responses including error boundaries.
Adding a New Allowed Origin
To allow a new origin:- Add a regex pattern to
ALLOWED_ORIGIN_PATTERNSin bothapi/_cors.jsandserver/cors.ts. - Update the test in
api/_cors.test.mjs. - If the origin is a new production subdomain, also add it to the Cloudflare R2 CORS rules (see MEMORY.md notes on R2 CORS in the repo root).
Allowed Headers
Both implementations allow these request headers:Content-TypeAuthorizationX-WorldMonitor-Key(API key for desktop/third-party access). See API Key Gating for key management details.X-Api-KeyX-Widget-KeyX-Pro-KeyX-WorldMonitor-Desktop-TimestampX-WorldMonitor-Desktop-SignatureIdempotency-KeyMcp-Session-IdMCP-Protocol-VersionLast-Event-ID
Access-Control-Allow-Headers in both files.
Browser-visible response headers exposed via Access-Control-Expose-Headers include Mcp-Session-Id, WWW-Authenticate, Retry-After, X-Billing-Verification, the idempotency headers (Idempotency-Key, Idempotent-Replayed), Location, and both rate-limit header families — the IETF fields (RateLimit, RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset) and the legacy X-RateLimit-* fields (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, plus X-RateLimit-Mode on fail-open limiter degradation) — so MCP clients can continue sessions, re-authenticate, respect backoff hints, self-throttle, tell a retryable billing-verification blip from a terminal lapse, and distinguish degraded limiter grants from healthy ones without parsing the response body. See Error handling and Rate limits.
Railway Relay CORS
The Railway relay (scripts/ais-relay.cjs) has its own CORS handling with the ALLOW_VERCEL_PREVIEW_ORIGINS env var. See RELAY_PARAMETERS.md for details.