Skip to content

fix(server): enforce CORS origin policy on /v1/realtime upgrade - #2047

Merged
inureyes merged 3 commits into
mainfrom
fix/issue-2042-realtime-ws-origin
Sep 29, 2026
Merged

inureyes merged 3 commits into
mainfrom
fix/issue-2042-realtime-ws-origin

Conversation

@inureyes

Copy link
Copy Markdown
Member

Summary

  • /v1/realtime now checks the upgrade's Origin against the --cors-origins / --allowed-origins policy (CorsPolicy::permits_websocket_origin). A disallowed origin gets 403 with {"error":{"message":"origin not allowed for /v1/realtime","type":"invalid_request_error"}} before the upgrade, so no engine reservation is taken. An absent Origin is accepted; the default * policy accepts any origin, so default behavior is unchanged.
  • realtime_router takes Arc<CorsPolicy>; build_routes passes the server's policy. The check lives in the route, so router-mode sub-apps (built without the CORS layer) enforce it too. The API-key layer stays outside: failing both checks is 401.
  • One sentence in docs/nemotron-voicechat.md next to the --api-key note.

Deviation from the issue

The issue shows the handler taking ws: WebSocketUpgrade. The handler instead takes Result<WebSocketUpgrade, WebSocketUpgradeRejection> as its last extractor, so the origin check runs before upgrade validation. A disallowed origin is refused even on a malformed handshake, and the in-process app test can reach the 403 through create_app / create_app_without_cors without a hyper upgrade handle.

Test plan

  • cargo test --release --lib server::cors (new websocket_origin_matrix: every policy against absent, allowed, disallowed, null, non-UTF-8)
  • cargo test --release --lib server::routes::realtime (new test over both app builds, including 401 ordering; existing mount/401 test)
  • cargo test --release --test realtime_ws (new start_server_with_policy, disallowed_origin_is_rejected_before_session, localhost/default policy test; all existing tests)
  • fmt, clippy (--lib --tests --examples -D warnings), contract tests, and cargo test --release -p mlxcel --lib server:: pass locally; GitHub CI not awaited (GB10 runner down)

Closes #2042

@inureyes inureyes added status:review Under review type:security Security vulnerability or fix priority:medium Medium priority modeltype:audio Audio or speech model labels Sep 29, 2026
inureyes added a commit that referenced this pull request Sep 29, 2026
@inureyes inureyes added status:done Completed and removed status:review Under review labels Sep 29, 2026
Browsers do not apply CORS to WebSocket upgrades, so a page on any site could open /v1/realtime, take the single session slot, and read every event even when --cors-origins or --allowed-origins restricted browser access.

CorsPolicy::permits_websocket_origin checks a present Origin against the configured rule (any under *, localhost hosts under localhost, byte equality under a literal, membership under --allowed-origins) and accepts an absent Origin, which only non-browser clients send. realtime_router now takes the policy, and the handler answers 403 with an invalid_request_error body before the upgrade, so no reservation is taken. The origin check runs before upgrade validation; the API-key layer stays outside, so failing both checks is still 401. The check lives in the route, so router-mode sub-apps built without the CORS layer enforce it too.

Tests: websocket_origin_matrix covers every policy against absent, allowed, disallowed, null and non-UTF-8 origins; an app-level test covers create_app and create_app_without_cors including the 401 ordering; tests/realtime_ws.rs gains start_server_with_policy and end-to-end handshake tests.

Closes #2042
@inureyes
inureyes force-pushed the fix/issue-2042-realtime-ws-origin branch from 987fa59 to 2fbdee6 Compare September 29, 2026 15:29
@inureyes
inureyes merged commit f5c9415 into main Sep 29, 2026
@inureyes
inureyes deleted the fix/issue-2042-realtime-ws-origin branch September 29, 2026 15:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

modeltype:audio Audio or speech model priority:medium Medium priority status:done Completed type:security Security vulnerability or fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(server): enforce CORS origin policy on /v1/realtime WebSocket upgrade

1 participant