T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- relayClient.ts:87
- Finding
- Operational Commands Are Accepted Before Relay Authentication Completes<![CDATA[ ## Vulnerability Details **File Location**: `relayClient.ts:87-102` **Vulnerability Type**: Missing authentication-state enforcement **Risk Level**: High ### Vulnerable Code ```ts private handleMessage(msg: { type: string; data?: unknown }): void { switch (msg.type) { case "hello_ok": console.log("[remote-relay] 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; ``` ### Technical Analysis The client changes its connection state to `online` only after receiving `hello_ok`, but operational command handling does not verify that this authentication transition has occurred. Any `prompt`, `status`, `restart`, or `workflow` message received over the socket is immediately passed to `dispatch()`. Consequently, the documented requirement that commands are accepted only while the node is authenticated and online is not enforced by the client. A malicious, compromised, incorrectly configured, or intercepted relay connection can send commands immediately after the WebSocket is established and before sending `hello_ok`. The affected commands map directly to privileged host-runtime methods: - `prompt` invokes `runtime.executePrompt()`. - `workflow` invokes `runtime.executeWorkflow()`. - `restart` invokes `runtime.restart()`. - `status` exposes runtime health information. TypeScript type assertions do not provide runtime validation, so malformed or attacker-controlled command envelopes also reach the dispatcher without schema verification. ### Attack Path 1. The OpenClaw node establishes a WebSocket connection to a malicious, compromised, or intercepted relay endpoint. 2. The endpoint does not complete the expected application-level authentication sequence and does not send `hello_ok`. 3. The endpoint ...[truncated 1130 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Enforce an explicit authenticated session state before dispatching any operational message. ```ts private authenticated = false; private handleMessage(msg: { type: string; data?: unknown }): void { if (msg.type === "hello_ok") { this.authenticated = true; this.backoff = INITIAL_BACKOFF; this.connectionState = "online"; this.startHeartbeat(); return; } const operationalTypes = new Set([ "prompt", "status", "restart", "workflow", ]); if (operationalTypes.has(msg.type)) { if (!this.authenticated || this.connectionState !== "online") { console.warn( `[remote-relay] Rejecting command before authentication: ${msg.type}` ); return; } // Validate the complete envelope with a runtime schema before dispatch. this.dispatch(msg as { type: string; data: IncomingMessage }); return; } } ``` Additional hardening should include: 1. Reset `authenticated` to `false` before opening a socket and whenever the socket closes or errors. 2. Use a runtime schema validator to verify message type, `request_id`, prompt length, workflow identifier, and parameter structure. 3. Reject missing or empty request identifiers instead of assigning `"unknown"`. 4. Add command authorization controls so each relay identity is limited to explicitly enabled capabilities. 5. Add replay protection or request nonces if the relay protocol supports them. 6. Add tests confirming that all four operational message types are rejected before `hello_ok` and after disconnection. 7. Consider closing the socket when operational commands are received before authentication, because this indicates a protocol violation. ]]>
