T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- relayClient.ts:91
- Finding
- Privileged Relay Commands Are Accepted Before Authentication## Vulnerability Details **File Location**: `relayClient.ts:58-62, 91-101` **Vulnerability Type**: Missing authentication-state authorization check **Risk Level**: High ### Vulnerable Code ```ts this.ws.onmessage = (event) => { try { const msg = JSON.parse(String(event.data)); this.handleMessage(msg); } catch { console.error("[privaclaw] Invalid message received"); } }; ``` ```ts private handleMessage(msg: { type: string; data?: unknown }): void { switch (msg.type) { case "hello_ok": console.log("[privaclaw] 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 marks the connection as authenticated only after receiving `hello_ok`, but privileged messages are dispatched without checking `connectionState === "online"` or a separate authenticated-session flag. Consequently, the remote WebSocket peer can send a `prompt`, `status`, `restart`, or `workflow` message immediately after the socket opens and before authentication succeeds. The command is passed directly to the host runtime through `executePrompt`, `executeWorkflow`, or `restart`. This violates the documented guarantee that relay commands are accepted only while the node is authenticated and online. TypeScript type assertions do not provide runtime authorization or message validation. ### Attack Path 1. The node establishes a WebSocket connection to a relay peer. 2. The client sends its authentication `hello` message. 3. Before returning `hello_ok`, the peer sends a validly formatted `prompt`, `workflow`, or `restart` envelope. 4. `onmessage` parses the envelope and calls `handleMessage`. 5. `handleMessage` dispatches the command without checking authenticated state. 6. The corresponding pri ...[truncated 703 chars]
- Remediation
- ## Remediation Suggestions - Maintain an explicit authentication flag that starts as `false`, becomes `true` only after a valid `hello_ok`, and is reset on connection close, disconnect, and reconnection. - Before dispatching any privileged message, require both an authenticated flag and the expected online connection state. - Reject or close the connection when privileged commands arrive before authentication. - Treat repeated or out-of-order authentication messages as protocol violations. - Validate incoming envelopes at runtime rather than relying on TypeScript casts. - Add regression tests proving that `prompt`, `status`, `restart`, and `workflow` messages received before `hello_ok` do not invoke runtime methods. Example hardening pattern: ```ts private authenticated = false; private handleMessage(msg: { type: string; data?: unknown }): void { if (msg.type === "hello_ok") { this.authenticated = true; this.connectionState = "online"; this.startHeartbeat(); return; } if (!this.authenticated || this.connectionState !== "online") { console.warn("[privaclaw] Rejecting command before authentication"); return; } switch (msg.type) { case "prompt": case "status": case "restart": case "workflow": void this.dispatch(msg as { type: string; data: IncomingMessage }); break; } } ```
