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:
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.
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".
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).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
const wss = new WebSocket.Server({ server })(server.js:20) has noverifyClientand noupgradehandler. The WebSocket upgrade never goes throughauthenticateSSO, so any client that can reach:8080gets a connection. On 2026-10-26 a bare handshake from the LAN got101 Switching Protocols. Through NPM the same handshake is redirected to Authelia, so this is LAN-only.Once connected:
browserClients.add(ws)), so it receives everybroadcast():execution_started,workflow_step_*,worker_update,prompt_response, and so on, with no user or group check.worker_connectis refused only when anapi_keyis sent and is wrong:if (api_key && api_key !== process.env.WORKER_API_KEY). A message with noapi_keyis accepted, and the connection is registered under any existing workername. It would then receive commands meant for that worker, and the real worker's entry is dropped as "stale".command_resultis accepted from any connection, including ones that never identified as a worker. So execution logs and workflow outcomes can be forged.Fix
verifyClientorserver.on('upgrade')to accept the connection only if one of these holds:TRUSTED_PROXIEScheck as #37) with validRemote-*headers and an allowed group, orAuthorization: Bearerheader.api_keyonworker_connect. Treat a missing key the same as a wrong key, and compare withcrypto.timingSafeEqual.command_result/workflow_resultfrom connections that authenticated as a worker, and check thatworker_idmatches the worker the command was sent to.WORKER_API_KEYis unset. At the moment the startup log only prints "configured: No".Related: #37 (
Remote-*headers trusted from any source).