fix(browser)!: preserve unknown WebSocket frame facts

Keep malformed or missing CDP opcodes and payload facts unknown, retain observed zero and empty payloads, and avoid false capture differences while preserving independently known facts.

Validation: 59 source tests; compiled public CLI and stdio MCP fake-CDP acceptance; check:fast; docs:check; changed-file formatting. Real Chromium malformed-event behavior was not tested.

BREAKING CHANGE: WebSocket event opcode is nullable and numeric values are restricted to the four-bit protocol range.

Fixes #5666
This commit is contained in:
morluto committed 2026-10-21 05:00:37 +08:00
1 parent c38f96fa69
commit 0bc2b42001
7 files changed
+458 -26

No files matched your search

@@ -0,0 +1,182 @@
import { expect, it } from "vitest";
import { inspectWebPageInputSchema } from "../domain/browserObservation.js";
import { webPageInspectionSchema } from "../domain/browserObservationSchemas.js";
import { CdpCaptureEvents } from "./CdpCaptureEvents.js";
const origin = "https://example.test";
const createEvents = (): CdpCaptureEvents =>
new CdpCaptureEvents(
inspectWebPageInputSchema.parse({
cdp_endpoint: "http://127.0.0.1:9222",
allowed_origins: [origin],
target_id: "page",
include_websocket_shapes: true,
}),
new Set([origin]),
);
const captureFrame = (
opcode: unknown,
payloadData: unknown,
includeOpcode = true,
includePayload = true,
) => {
const events = createEvents();
events.ingest({
method: "Network.webSocketCreated",
params: { requestId: "socket", url: "wss://example.test/live" },
});
events.ingest({
method: "Network.webSocketFrameReceived",
params: {
requestId: "socket",
response: {
...(includeOpcode ? { opcode } : {}),
...(includePayload ? { payloadData } : {}),
},
},
});
return {
events,
frame: events.webSocketConnectionsById.get("socket")?.events[0],
};
};
it.each([
{
label: "missing",
opcode: undefined,
payload: "",
hasOpcode: false,
hasPayload: true,
},
{
label: "wrong type",
opcode: "1",
payload: "text",
hasOpcode: true,
hasPayload: true,
},
{
label: "explicit null",
opcode: null,
payload: "",
hasOpcode: true,
hasPayload: true,
},
{
label: "negative",
opcode: -1,
payload: "",
hasOpcode: true,
hasPayload: true,
},
{
label: "fractional",
opcode: 1.5,
payload: "text",
hasOpcode: true,
hasPayload: true,
},
{
label: "above opcode width",
opcode: 16,
payload: "",
hasOpcode: true,
hasPayload: true,
},
{
label: "non-finite",
opcode: Number.NaN,
payload: "",
hasOpcode: true,
hasPayload: true,
},
] as const)(
"keeps $label WebSocket opcode unknown",
({ opcode, payload, hasOpcode, hasPayload }) => {
const { events, frame } = captureFrame(
opcode,
payload,
hasOpcode,
hasPayload,
);
expect(frame).toEqual({
direction: "received",
opcode: null,
payload_bytes: null,
payload_shape: null,
});
expect(events.completeness.snapshot().excluded).toContainEqual({
section: "websocket_frames",
reason: "invalid_protocol_value",
count: 1,
});
expect(
webPageInspectionSchema.shape.network.shape.websocket_connections.element.safeParse(
{
request_id: "socket",
url: "wss://example.test/live",
events: [frame],
},
).success,
).toBe(true);
},
);
it.each([
{ label: "continuation", opcode: 0, payload: "", bytes: 0, format: "binary" },
{ label: "empty text", opcode: 1, payload: "", bytes: 0, format: "text" },
{
label: "reserved four-bit opcode",
opcode: 15,
payload: "",
bytes: 0,
format: "binary",
},
] as const)(
"preserves observed $label data",
({ opcode, payload, bytes, format }) => {
const { events, frame } = captureFrame(opcode, payload);
expect(frame).toEqual({
direction: "received",
opcode,
payload_bytes: bytes,
payload_shape: { format, json_shape: null },
});
expect(events.completeness.snapshot().excluded).not.toContainEqual(
expect.objectContaining({
section: "websocket_frames",
reason: "invalid_protocol_value",
}),
);
},
);
it.each([
{
label: "missing payload",
opcode: 1,
payload: undefined,
hasPayload: false,
},
{ label: "wrong-type payload", opcode: 1, payload: 42, hasPayload: true },
] as const)(
"does not fabricate an empty payload for $label",
({ opcode, payload, hasPayload }) => {
const { events, frame } = captureFrame(opcode, payload, true, hasPayload);
expect(frame).toEqual({
direction: "received",
opcode,
payload_bytes: null,
payload_shape: null,
});
expect(events.completeness.snapshot().excluded).toContainEqual({
section: "websocket_frames",
reason: "invalid_protocol_value",
count: 1,
});
},
);