mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 17:28:08 +00:00
fix(browser): reject malformed UTF-8 CDP protocol bytes (#10040)
This commit is contained in:
1 parent
8c40ba78de
commit
9cd1eadfb7
3 files changed
+55
-12
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import WebSocket, { type RawData } from "ws";
|
||||
import { isUtf8 } from "node:buffer";
|
||||
|
||||
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
||||
import { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
@@ -295,7 +296,14 @@ class CdpTransportCore {
|
||||
|
||||
#receive(data: RawData): void {
|
||||
if (this.#closed || this.#protocolFailed) return;
|
||||
const parsed = safeParseJson(rawText(data));
|
||||
const bytes = rawBytes(data);
|
||||
// Binary WebSocket messages bypass ws's text-frame UTF-8 validation.
|
||||
// Never replacement-decode protocol facts into a different accepted value.
|
||||
if (!isUtf8(bytes)) {
|
||||
this.#failPending("protocol_error");
|
||||
return;
|
||||
}
|
||||
const parsed = safeParseJson(bytes.toString("utf8"));
|
||||
if (!parsed.ok) {
|
||||
this.#failPending("protocol_error");
|
||||
return;
|
||||
@@ -326,7 +334,7 @@ class CdpTransportCore {
|
||||
this.#failPending("protocol_error");
|
||||
return;
|
||||
}
|
||||
const wireBytes = rawByteLength(data);
|
||||
const wireBytes = bytes.byteLength;
|
||||
const event: CdpTransportEvent = {
|
||||
method: message.method,
|
||||
params: "params" in message ? message.params : {},
|
||||
@@ -593,17 +601,12 @@ export class CdpConnection {
|
||||
const socketIsClosed = (socket: WebSocket): boolean =>
|
||||
socket.readyState === WebSocket.CLOSED;
|
||||
|
||||
const rawText = (data: RawData): string =>
|
||||
const rawBytes = (data: RawData): Buffer =>
|
||||
Array.isArray(data)
|
||||
? Buffer.concat(data).toString("utf8")
|
||||
? Buffer.concat(data)
|
||||
: Buffer.isBuffer(data)
|
||||
? data.toString("utf8")
|
||||
: Buffer.from(data).toString("utf8");
|
||||
|
||||
const rawByteLength = (data: RawData): number =>
|
||||
Array.isArray(data)
|
||||
? data.reduce((bytes, chunk) => bytes + chunk.byteLength, 0)
|
||||
: data.byteLength;
|
||||
? data
|
||||
: Buffer.from(data);
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
|
||||
@@ -359,6 +359,46 @@ describe("CDP transport failure precedence", () => {
|
||||
});
|
||||
|
||||
describe("CDP connection command responses", () => {
|
||||
it("rejects invalid UTF-8 replies without aliasing a literal replacement character", async () => {
|
||||
const browser = await startFakeCdpBrowser({ hangOnMethod: "Page.enable" });
|
||||
browsers.push(browser);
|
||||
const connection = await CdpConnection.connect(
|
||||
browser.browserWebSocketUrl,
|
||||
"inspect_web_page",
|
||||
);
|
||||
try {
|
||||
const control = connection.send("Page.enable");
|
||||
await vi.waitFor(() => expect(browser.commands).toHaveLength(1));
|
||||
browser.emitRawMessage(
|
||||
Buffer.from(
|
||||
JSON.stringify({
|
||||
id: browser.commands[0]?.id,
|
||||
result: { value: "�" },
|
||||
}),
|
||||
),
|
||||
);
|
||||
await expect(control).resolves.toEqual({ value: "�" });
|
||||
const pending = connection.send("Page.enable");
|
||||
const rejected = expect(pending).rejects.toMatchObject({
|
||||
reason: "protocol_error",
|
||||
});
|
||||
await vi.waitFor(() => expect(browser.commands).toHaveLength(2));
|
||||
browser.emitRawMessage(
|
||||
Buffer.concat([
|
||||
Buffer.from(
|
||||
`{"id":${String(browser.commands[1]?.id)},"result":{"value":"`,
|
||||
),
|
||||
Buffer.from([0xff]),
|
||||
Buffer.from('"}}'),
|
||||
]),
|
||||
);
|
||||
await rejected;
|
||||
expect(connection.isReusable()).toBe(false);
|
||||
} finally {
|
||||
await connection.close();
|
||||
}
|
||||
});
|
||||
|
||||
it("surfaces an unmodeled fake command as a CDP method rejection", async () => {
|
||||
const browser = await startFakeCdpBrowser();
|
||||
browsers.push(browser);
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ export interface FakeCdpBrowser {
|
||||
/** WebSocket upgrades accepted over the fixture's lifetime. */
|
||||
openedConnectionCount(): number;
|
||||
/** Deliver actual malformed wire data without bypassing the production transport parser. */
|
||||
emitRawMessage(message: string): void;
|
||||
emitRawMessage(message: string | Buffer): void;
|
||||
emitEvent(event: CdpEvent): void;
|
||||
/** Drop currently open WebSockets from the server side to exercise reconnect paths. */
|
||||
closeActiveConnections(): void;
|
||||
|
||||
Reference in new issue
Block a user