Skip to content

πŸ€– fix: remediate CSWSH β†’ RCE with origin validation + auth hardening#2418

Merged
ibetitsmike merged 6 commits intomainfrom
mike/security-2h41
Feb 14, 2026
Merged

πŸ€– fix: remediate CSWSH β†’ RCE with origin validation + auth hardening#2418
ibetitsmike merged 6 commits intomainfrom
mike/security-2h41

Conversation

@ibetitsmike
Copy link
Contributor

@ibetitsmike ibetitsmike commented Feb 13, 2026

Summary

Remediates coder/security#120 (Cross-Site WebSocket Hijacking β†’ RCE in Mux app deployments) by enforcing strict same-origin checks at the server boundary, hardening server-mode auth defaults, and preserving required OAuth callback compatibility.

Background

The reported exploit chain relied on browser-initiated cross-origin access to the Mux HTTP/WS API plus unauthenticated exposed deployments.

This PR closes that path by:

  1. Blocking cross-origin HTTP and WS requests for normal API routes.
  2. Requiring auth by default for non-loopback mux server binds.
  3. Allowing only narrowly scoped OAuth callback navigations that must remain cross-origin for standards-compliant providers.

Implementation

1) Origin validation + restrictive CORS (src/node/orpc/server.ts)

  • Removed permissive app.use(cors()).
  • Added expected-origin derivation + normalization helpers.
  • Added global origin middleware:
    • blocks cross-origin non-callback requests with 403.
    • returns restrictive same-origin CORS headers.
  • Added manual WS upgrade gate (noServer + handleUpgrade) to block cross-origin WS handshakes before oRPC handling.

2) OAuth callback safe carveout (src/node/orpc/server.ts)

  • Added explicit allowlist for callback endpoints:
    • /auth/mux-gateway/callback
    • /auth/mux-governor/callback
    • /auth/mcp-oauth/callback
  • Carveout is limited to GET/POST navigation-style callback methods.
  • Rationale in code: callback handlers validate OAuth state before code exchange, so this preserves secure OAuth semantics while keeping the API origin gate strict elsewhere.

3) Forwarded-proto compatibility fix (src/node/orpc/server.ts)

  • Updated expected-origin derivation to respect X-Forwarded-Proto even when only Host is present.
  • This avoids false cross-origin rejections behind common TLS-terminating proxies.

4) Secure-by-default auth for non-loopback binds (src/cli/serverSecurity.ts, src/cli/server.ts)

  • Added resolveServerAuthToken() policy:
    • explicit token wins,
    • loopback host may remain unauthenticated,
    • non-loopback host auto-generates a secure random token.
  • CLI server now uses this helper and prints a bootstrap URL when token is generated.

Validation

  • bun test src/node/orpc/server.test.ts
  • bun test src/cli/serverSecurity.test.ts src/node/orpc/server.test.ts
  • make static-check

All passed locally.

Risks

  • OAuth callback carveout is intentionally narrow (explicit paths + GET/POST only), but future callback route additions must be reflected in the allowlist.
  • Workspace ownership validation was intentionally not added in this PR (out of scope per product direction).

Generated with mux β€’ Model: openai:gpt-5.3-codex β€’ Thinking: xhigh β€’ Cost: $4.46

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5ba60e9390

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Allow cross-origin GET/POST requests only on explicit OAuth callback routes so
response_mode=form_post and POST-preserving redirects continue to work without
opening general CORS access.

Also update expected-origin derivation to honor X-Forwarded-Proto even when
only Host is present, matching common TLS-terminating proxy deployments.
@ibetitsmike
Copy link
Contributor Author

@codex review

Addressed both prior P1 comments:

  • allowed cross-origin OAuth callback POST/GET only on explicit callback endpoints
  • fixed expected-origin derivation to respect X-Forwarded-Proto with Host fallback

Also added/updated tests for both behaviors.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 56195cd9e0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Prevent hostnames like 127.example.com from being misclassified as
loopback, which would skip auth-token generation on non-local binds.
Also handle bracketed IPv6 ([::1]).
@ibetitsmike
Copy link
Contributor Author

@codex review

Addressed P1: isLoopbackHost now validates literal IPv4 addresses (4 numeric octets, 0-255) instead of prefix-matching 127.. Also handles bracketed IPv6 [::1]. Added regression tests for 127.example.com, wrong octet count, and out-of-range octets.

@chatgpt-codex-connector
Copy link

Codex Review: Didn't find any major issues. More of your lovely PRs please.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@ibetitsmike ibetitsmike merged commit 60b1c00 into main Feb 14, 2026
23 checks passed
@ibetitsmike ibetitsmike deleted the mike/security-2h41 branch February 14, 2026 16:06
ibetitsmike added a commit to coder/registry that referenced this pull request Feb 14, 2026
#728)

## Summary

Add per-workspace authentication token wiring to the Mux Coder module,
closing the last-mile deployment gap for cross-site WebSocket hijacking
(CSWSH) protection identified in coder/security#120.

## Background

When Mux runs as a Coder workspace app, it is accessible via Coder's
subdomain proxy (e.g., `mux--ws--user.apps.coder.com`). Without an auth
token, a malicious same-site origin (another user's workspace app on the
same `*.coder.com` domain) can hijack the WebSocket session and execute
arbitrary commands via the oRPC API.

The Mux application itself already implements:
- **Strict same-origin enforcement** for HTTP/CORS and WebSocket
upgrades (coder/mux#2418)
- **Auth token support** β€” the server reads `MUX_SERVER_AUTH_TOKEN` or
`--auth-token`, and the browser frontend extracts `?token=` from the URL
and persists it to localStorage

What was missing was module-level token generation and browser/backend
wiring.

## Implementation

- **`random_password.mux_auth_token`** generates a 64-character token
per module instance.
- **Backend wiring:** `run.sh` launches mux with a process-scoped
`MUX_SERVER_AUTH_TOKEN` environment variable.
- **Frontend wiring:** `coder_app.mux.url` includes `?token=<secret>` so
first launch from Coder passes the token to the browser for
bootstrap/persistence.

To avoid cross-instance breakage, this change intentionally does **not**
use a shared `coder_env` key. Multiple `coder/mux` module instances can
target the same `agent_id` (different `slug`/`port`), and a single
global env key would collide. Process-scoped env keeps each instance's
backend token aligned with its app URL token.

## Validation

- `terraform fmt -check -diff` in `registry/coder/modules/mux`
- `terraform test` in `registry/coder/modules/mux` (8 passed, 0 failed)
- Updated tests now verify the URL token value (not just prefix) and
verify the launch script sets `MUX_SERVER_AUTH_TOKEN` using the
generated token.

---

_Generated with `mux` β€’ Model: `anthropic:claude-opus-4-6` β€’ Thinking:
`xhigh`_

<!-- mux-attribution: model=anthropic:claude-opus-4-6 thinking=xhigh -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant