Expand description
Route-scoped CORS (DW-027, feature analysis 4.14).
Two phases, both driven by one Cors policy attached to a route:
- Preflight short-circuit (
is_preflight+preflight_response): anOPTIONSrequest carrying BOTHOriginandAccess-Control-Request-Methodon a CORS-configured route is answered 204 by the gateway immediately after route resolution and the route-scoped limit checks — before authentication, authorization, rate limiting, and cap admission, and NEVER proxied upstream. Browsers send preflights without credentials, so gating them on authn would break every credentialed route; they are also pure metadata probes, so admitting them to the concurrency cap would let a hostile page burn slots cheaply. A preflight that fails validation (origin, method, or requested headers not allowed) is still short-circuited — 204 with NO CORS headers, which the browser reads as a failed preflight. A plainOPTIONSwithout the preflight markers is NOT intercepted and proxies normally. A preflight whose routematch.methodslist excludesOPTIONSnever resolves the route at all (404, the documented request-path order) — includeOPTIONSin the method list of CORS routes. - Actual-response decoration (
decorate_actual): responses on the route (every action: proxy, redirect, respond) carryAccess-Control-Allow-Origin(*, or the echoed request origin under a specific list),Access-Control-Allow-Credentials: truewhen configured,Access-Control-Expose-Headerswhen configured, andVary: Origin(merged; seecrate::dataplane::hardening::merge_vary). A request whoseOriginis not allowed gets NO CORS headers — the response passes through unchanged (same-origin and no-cors reads never consult CORS).
Origin matching uses the shared origin grammar in
crate::config::normalize_origin: exact match on the normalized
form (lowercase scheme and host, default port dropped, no userinfo)
or the single config entry *. The config side of that comparison
is precompiled into crate::config::CompiledCorsOrigins at
snapshot-compile time (see crate::snapshot::RouteTable::cors_origins),
so the request path normalizes only the request’s own Origin.
Subdomain wildcards (*.example.com) are deliberately not offered
in v1: an explicit origin list is auditable, a wildcard is not; add
entries per origin until that tradeoff is worth revisiting.
Functions§
- decorate_
actual - Decorate an ACTUAL (non-preflight) response with the route’s CORS
headers (DW-027). No-ops when the request carried no
Originor an origin the policy does not allow.originsis the policy’s snapshot-compiled origin set (seepreflight_response). - is_
preflight - Is this request a CORS preflight (the only request shape the gateway
answers without proxying)? Requires
OPTIONS, anOrigin, and theAccess-Control-Request-Methodmarker the Fetch spec defines for preflights; anything less is a normal request. - preflight_
response - Build the gateway-answered preflight response (DW-027): always 204;
CORS headers only when the preflight validates against the policy.
originsis the policy’s snapshot-compiled origin set (normalized once at compile time, not per request).