WebSocket has no authentication: anyone reaching :8080 gets the live event feed and can pose as a worker #38

Open
opened 2026-10-26 12:28:15 -04:00 by jared · 0 comments
Owner

Problem

const wss = new WebSocket.Server({ server }) (server.js:20) has no verifyClient and no upgrade handler. The WebSocket upgrade never goes through authenticateSSO, so any client that can reach :8080 gets a connection. On 2026-10-26 a bare handshake from the LAN got 101 Switching Protocols. Through NPM the same handshake is redirected to Authelia, so this is LAN-only.

Once connected:

  1. Every connection starts as a browser client (browserClients.add(ws)), so it receives every broadcast(): execution_started, workflow_step_*, worker_update, prompt_response, and so on, with no user or group check.
  2. The worker check is opt-in. worker_connect is refused only when an api_key is sent and is wrong: if (api_key && api_key !== process.env.WORKER_API_KEY). A message with no api_key is accepted, and the connection is registered under any existing worker name. It would then receive commands meant for that worker, and the real worker's entry is dropped as "stale".
  3. command_result is accepted from any connection, including ones that never identified as a worker. So execution logs and workflow outcomes can be forged.

Fix

  • Authenticate the upgrade. Use verifyClient or server.on('upgrade') to accept the connection only if one of these holds:
    • it comes from a trusted proxy (same TRUSTED_PROXIES check as #37) with valid Remote-* headers and an allowed group, or
    • it presents the worker key at the handshake, e.g. an Authorization: Bearer header.
  • Require api_key on worker_connect. Treat a missing key the same as a wrong key, and compare with crypto.timingSafeEqual.
  • Only handle command_result / workflow_result from connections that authenticated as a worker, and check that worker_id matches the worker the command was sent to.
  • Fail closed if WORKER_API_KEY is unset. At the moment the startup log only prints "configured: No".

Related: #37 (Remote-* headers trusted from any source).

## Problem `const wss = new WebSocket.Server({ server })` (server.js:20) has no `verifyClient` and no `upgrade` handler. The WebSocket upgrade never goes through `authenticateSSO`, so any client that can reach `:8080` gets a connection. On 2026-10-26 a bare handshake from the LAN got `101 Switching Protocols`. Through NPM the same handshake is redirected to Authelia, so this is LAN-only. Once connected: 1. **Every connection starts as a browser client** (`browserClients.add(ws)`), so it receives every `broadcast()`: `execution_started`, `workflow_step_*`, `worker_update`, `prompt_response`, and so on, with no user or group check. 2. **The worker check is opt-in.** `worker_connect` is refused only when an `api_key` is sent *and is wrong*: `if (api_key && api_key !== process.env.WORKER_API_KEY)`. A message with no `api_key` is accepted, and the connection is registered under any existing worker `name`. It would then receive commands meant for that worker, and the real worker's entry is dropped as "stale". 3. **`command_result` is accepted from any connection**, including ones that never identified as a worker. So execution logs and workflow outcomes can be forged. ## Fix - **Authenticate the upgrade.** Use `verifyClient` or `server.on('upgrade')` to accept the connection only if one of these holds: - it comes from a trusted proxy (same `TRUSTED_PROXIES` check as #37) with valid `Remote-*` headers and an allowed group, or - it presents the worker key at the handshake, e.g. an `Authorization: Bearer` header. - **Require `api_key` on `worker_connect`.** Treat a missing key the same as a wrong key, and compare with `crypto.timingSafeEqual`. - **Only handle `command_result` / `workflow_result` from connections that authenticated as a worker**, and check that `worker_id` matches the worker the command was sent to. - Fail closed if `WORKER_API_KEY` is unset. At the moment the startup log only prints "configured: No". Related: #37 (`Remote-*` headers trusted from any source).
jared added the bugbackendworkerpriority/high labels 2026-10-26 12:28:15 -04:00
Sign in to join this conversation.