Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

CORS settings

The [cors] section: the browser handshake for cross-origin calls to any route on the instance.

Synopsis

[cors]
allowed_origins = ["*"]
additional_allowed_headers = []
additional_exposed_headers = []
allow_credentials = false
# max_age_secs = …   # no default

Description

CORS is instance-level. It cannot be configured per channel: preflights are answered before routing, so no channel is known yet. The per-channel origin_allow_list is a server-side origin check, not CORS.

Options

SettingDefaultEnv varWhen to change
cors.allowed_origins["*"]ORION_CORS__ALLOWED_ORIGINSList explicit origins before production; comma-separated in the env var.
cors.additional_allowed_headers[]ORION_CORS__ADDITIONAL_ALLOWED_HEADERSYour browser client sends a custom request header (deviceid, a tenant id, a trace header).
cors.additional_exposed_headers[]ORION_CORS__ADDITIONAL_EXPOSED_HEADERSA page script needs to read a response header, for example set-cookie on login.
cors.allow_credentialsfalseORION_CORS__ALLOW_CREDENTIALSCookie-based cross-origin sessions. Requires explicit allowed_origins.
cors.max_age_secsORION_CORS__MAX_AGE_SECSCache preflights to cut their volume. Unset omits the header; capped at 86400.

Exactly ["*"] is permissive CORS and is rejected at startup when environment starts with prod. Mixing "*" into a list of explicit origins is always a config error — it used to pass validation and then panic at router build.

The two header lists are additive. They extend the built-in sets; they cannot narrow them. Allowed: content-type, authorization, accept, x-api-key, idempotency-key, x-request-id. Exposed: x-request-id, retry-after. A replacing key would let an operator adding deviceid silently drop authorization and content-type. That would break admin auth, the dedup guard and every browser JSON call, with no server-side error anywhere. Header names are lowercased (deviceIddeviceid), which is correct: HTTP header names are case-insensitive and the preflight match is byte-lowercase. An entry that is not a valid header name is a startup error, not a silently dropped one.

allow_credentials requires explicit origins. The Fetch spec forbids pairing Access-Control-Allow-Credentials: true with Access-Control-Allow-Origin: *, and the browser rejects the response. The underlying layer asserts on the combination at router construction, so it would crash the process at boot. Orion refuses it during config validation instead. A "*" entry in either header list is refused for the same reason: it silently converts the explicit list back into a wildcard.

Note

Orion sends the explicit allow-headers list on every configuration, including the wildcard-origin default. It previously sent Access-Control-Allow-Headers: * there, and per the Fetch Standard Authorization is a CORS non-wildcard request-header name that * never covers, so a browser calling the admin API with a bearer token failed preflight on a default install. Sending the list is strictly widening.

Last verified 14 September 2026