all repos — sz-chrome-remote-cdp @ 52fd9523d1e4739d9a9e082e11686f244c767cc1

pi-browser-harness, patched to drive a Chrome on another host over CDP, with castle_cdp_search web search

fix(cdp): validate inbound CDP messages with a runtime type guard
amankumarsingh77 amankumarsingh7702@gmal.com
Sat, 02 May 2026 15:48:28 +0530
commit

52fd9523d1e4739d9a9e082e11686f244c767cc1

parent

22019a655c8708d5253e1d25b8ae654069032666

2 files changed, 31 insertions(+), 2 deletions(-)

jump to
M src/cdp/transport.ts → src/cdp/transport.ts

@@ -1,6 +1,7 @@

import WebSocket from "ws"; import { type Result, err, ok } from "../util/result"; import { type CdpError, cdpError } from "./errors"; +import { isCdpRawMessage } from "./types"; import type { CdpEvent, CdpRawMessage } from "./types"; const DEFAULT_TIMEOUT_MS = 15_000;

@@ -75,12 +76,14 @@

let queue = makeEventQueue(); const handleMessage = (raw: string): void => { - let msg: CdpRawMessage; + let parsed: unknown; try { - msg = JSON.parse(raw); + parsed = JSON.parse(raw); } catch { return; } + if (!isCdpRawMessage(parsed)) return; + const msg: CdpRawMessage = parsed; if (msg.id === undefined) { if (msg.method) { queue.push({

@@ -194,6 +197,11 @@ }

}); }, events(): AsyncIterable<CdpEvent> { + // Single-consumer: each connection's event stream may be iterated by + // exactly one for-await loop. Calling events() multiple times returns + // the same iterable; the second consumer will silently steal events + // from the first. After a reconnect, the previous iterator is ended + // and callers must re-call events() to receive new events. return queue.iter; }, state(): "open" | "closed" | "connecting" {
M src/cdp/types.ts → src/cdp/types.ts

@@ -49,3 +49,24 @@ ];

export const isInternalUrl = (url: string): boolean => INTERNAL_PREFIXES.some((p) => url.startsWith(p)); + +const isObject = (v: unknown): v is Readonly<Record<string, unknown>> => + typeof v === "object" && v !== null && !Array.isArray(v); + +export const isCdpRawMessage = (v: unknown): v is CdpRawMessage => { + if (!isObject(v)) return false; + const id = v["id"]; + if (id !== undefined && typeof id !== "number") return false; + const method = v["method"]; + if (method !== undefined && typeof method !== "string") return false; + const sessionId = v["sessionId"]; + if (sessionId !== undefined && typeof sessionId !== "string") return false; + const errVal = v["error"]; + if (errVal !== undefined) { + if (!isObject(errVal)) return false; + if (typeof errVal["message"] !== "string") return false; + const code = errVal["code"]; + if (code !== undefined && typeof code !== "number") return false; + } + return true; +};