T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- scripts/cdp-proxy.mjs:275
- Finding
- Unauthenticated Local API Provides Full Control of the User's Authenticated Browser<![CDATA[ ## Vulnerability Details **File Location**: `scripts/cdp-proxy.mjs:275-562`; persistent startup behavior in `scripts/check-deps.mjs:97-107` **Vulnerability Type**: Missing authentication and insufficient authorization on a privileged browser-control API **Risk Level**: High ### Code Snippet ```javascript if (pathname === '/targets') { const resp = await sendCDP('Target.getTargets'); const pages = resp.result.targetInfos.filter(t => t.type === 'page'); res.end(JSON.stringify(pages, null, 2)); } ``` ```javascript else if (pathname === '/eval') { const sid = await ensureSession(q.target); const body = await readBody(req); const expr = body || q.expr || 'document.title'; const resp = await sendCDP('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true, }, sid); if (resp.result?.result?.value !== undefined) { res.end(JSON.stringify({ value: resp.result.result.value })); } else if (resp.result?.exceptionDetails) { res.statusCode = 400; res.end(JSON.stringify({ error: resp.result.exceptionDetails.text })); } else { res.end(JSON.stringify(resp.result)); } } ``` ```javascript server.listen(PORT, '127.0.0.1', () => { console.log(`[CDP Proxy] Running at http://localhost:${PORT}`); connect().catch(e => console.error( '[CDP Proxy] Initial connection failed:', e.message )); }); ``` The proxy is also launched as a detached process: ```javascript const child = spawn(process.execPath, [PROXY_SCRIPT], { detached: true, stdio: ['ignore', logFd, logFd], ...(os.platform() === 'win32' ? { windowsHide: true } : {}), }); child.unref(); ``` ### Technical Analysis The proxy binds only to `127.0.0.1`, which reduces remote network exposure, but it does not authenticate callers or authorize individual operations. Any local process able to connect to the configured port can invoke all API endpoints. The exposed operations include: - Enumerating all existing browser tabs throug ...[truncated 2316 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Generate a cryptographically random bearer token each time the proxy starts and require it on every endpoint, including `/health`. 2. Store the token in a user-only file with restrictive permissions or pass it directly to the authorized client. 3. Prefer a private Unix-domain socket with user-only permissions on supported systems. 4. Track tabs created by the proxy and deny access to pre-existing tabs by default. 5. Require explicit, operation-specific user approval before accessing existing tabs, uploading files, submitting forms, or invoking other state-changing actions. 6. Add method allowlists and capability separation instead of exposing unrestricted `Runtime.evaluate`. 7. Reject requests carrying browser `Origin` headers unless an explicitly trusted origin is configured. 8. Use an unpredictable port in addition to authentication; port randomization must not replace authentication. 9. Terminate the proxy when the task ends, or implement a short inactivity timeout and automatic shutdown. 10. Log privileged operations without recording page content, credentials, session tokens, or submitted JavaScript containing secrets. ]]>
