T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- lib/browser-relay.js:62
- Finding
- Unauthenticated Browser Relay Exposes Screen Content and Full Browser Input Control<![CDATA[ ## Vulnerability Details **File Location**: `lib/browser-relay.js:62-190` **Vulnerability Type**: Missing authentication and authorization on a network-accessible browser-control service **Risk Level**: High ### Technical Analysis The browser relay binds to all network interfaces by default and accepts every HTTP and WebSocket client without authentication, authorization, origin validation, or a session-specific access token. Relevant code: ```js async function createBrowserRelay({ cdpPort = 18800, targetId, port = 0, host = '0.0.0.0', timeout = 300000, quality = 60, maxWidth = 1280, maxHeight = 900, everyNthFrame = 1, } = {}) { ``` ```js // HTTP server const server = http.createServer((req, res) => { if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html); } else { res.writeHead(404); res.end('Not found'); } }); // WebSocket server for client interaction const wss = new WebSocket.Server({ server }); wss.on('connection', async (ws) => { clients.add(ws); console.log(`[browser-relay] client connected (${clients.size} total)`); // Restart screencast so frames flow to new clients try { await cdp.send('Page.stopScreencast').catch(() => {}); await cdp.send('Page.startScreencast', { format: 'jpeg', quality, maxWidth, maxHeight, everyNthFrame, }); console.log('[browser-relay] screencast restarted for new client'); } catch (e) { console.error('[browser-relay] screencast restart failed:', e.message); } ws.on('message', async (raw) => { try { const msg = JSON.parse(raw); await handleInput(cdp, msg, viewportWidth, viewportHeight); } catch (e) { console.error('[browser-relay] input error:', e.message); } }); ws.on('close', () => { clients.delete(ws); console.log(`[browser-relay] client disconnected (${clients.size} total)`); }); }); ``` ```js ser ...[truncated 1953 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Bind to `127.0.0.1` by default and require an explicit option to expose the service. - Generate a cryptographically random, single-use session token and require it for both HTTP and WebSocket access. - Validate the WebSocket `Origin` header and reject unapproved origins. - Restrict access using Tailscale ACLs or a host firewall; do not treat Tailnet membership alone as application authorization. - Use TLS whenever traffic can leave a trusted encrypted overlay. - Limit the relay to the CAPTCHA frame or coordinates rather than exposing the complete browser tab. - Require explicit user confirmation before enabling keyboard input or sensitive navigation. - Allow only one authorized client and close the service after CAPTCHA completion. - Add connection rate limits, message-size limits, input schema validation, and security event logging. ]]>
