T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- relayClient.ts:64
- Finding
- Privileged Relay Commands Are Processed Before Authentication<![CDATA[ ## Vulnerability Details **File Location**: `relayClient.ts:64-75, 89-108` **Vulnerability Type**: Missing authentication-state enforcement **Risk Level**: High ### Vulnerable Code ```ts this.ws.onmessage = (event) => { try { const msg = JSON.parse(String(event.data)); this.handleMessage(msg); } catch { console.error("[private-bridge] Invalid message received"); } }; private handleMessage(msg: { type: string; data?: unknown }): void { switch (msg.type) { case "hello_ok": console.log("[private-bridge] Authenticated"); this.backoff = INITIAL_BACKOFF; this.connectionState = "online"; this.startHeartbeat(); break; case "prompt": case "status": case "restart": case "workflow": this.dispatch(msg as { type: string; data: IncomingMessage }); break; case "error": console.error("[private-bridge] Relay error:", msg.data); break; default: console.warn(`[private-bridge] Rejecting unknown type: ${msg.type}`); break; } } ``` ### Technical Analysis The client marks the connection as authenticated only after receiving `hello_ok`, but command dispatch does not verify that `connectionState` is `online` or that authentication has completed. Consequently, recognized command messages are passed to `dispatch()` immediately, including while the client is still in the `reconnecting` state and before the relay has accepted the authentication token. The affected commands invoke sensitive runtime capabilities: - `prompt` invokes `runtime.executePrompt()`. - `workflow` invokes `runtime.executeWorkflow()` with relay-controlled identifiers and parameters. - `status` discloses runtime health information. - `restart` invokes `runtime.restart()`. This contradicts the documented guarantee that relay commands are accepted only while the node is authenticated and online. Transport-level connection establishment is not equivalent to application-level authentication ...[truncated 1526 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Add a dedicated authentication flag and initialize it to `false`. 2. Reset the flag whenever a connection starts, closes, errors, or is disconnected. 3. Set the flag to `true` only after a valid `hello_ok` response is received. 4. Reject all `prompt`, `status`, `restart`, and `workflow` messages unless both conditions hold: - The socket is open. - The client is authenticated and `connectionState === "online"`. 5. Consider closing the socket when a privileged command is received before authentication, because this indicates a protocol violation. 6. Validate the complete message schema before dispatch, including `request_id`, nested `data`, prompt type, workflow identifier, and parameter shape. 7. Apply explicit workflow allowlists and capability-level authorization rather than assuming every authenticated relay session may invoke every operation. 8. Add regression tests proving that all privileged message types are rejected before `hello_ok`, after closure, and during reconnection. Example hardening pattern: ```ts private authenticated = false; private openSocket(): void { this.authenticated = false; this.connectionState = "reconnecting"; // Open the socket... } private handleMessage(msg: { type: string; data?: unknown }): void { if (msg.type === "hello_ok") { this.authenticated = true; this.connectionState = "online"; this.startHeartbeat(); return; } const privilegedTypes = new Set([ "prompt", "status", "restart", "workflow", ]); if (privilegedTypes.has(msg.type)) { if (!this.authenticated || this.connectionState !== "online") { console.warn("[private-bridge] Rejecting command before authentication"); this.ws?.close(1008, "authentication required"); return; } this.dispatch(msg as { type: string; data: IncomingMessage }); } } ``` ]]>
