[GH-ISSUE #5934] [Bug] Malformed CDP console args become an empty inventory or shift argument indices #3609

Closed
opened 2026-10-31 00:42:20 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 21, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5934

Originally assigned to: @kaoru0822-kitauji on GitHub.

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Process capture or runtime observation

Observed behavior

A loopback CDP producer can supply a Runtime.consoleAPICalled event with an omitted, null, or non-array args, and CdpBrowserProvider.inspectPage successfully returns an event with argument_types: []. With console text selected, it also returns text_capture.status: "included" and no values. No console_events exclusion or unavailable marker identifies the invalid argument inventory.

When args is [null, {"type":"string","value":"second"}], the malformed first element disappears and the retained second argument is assigned argument_index: 0, although its original position is 1.

These results were executed against the repository's fake CDP server over HTTP/WebSocket. No real browser was used. The CLI inspect-web-page and MCP inspect_web_page routes reach the same provider by source inspection; their transport adapters were not executed.

Reproduction

With the baseline's dependencies installed, save this focused test as tests/boundary/browser/consoleArguments.test.ts and run node node_modules/vitest/vitest.mjs run tests/boundary/browser/consoleArguments.test.ts --maxWorkers=1 --reporter=verbose. It exercises the provider, input parsing, loopback transport, and final result schema. All five cases passed in the executed run: the assertions pin the observed incorrect output and the valid empty-array control, rather than the desired corrected behavior.

import { expect, it } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";

it.each([
  ["omitted", undefined],
  ["null", null],
  ["non-array", "invalid"],
  ["invalid-element", [null, { type: "string", value: "second" }]],
  ["valid-empty-control", []],
])("observes console argument handling: %s", async (label, args) => {
  const browser = await startFakeCdpBrowser({
    commandEvents(command, origin) {
      if (command.method !== "Runtime.enable") return undefined;
      return [{
        ...(command.sessionId === undefined ? {} : { sessionId: command.sessionId }),
        method: "Runtime.consoleAPICalled",
        params: {
          type: "log",
          timestamp: 1,
          ...(args === undefined ? {} : { args }),
          stackTrace: { callFrames: [{ url: `${origin}/app.js`, lineNumber: 0, columnNumber: 0 }] },
        },
      }];
    },
  });
  try {
    const result = await new CdpBrowserProvider().inspectPage(inspectWebPageInputSchema.parse({
      cdp_endpoint: browser.endpoint,
      allowed_origins: [browser.allowedOrigin],
      target_id: "allowed-page",
      observation_ms: 1,
      include_console_text: true,
    }));
    if (!result.ok) throw result.error;
    expect(result.value.console.events).toHaveLength(1);
    const event = result.value.console.events[0]!;
    const consoleExclusions = result.value.completeness.excluded.filter(item => item.section === "console_events");
    console.log(JSON.stringify({ label, argument_types: event.argument_types, text_capture: event.text_capture, consoleExclusions, completeness: result.value.completeness }));
    expect(consoleExclusions).toEqual([]);
    if (label === "invalid-element") {
      expect(event.argument_types).toEqual(["string"]);
      expect(event.text_capture.values).toEqual([{ argument_index: 0, type: "string", text: "second" }]);
    } else {
      expect(event.argument_types).toEqual([]);
    }
  } finally {
    await browser.close();
  }
});

Expected behavior

A malformed argument inventory should remain distinguishable from an observed empty array. Report the invalid protocol value in console completeness and avoid presenting missing arguments as a known empty inventory. If partially retaining a malformed list, preserve original argument positions rather than renumbering surviving arguments. A genuine args: [] should remain a valid empty observation.

The console and completeness documentation promises retained argument types and reporting of values omitted by validation. The existing required-timestamp handling already records invalid_protocol_value when that required console field is unusable.

Impact and scope

P2: retained console evidence reports a known empty inventory for missing data, and malformed elements can change the identity of retained text arguments. The executed cases show no console-specific invalid-value marker.

This affects malformed CDP producer events. Frequency on real browsers is unknown, and no failure with conforming browser events is claimed. Existing attach-limited coverage remains present; the finding concerns missing invalid-value reporting within the retained event, not a claim that the entire capture becomes complete. The empty-array control behaved normally.

REA version

52415975942a4b9c53a817915b313ccc404f280e (package source version 6.7.0).

Runtime and platform

macOS 27.0.1 arm64; Node.js 24.18.0; Vitest 5.0.3. Simulated CDP producer; no browser launched.

Target and artifact context

An allowed loopback HTTP origin and the repository's fake CDP page target. Each test sends a console event with a valid timestamp and an allowed source URL, varying only the argument inventory. The malformed null array element is not JavaScript null's valid CDP representation as an object with subtype: "null".

Interface

Other

Earliest failure stage

Capture or observation

Relevant output or evidence

The executed provider-boundary test returned these selected fields:

{"input":"omitted args","argument_types":[],"text_capture":{"status":"included","values":[],"retained_bytes":0},"consoleExclusions":[],"unavailable_sections":[]}
{"input":"args: null","argument_types":[],"text_capture":{"status":"included","values":[],"retained_bytes":0},"consoleExclusions":[],"unavailable_sections":[]}
{"input":"non-array args","argument_types":[],"text_capture":{"status":"included","values":[],"retained_bytes":0},"consoleExclusions":[],"unavailable_sections":[]}
{"input":"invalid element followed by a string","argument_types":["string"],"text_capture":{"status":"included","values":[{"argument_index":0,"type":"string","text":"second"}],"retained_bytes":6},"consoleExclusions":[],"unavailable_sections":[]}
{"input":"valid empty array control","argument_types":[],"text_capture":{"status":"included","values":[],"retained_bytes":0},"consoleExclusions":[],"unavailable_sections":[]}

consoleExclusions is the test's filter of completeness.excluded for console_events; it is not a result-schema field. All cases also retained the normal console_events attach limitation.

The page capture listener forwards same-session events into the accumulator. The console handler normalizes arguments before storing them. The text projection assigns indices from that already-filtered list.

Typed outcome or recovery expected

Record console_events with invalid_protocol_value when the required argument inventory is absent or malformed. Retain unknown or partial argument information explicitly if supported by the output contract; preserve original positions for any retained arguments. Do not make an unusable inventory indistinguishable from a legitimate empty list.

Analysis or suspected cause

Source-derived: recordsValue returns [] for non-array input and filters non-record elements. The console handler uses that normalized list directly for both argument types and text positions without recording argument-validation failures. The public console event schema currently exposes a concrete argument-type array.

Additional context

The relevant handler, normalization helper, schema, and documentation were unchanged on upstream main at 905cbaf2398c905e96a1492b6145ece13e91c3cc when checked. Related issue #3377 and PR #3479 concern valid null primitives; issue #5656 and PR #5663 concern timestamps. Neither addresses this argument-inventory path.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5934 Originally assigned to: @kaoru0822-kitauji on GitHub. ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Process capture or runtime observation ### Observed behavior A loopback CDP producer can supply a `Runtime.consoleAPICalled` event with an omitted, null, or non-array `args`, and `CdpBrowserProvider.inspectPage` successfully returns an event with `argument_types: []`. With console text selected, it also returns `text_capture.status: "included"` and no values. No `console_events` exclusion or unavailable marker identifies the invalid argument inventory. When `args` is `[null, {"type":"string","value":"second"}]`, the malformed first element disappears and the retained second argument is assigned `argument_index: 0`, although its original position is 1. These results were executed against the repository's fake CDP server over HTTP/WebSocket. No real browser was used. The CLI `inspect-web-page` and MCP `inspect_web_page` routes reach the same provider by source inspection; their transport adapters were not executed. ### Reproduction With the baseline's dependencies installed, save this focused test as `tests/boundary/browser/consoleArguments.test.ts` and run `node node_modules/vitest/vitest.mjs run tests/boundary/browser/consoleArguments.test.ts --maxWorkers=1 --reporter=verbose`. It exercises the provider, input parsing, loopback transport, and final result schema. All five cases passed in the executed run: the assertions pin the observed incorrect output and the valid empty-array control, rather than the desired corrected behavior. ```ts import { expect, it } from "vitest"; import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js"; import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js"; it.each([ ["omitted", undefined], ["null", null], ["non-array", "invalid"], ["invalid-element", [null, { type: "string", value: "second" }]], ["valid-empty-control", []], ])("observes console argument handling: %s", async (label, args) => { const browser = await startFakeCdpBrowser({ commandEvents(command, origin) { if (command.method !== "Runtime.enable") return undefined; return [{ ...(command.sessionId === undefined ? {} : { sessionId: command.sessionId }), method: "Runtime.consoleAPICalled", params: { type: "log", timestamp: 1, ...(args === undefined ? {} : { args }), stackTrace: { callFrames: [{ url: `${origin}/app.js`, lineNumber: 0, columnNumber: 0 }] }, }, }]; }, }); try { const result = await new CdpBrowserProvider().inspectPage(inspectWebPageInputSchema.parse({ cdp_endpoint: browser.endpoint, allowed_origins: [browser.allowedOrigin], target_id: "allowed-page", observation_ms: 1, include_console_text: true, })); if (!result.ok) throw result.error; expect(result.value.console.events).toHaveLength(1); const event = result.value.console.events[0]!; const consoleExclusions = result.value.completeness.excluded.filter(item => item.section === "console_events"); console.log(JSON.stringify({ label, argument_types: event.argument_types, text_capture: event.text_capture, consoleExclusions, completeness: result.value.completeness })); expect(consoleExclusions).toEqual([]); if (label === "invalid-element") { expect(event.argument_types).toEqual(["string"]); expect(event.text_capture.values).toEqual([{ argument_index: 0, type: "string", text: "second" }]); } else { expect(event.argument_types).toEqual([]); } } finally { await browser.close(); } }); ``` ### Expected behavior A malformed argument inventory should remain distinguishable from an observed empty array. Report the invalid protocol value in console completeness and avoid presenting missing arguments as a known empty inventory. If partially retaining a malformed list, preserve original argument positions rather than renumbering surviving arguments. A genuine `args: []` should remain a valid empty observation. The [console and completeness documentation](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/docs/browser-observation.md#L364-L385) promises retained argument types and reporting of values omitted by validation. The [existing required-timestamp handling](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/browser/CdpCaptureEventHandlers.ts#L455-L457) already records `invalid_protocol_value` when that required console field is unusable. ### Impact and scope P2: retained console evidence reports a known empty inventory for missing data, and malformed elements can change the identity of retained text arguments. The executed cases show no console-specific invalid-value marker. This affects malformed CDP producer events. Frequency on real browsers is unknown, and no failure with conforming browser events is claimed. Existing attach-limited coverage remains present; the finding concerns missing invalid-value reporting within the retained event, not a claim that the entire capture becomes complete. The empty-array control behaved normally. ### REA version `52415975942a4b9c53a817915b313ccc404f280e` (package source version 6.7.0). ### Runtime and platform macOS 27.0.1 arm64; Node.js 24.18.0; Vitest 5.0.3. Simulated CDP producer; no browser launched. ### Target and artifact context An allowed loopback HTTP origin and the repository's fake CDP page target. Each test sends a console event with a valid timestamp and an allowed source URL, varying only the argument inventory. The malformed null array element is not JavaScript null's valid CDP representation as an object with `subtype: "null"`. ### Interface Other ### Earliest failure stage Capture or observation ### Relevant output or evidence The executed provider-boundary test returned these selected fields: ```json {"input":"omitted args","argument_types":[],"text_capture":{"status":"included","values":[],"retained_bytes":0},"consoleExclusions":[],"unavailable_sections":[]} {"input":"args: null","argument_types":[],"text_capture":{"status":"included","values":[],"retained_bytes":0},"consoleExclusions":[],"unavailable_sections":[]} {"input":"non-array args","argument_types":[],"text_capture":{"status":"included","values":[],"retained_bytes":0},"consoleExclusions":[],"unavailable_sections":[]} {"input":"invalid element followed by a string","argument_types":["string"],"text_capture":{"status":"included","values":[{"argument_index":0,"type":"string","text":"second"}],"retained_bytes":6},"consoleExclusions":[],"unavailable_sections":[]} {"input":"valid empty array control","argument_types":[],"text_capture":{"status":"included","values":[],"retained_bytes":0},"consoleExclusions":[],"unavailable_sections":[]} ``` `consoleExclusions` is the test's filter of `completeness.excluded` for `console_events`; it is not a result-schema field. All cases also retained the normal `console_events` attach limitation. The [page capture listener](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/browser/CdpPageCapture.ts#L71-L79) forwards same-session events into the accumulator. The [console handler](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/browser/CdpCaptureEventHandlers.ts#L437-L468) normalizes arguments before storing them. The [text projection](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/browser/CdpCaptureEventHandlers.ts#L481-L491) assigns indices from that already-filtered list. ### Typed outcome or recovery expected Record `console_events` with `invalid_protocol_value` when the required argument inventory is absent or malformed. Retain unknown or partial argument information explicitly if supported by the output contract; preserve original positions for any retained arguments. Do not make an unusable inventory indistinguishable from a legitimate empty list. ### Analysis or suspected cause Source-derived: [recordsValue](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/browser/CdpCaptureValues.ts#L20-L26) returns `[]` for non-array input and filters non-record elements. The console handler uses that normalized list directly for both argument types and text positions without recording argument-validation failures. The public [console event schema](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/browserObservationSchemas.ts#L162-L185) currently exposes a concrete argument-type array. ### Additional context The relevant handler, normalization helper, schema, and documentation were unchanged on upstream main at `905cbaf2398c905e96a1492b6145ece13e91c3cc` when checked. Related issue #3377 and PR #3479 concern valid null primitives; issue #5656 and PR #5663 concern timestamps. Neither addresses this argument-inventory path.
Author
Owner

@mdmasoomalam143 commented on GitHub (Oct 31, 2026):

Hey

<!-- gh-comment-id:6542631958 --> @mdmasoomalam143 commented on GitHub (Oct 31, 2026): Hey
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3609
No description provided.