T05 · Unauthorized Access and Privilege Escalation
- Location
lib/relay-client.js:31- Finding
Relay Messages Can Trigger Unauthenticated Local Agent Invocations
- Content
View full analysis
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: HighVulnerable 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
View remediation
Remediation Suggestions
- 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.
- 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.
- Reject
relay:messageobjects unlessmsg.sessionIdstrictly equals the currently activethis.sessionId. - Validate message schemas before dispatch:
- Require
sessionIdandtextto be strings. - Enforce conservative maximum lengths.
- Reject unknown fields and malformed identifiers.
- Enforce expected state transitions so messages cannot be accepted before pairing.
- Require
- Generate a dedicated local OpenClaw session rather than accepting a session identifier supplied by the relay.
- Require explicit local approval before enabling agent tools or other sensitive capabilities for a newly paired client.
- Run the agent with least privilege and disable filesystem, shell, credential, and network capabilities that are unnecessary for voice interaction.
- Add replay protection using monotonic sequence numbers, timestamps, and unique nonces.
- Record security-relevant events, including pairing changes and rejected session identifiers, without logging sensitive transcript contents.
