Skip to content

Harden the web dashboard request surface: origin/host validation and consistent auth across HTTP + WebSocket #397

Description

Summary

The web dashboard (src/conductor/web/server.py) serves an HTTP API and a WebSocket endpoint for the local browser UI. The request path currently has no origin or host validation, and the optional auth token covers only part of the surface. This issue tracks hardening that surface to standard practice for a locally-bound developer server.

This is defense-in-depth work, not a behavior change: the dashboard should continue to work exactly as it does today for the intended local-browser use case.

Current gaps

  1. No origin/host validation. WebDashboard._create_app builds the FastAPI app with no middleware, and no route validates Origin, Referer, or Host. A repo-wide search for add_middleware / CORSMiddleware / TrustedHostMiddleware returns nothing in the request path.

  2. Content type is not constrained on mutating routes. Mutating endpoints parse their body regardless of the declared Content-Type.

  3. Auth coverage is inconsistent. _gate_token_ok is applied to gate resolution only. Other mutating routes and the other WebSocket message types (dialog_message / dialog_decline, iteration_limit_response) are not covered by any equivalent check. Notably, iteration_limit_response resolves the max_iterations safety gate.

  4. The token can't be used from the browser UI. CONDUCTOR_GATE_TOKEN is read from an Authorization header, but the dashboard sends gate responses over the WebSocket (stores/workflow-store.ts), and browsers cannot set request headers on a WebSocket handshake. So enabling the token today breaks the dashboard's own approve/reject buttons — there is no configuration that is both enabled and usable from the UI.

Proposed work

  • Add an origin/host allowlist enforced on all routes, including the /ws handshake — the WebSocket handshake is not covered by the browser's same-origin policy, so it needs the check explicitly rather than inheriting one. Reject requests whose Origin is not the dashboard's own and whose Host is not the bound loopback address and port.
  • Require application/json on mutating routes.
  • Apply consistent auth coverage across every mutating route and every inbound WebSocket message type, rather than gate resolution alone.
  • Make the token usable in-browser: generate a per-run token automatically, deliver it to the page, and accept it on the WebSocket via a query parameter or subprotocol value (since headers aren't available there). Goal is for the protected configuration to be the default and remain fully functional.

Item 1 is the highest-value change and subsumes most of the others; the rest are layered on top so no single check is load-bearing.

Notes

  • --web-port is documented with a fixed port (docs/cli-reference.md), so the listening port is frequently deterministic rather than OS-random.
  • src/conductor/web/replay.py builds a second FastAPI app with the same posture and should get the same treatment.
  • Please keep the existing behavior for the normal local-browser flow — the dashboard and conductor gate respond must both continue to work without extra setup.

Acceptance criteria

  • Requests carrying a foreign Origin are rejected on every route, including the WebSocket handshake.
  • Requests with a Host outside the bound loopback address/port are rejected.
  • All mutating routes and all inbound WebSocket message types enforce the same auth policy.
  • The dashboard UI and conductor gate respond both work end-to-end with protection enabled by default.
  • Regression tests cover each of the above in tests/test_web/.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:webWeb dashboard (server + frontend)enhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions