fix(browser): use CDP command probe for cdpReady health#31421
Conversation
|
Rebased this PR onto current Local verification on updated head:
This keeps the same behavior: |
564a0ce to
2424ebd
Compare
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
1. 🟡 SSRF / outbound WebSocket interaction via user-controlled CDP URL in readiness check
DescriptionThe Chrome CDP readiness probe makes outbound HTTP and WebSocket connections based on If an attacker can influence
This is an SSRF/outbound network interaction primitive (HTTP + WebSocket) that can be used for internal network reachability probing and for interacting with arbitrary WebSocket services. Relevant code: const wsUrl = await getChromeWebSocketUrl(cdpUrl, timeoutMs);
...
return await canRunCdpHealthCommand(wsUrl, handshakeTimeoutMs);Source of attacker control: profiles can be created with arbitrary remote Note: The browser control server typically binds to RecommendationTreat Mitigations (choose based on intended product behavior):
Example (sketch): const cdp = new URL(cdpUrl);
const ws = new URL(wsUrl);
if (!['ws:', 'wss:'].includes(ws.protocol)) throw new Error('invalid ws protocol');
if (ws.hostname !== cdp.hostname) throw new Error('wsUrl host mismatch');
// optionally apply infra/net/ssrf policy checks hereThis reduces the ability for attacker-controlled CDP endpoints (or injected responses) to redirect the probe to arbitrary internal/external websocket services. 2. 🔵 Unbounded WebSocket message parsing in CDP health check can cause memory/CPU DoS
DescriptionThe new CDP readiness probe ( Impact:
Vulnerable code (new path): parsed = JSON.parse(rawDataToString(raw))Supporting references:
RecommendationEnforce a strict upper bound on CDP message sizes before converting to string / parsing, and configure the websocket client to reject oversized frames. Recommended changes:
// in openCdpWebSocket
return new WebSocket(wsUrl, {
handshakeTimeout: handshakeTimeoutMs,
maxPayload: 1024 * 1024, // 1 MiB (tune as needed)
...(Object.keys(headers).length ? { headers } : {}),
...(agent ? { agent } : {}),
});
const MAX_CDP_HEALTH_MSG_BYTES = 1024 * 1024;
function rawSize(raw: WebSocket.RawData): number {
if (typeof raw === "string") return Buffer.byteLength(raw, "utf8");
if (Buffer.isBuffer(raw)) return raw.length;
if (Array.isArray(raw)) return raw.reduce((n, b) => n + b.length, 0);
if (raw instanceof ArrayBuffer) return raw.byteLength;
return Buffer.byteLength(String(raw), "utf8");
}
if (rawSize(raw) > MAX_CDP_HEALTH_MSG_BYTES) {
finish(false);
return;
}This prevents large allocations in Analyzed PR: #31421 at commit Last updated on: 2026-03-02T23:27:18Z |
Greptile SummaryThis PR fixes a false-positive health status bug where Key changes:
Confidence Score: 4/5
Last reviewed commit: e1d7123 |
|
Addressed the ws-listener hygiene point. Change made in
Validation:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bc3f4b890d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } catch { | ||
| // ignore | ||
| } | ||
| resolve(false); | ||
| finish(false); | ||
| }, | ||
| Math.max(50, timeoutMs + 25), | ||
| ); |
There was a problem hiding this comment.
Restart health timeout after CDP socket opens
The timeout window starts before the websocket handshake and is never reset on open, so once the connection is established the probe only has the leftover budget (often ~25ms when handshake is near handshakeTimeoutMs) to receive Browser.getVersion. In slow or high-latency environments this can mark a healthy CDP endpoint as unhealthy and trigger avoidable attach failures/restarts in isChromeCdpReady callers, even though command execution would succeed with a normal post-open timeout window.
Useful? React with 👍 / 👎.
* fix(browser): validate cdp command channel health * test(browser): cover stale cdp command channel readiness * changelog: note cdp command-channel readiness check * browser(cdp): detach ws message listener on health-probe cleanup
* fix(browser): validate cdp command channel health * test(browser): cover stale cdp command channel readiness * changelog: note cdp command-channel readiness check * browser(cdp): detach ws message listener on health-probe cleanup
* fix(browser): validate cdp command channel health * test(browser): cover stale cdp command channel readiness * changelog: note cdp command-channel readiness check * browser(cdp): detach ws message listener on health-probe cleanup
Summary
cdpReady: truewhen the websocket was open but the CDP command channel was stale, matching idle-time failure reports.act/snapshotfail, delaying recovery and diagnosis.isChromeCdpReadynow requires a successfulBrowser.getVersioncommand response over websocket, not just handshake/open success.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
browser statusnow reportscdpReadybased on real command-channel health, reducing false healthy states after idle CDP degradation.Security Impact (required)
No)No)No)No)No)Yes, explain risk + mitigation:Repro + Verification
Environment
Steps
/json/versionwith a websocket endpoint.isChromeCdpReadyreturns true only when websocket command response forBrowser.getVersionis received.Expected
cdpReadyrequires command-channel responsiveness.Actual
Evidence
Human Verification (required)
What you personally verified (not just CI), and how:
pnpm vitest run src/browser/chrome.test.tscdpReady=false).Compatibility / Migration
Yes)No)No)Failure Recovery (if this breaks)
src/browser/chrome.ts.Risks and Mitigations
Browser.getVersiondespite being partially usable.Browser.getVersion.