From 0e7e79d205fb853038297db36192968a783c916e Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Mon, 1 Jun 2026 23:46:59 +0200 Subject: [PATCH] Sanitize websocket upgrade errors --- services/edge-broker/server.mjs | 6 ++--- services/edge-broker/test/broker.test.mjs | 29 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/services/edge-broker/server.mjs b/services/edge-broker/server.mjs index 535a2a65..826ea9da 100644 --- a/services/edge-broker/server.mjs +++ b/services/edge-broker/server.mjs @@ -575,7 +575,7 @@ export function createBrokerServer(options = {}) { gatewayInfo = await validateAgent({ gatewayId, token, headers: req.headers }); } catch (error) { const status = Number(error?.status) === 403 ? 403 : Number(error?.status) === 401 ? 401 : 503; - rejectUpgrade(socket, status, error?.code || "agent_validation_failed", normalizeErrorMessage(error, "Gateway agent could not be validated."), { + rejectUpgrade(socket, status, error?.code || "agent_validation_failed", "Gateway agent could not be validated.", { stage: "agent_validate", }); return; @@ -642,7 +642,7 @@ export function createBrokerServer(options = {}) { try { session = await validateShellSession({ token, headers: req.headers }); } catch (error) { - rejectUpgrade(socket, Number(error?.status) === 403 ? 403 : 401, error?.code || "shell_session_invalid", normalizeErrorMessage(error, "Shell session could not be validated."), { + rejectUpgrade(socket, Number(error?.status) === 403 ? 403 : 401, error?.code || "shell_session_invalid", "Shell session could not be validated.", { stage: "shell_session_validate", }); return; @@ -743,7 +743,7 @@ export function createBrokerServer(options = {}) { return; } } catch (error) { - rejectUpgrade(socket, 500, "websocket_upgrade_failed", normalizeErrorMessage(error, "WebSocket upgrade failed.")); + rejectUpgrade(socket, 500, "websocket_upgrade_failed", "WebSocket upgrade failed."); return; } diff --git a/services/edge-broker/test/broker.test.mjs b/services/edge-broker/test/broker.test.mjs index f971aeed..d6872190 100644 --- a/services/edge-broker/test/broker.test.mjs +++ b/services/edge-broker/test/broker.test.mjs @@ -95,13 +95,15 @@ test("broker defaults to strict auth and fails closed when manager URL is missin assert.doesNotMatch(shellResponse, /101 Switching Protocols/); assert.match(shellResponse, /^HTTP\/1\.1 401 Unauthorized/m); assert.match(shellResponse, /"error_code":"shell_session_invalid"/); - assert.match(shellResponse, /Edge manager URL is not configured/); + assert.match(shellResponse, /"message":"Shell session could not be validated\."/); + assert.doesNotMatch(shellResponse, /Edge manager URL is not configured/); assert.doesNotMatch(agentResponse, /101 Switching Protocols/); assert.match(agentResponse, /^HTTP\/1\.1 503 Service Unavailable/m); assert.match(agentResponse, /"error_code":"agent_validation_failed"/); assert.match(agentResponse, /"stage":"agent_validate"/); - assert.match(agentResponse, /Edge manager URL is not configured/); + assert.match(agentResponse, /"message":"Gateway agent could not be validated\."/); + assert.doesNotMatch(agentResponse, /Edge manager URL is not configured/); } finally { if (broker) { await broker.close(); @@ -397,11 +399,34 @@ test("broker rejects invalid browser shell upgrades without leaking the token", assert.match(response, /^HTTP\/1\.1 401 Unauthorized/m); assert.match(response, /"error_code":"shell_session_expired"/); + assert.match(response, /"message":"Shell session could not be validated\."/); + assert.doesNotMatch(response, /Shell session expired/); assert.doesNotMatch(response, new RegExp(rawToken)); await broker.close(); }); +test("broker rejects websocket upgrade errors without exposing exception text", async () => { + const broker = createBrokerServer({ + authMode: "stub", + validateBrowserStream: async () => { + throw new Error("UPSTREAM-SENSITIVE: redis://cache.internal:6379 timeout"); + }, + }); + const address = await broker.listen(0); + const port = address.port; + + const response = await rawUpgradeRequest(port, "/ws/browser-gateway-stream?token=session-token"); + + assert.match(response, /^HTTP\/1\.1 500 Internal Server Error/m); + assert.match(response, /"error_code":"websocket_upgrade_failed"/); + assert.match(response, /"message":"WebSocket upgrade failed\."/); + assert.doesNotMatch(response, /UPSTREAM-SENSITIVE/); + assert.doesNotMatch(response, /redis:\/\/cache\.internal/); + + await broker.close(); +}); + test("broker closes browser shell sessions when the agent never reports shell opened", async () => { const closedSessions = []; const broker = createBrokerServer({