T05 · Unauthorized Access and Privilege Escalation
- Location
- lib/relay-client.js:31
- Finding
- Relay Messages Can Trigger Unauthenticated Local Agent Invocations## Vulnerability Details **File Location**: `lib/relay-client.js:31-56`, `bin/voice-bridge.js:111-123`, `lib/agent-bridge.js:18-35` **Vulnerability Type**: Missing message authentication and session validation **Risk Level**: High ### Vulnerable Code `lib/relay-client.js:31-56`: ```js this.ws.on('message', (raw) => { let msg; try { msg = JSON.parse(raw); } catch { return; } switch (msg.type) { case 'relay:code': this.pairingCode = msg.code; this._emit('code', msg.code); break; case 'relay:paired': this.sessionId = msg.sessionId; this._emit('paired', { sessionId: msg.sessionId }); break; case 'relay:message': this._emit('message', { sessionId: msg.sessionId, text: msg.text, timestamp: msg.timestamp }); break; case 'relay:client-disconnected': this.sessionId = null; this._emit('client-disconnected', { sessionId: msg.sessionId }); break; case 'relay:error': this._emit('error', { error: msg.error }); break; } }); ``` `bin/voice-bridge.js:111-123`: ```js relay.on('message', async ({ sessionId, text }) => { console.log(`[voice-bridge] User said: "${text}"`); relay.sendTyping(true); try { const response = await bridge.sendMessage(sessionId, text); console.log(`[voice-bridge] Agent response: "${response}"`); relay.sendMessage(response); relay.sendTyping(false); } catch (err) { console.error(`[voice-bridge] Error sending to agent:`, err.message); relay.sendMessage('Sorry, I encountered an error processing your message.'); relay.sendTyping(false); } }); ``` `lib/agent-bridge.js:18-35`: ```js return new Promise((resolve, reject) => { const args = [ 'agent', '--session-id', sessionId, '-m', text, ]; if (this.agent) { args.push('--agent', this.agent); } execFile('openclaw', args, ...[truncated 3019 chars]
- Remediation
- ## Remediation Suggestions 1. Introduce an end-to-end pairing capability generated locally and exchanged through an authenticated pairing flow. Require every browser-originated command to carry a valid, unguessable capability. 2. Cryptographically authenticate messages independently of the relay, such as with signatures or a message authentication code based on a key known only to the paired endpoints. 3. Reject `relay:message` objects unless `msg.sessionId` strictly equals the currently active `this.sessionId`. 4. Validate message schemas before dispatch: - Require `sessionId` and `text` to be strings. - Enforce conservative maximum lengths. - Reject unknown fields and malformed identifiers. - Enforce expected state transitions so messages cannot be accepted before pairing. 5. Generate a dedicated local OpenClaw session rather than accepting a session identifier supplied by the relay. 6. Require explicit local approval before enabling agent tools or other sensitive capabilities for a newly paired client. 7. Run the agent with least privilege and disable filesystem, shell, credential, and network capabilities that are unnecessary for voice interaction. 8. Add replay protection using monotonic sequence numbers, timestamps, and unique nonces. 9. Record security-relevant events, including pairing changes and rejected session identifiers, without logging sensitive transcript contents.
