[GH-ISSUE #7370] [Bug] Page inspection silently replaces malformed console and initiator source offsets #4378

Closed
opened 2026-10-31 00:49:22 +00:00 by bsakaguchi · 0 comments
Owner

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

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

The public inspect_web_page MCP tool successfully returns fabricated source coordinates when a selected CDP producer sends malformed finite offsets. In the observed console event and request initiator, lineNumber -5 and columnNumber -2 became line 0 and column 0; 7.9 and 3.2 became 7 and 3. Neither console_events nor network_initiators received an invalid_protocol_value exclusion. Ordinary attach-window and policy limitations remained.

Reproduction

From a fresh checkout at the source commit below, use Node 24.22.1 and prepare locked dependencies with npm ci && npm run build:cached. The following is the independently executed four-case MCP probe, with portable directory names. It uses the repository's fake CDP server over loopback HTTP/WebSocket; no real browser or external service is needed. Each case ran once. The two malformed cases fail assertions for unknown coordinates and invalid-value coverage; both valid controls pass.

WORK="$(mktemp -d)"
mkdir -p "$WORK/home" "$WORK/config" "$WORK/data" "$WORK/cache" "$WORK/state" "$WORK/temp"
export HOME="$WORK/home" XDG_CONFIG_HOME="$WORK/config" XDG_DATA_HOME="$WORK/data"
export XDG_CACHE_HOME="$WORK/cache" XDG_STATE_HOME="$WORK/state" TMPDIR="$WORK/temp"
cat > tests/boundary/mcp/consoleOffsets.test.ts <<'TS'
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { expect, it } from "vitest";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { createServer } from "../../../src/server/createServer.js";
import { observed } from "../../fixtures/analysisExecution.js";
import { ok } from "../../../src/domain/result.js";

it.each([[-5, -2, null, null], [7.9, 3.2, null, null], [0, 0, 0, 0], [7, 3, 7, 3]])("preserves source offsets %s/%s", async (lineNumber, columnNumber, line, column) => {
  const browser = await startFakeCdpBrowser({ commandEvents(command, origin) {
    const session = command.sessionId === undefined ? {} : { sessionId: command.sessionId };
    const frame = { url: `${origin}/app.js`, lineNumber, columnNumber };
    if (command.method === "Runtime.enable") return [{ ...session, method: "Runtime.consoleAPICalled", params: { type: "log", timestamp: 1, args: [], stackTrace: { callFrames: [frame] } } }];
    if (command.method === "Network.enable") return [{ ...session, method: "Network.requestWillBeSent", params: { requestId: "offset-probe", timestamp: 1, type: "Script", request: { url: `${origin}/app.js`, method: "GET" }, initiator: { type: "script", stack: { callFrames: [frame] } } } }];
    return undefined;
  } });
  const session = createTestBinarySession(() => ({ execute: () => Promise.resolve(observed(null)), close: () => Promise.resolve(ok(null)) }));
  const server = createServer({ kind: "session", session }, { environment: { ...process.env }, browserObservation: new CdpBrowserProvider(), availabilityPolicy: () => ({ processCaptureEnabled: false, investigationInputRoots: 0 }) });
  const client = new Client({ name: "offset-verifier", version: "1" });
  const [ct, st] = InMemoryTransport.createLinkedPair();
  try {
    await server.connect(st);
    await client.connect(ct);
    const result = await client.callTool({ name: "inspect_web_page", arguments: { cdp_endpoint: browser.endpoint, target_id: "allowed-page", observation_ms: 0 } });
    if (result.isError === true) throw new Error(JSON.stringify(result));
    const value = (result.structuredContent as any).normalized_result;
    const event = value.console.events.find((e: any) => e.timestamp === 1);
    const request = value.network.requests.find((r: any) => r.request_id === "offset-probe");
    console.log(JSON.stringify({ input: { lineNumber, columnNumber }, console: event, initiator: request?.initiator, completeness: value.completeness }));
    expect.soft(event).toMatchObject({ line, column });
    expect.soft(request?.initiator).toMatchObject({ line, column });
    for (const section of ["console_events", "network_initiators"]) expect.soft(value.completeness.excluded.some((e: any) => e.section === section && e.reason === "invalid_protocol_value")).toBe(line === null);
  } finally {
    await client.close(); await server.close(); await session.close(); await browser.close();
  }
}, 30_000);
TS
npx vitest run tests/boundary/mcp/consoleOffsets.test.ts
rm tests/boundary/mcp/consoleOffsets.test.ts

Expected behavior

Keep malformed source offsets unknown and record invalid protocol coverage for the affected section, while preserving observed zero and valid integer positions. AGENTS.md requires source evidence and uncertainty to survive boundaries, including explicit unknowns when normalization loses information. At the stated commit, docs/browser-observation.md:454,486 promises retained initiator/console source locations and documents invalid-value handling for other malformed frame and console facts; src/domain/browserObservationSchemas.ts:127-128,151-152 already permits null coordinates. Returning another definite source position without disclosing the substitution loses the producer's evidence.

Impact and scope

P2: the capture presents a different precise source position as an observation. The scope demonstrated here is malformed finite CDP offsets in retained console and network-initiator frames; valid integer controls are unaffected. Frequency with real browsers and downstream analyst consequences are NOT_ENOUGH_INFO. No tested caller workaround recovers the discarded offsets from this result. No code execution, confidentiality, privilege or resource-exhaustion effect was observed.

REA version

Exact output of node scripts/rea.mjs --version: 6.8.0. Source commit ded6542d93, rebuilt with npm run build:cached.

Runtime and platform

Node.js v24.22.1; macOS, Darwin kernel 27.0.0; arm64. Locked checkout dependencies; simulated CDP producer.

Target and artifact context

A synthetic allowed loopback page and one source frame in app.js. Runtime.consoleAPICalled has a valid timestamp and empty argument inventory. Network.requestWillBeSent has a valid GET request and script initiator. Only the two source offsets vary.

Interface

MCP

Earliest failure stage

Capture or observation

Relevant output or evidence

The executed public MCP calls returned the following fields for both console.events and network.requests[].initiator:

Producer lineNumber / columnNumber Returned line / column Invalid-value exclusion in either affected section
-5 / -2 0 / 0 absent
7.9 / 3.2 7 / 3 absent
0 / 0 0 / 0 absent
7 / 3 7 / 3 absent

The runner reported 2 failed and 2 passed tests. All four MCP calls succeeded; the failures are assertions against the required evidence semantics. Evidence provenance: observed for these calls, output fields and assertions; source-derived for the contract and cause below.

Typed outcome or recovery expected

Return null for each malformed coordinate, with invalid_protocol_value coverage identifying the affected console_events or network_initiators section, or an equivalently explicit unknown outcome. Preserve valid 0/0 and 7/3 coordinates.

Analysis or suspected cause

Source-derived: src/browser/CdpCaptureEventHelpers.ts:60-63 accepts any finite number and clamps/truncates it. src/browser/CdpCaptureEventHandlers.ts:360-361,534-535 applies that helper to both positions. The preceding stack checks validate container shape, but no coordinate-validity check adds a completeness exclusion.

Additional context

Related closed issue https://github.com/morluto/rea/issues/5934 and merged PR https://github.com/morluto/rea/pull/6016 cover malformed argument inventories and stack/frame container shapes; they do not cover numeric offset admission. Closed https://github.com/morluto/rea/issues/5656 covers console timestamp substitution. Open https://github.com/morluto/rea/issues/7329 concerns comparison of unavailable WebMCP sources, with different fields and acceptance criteria. No matching open report was found in the issue/PR and title/body snapshot searches.

Real-browser occurrence frequency, other platforms, published-package behavior and regression onset were not tested. Restoration proposal: rerun the same four cases; both malformed cases should satisfy the unknown/coverage assertions, and the two integer controls should continue passing.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The public MCP result replaces malformed source evidence with definite different coordinates without an invalid-value marker, a bounded behavior defect beyond cosmetic output; valid controls pass and no severe wider consequence was established.
  • Workaround status: unverified
  • Workaround basis: No caller-side procedure was demonstrated that recovers or reliably identifies the discarded producer offsets from the returned capture.
Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7370 ## 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 The public inspect_web_page MCP tool successfully returns fabricated source coordinates when a selected CDP producer sends malformed finite offsets. In the observed console event and request initiator, lineNumber -5 and columnNumber -2 became line 0 and column 0; 7.9 and 3.2 became 7 and 3. Neither console_events nor network_initiators received an invalid_protocol_value exclusion. Ordinary attach-window and policy limitations remained. ## Reproduction From a fresh checkout at the source commit below, use Node 24.22.1 and prepare locked dependencies with npm ci && npm run build:cached. The following is the independently executed four-case MCP probe, with portable directory names. It uses the repository's fake CDP server over loopback HTTP/WebSocket; no real browser or external service is needed. Each case ran once. The two malformed cases fail assertions for unknown coordinates and invalid-value coverage; both valid controls pass. ~~~sh WORK="$(mktemp -d)" mkdir -p "$WORK/home" "$WORK/config" "$WORK/data" "$WORK/cache" "$WORK/state" "$WORK/temp" export HOME="$WORK/home" XDG_CONFIG_HOME="$WORK/config" XDG_DATA_HOME="$WORK/data" export XDG_CACHE_HOME="$WORK/cache" XDG_STATE_HOME="$WORK/state" TMPDIR="$WORK/temp" cat > tests/boundary/mcp/consoleOffsets.test.ts <<'TS' import { Client, InMemoryTransport } from "@modelcontextprotocol/client"; import { expect, it } from "vitest"; import { createTestBinarySession } from "../../fixtures/binarySession.js"; import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js"; import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js"; import { createServer } from "../../../src/server/createServer.js"; import { observed } from "../../fixtures/analysisExecution.js"; import { ok } from "../../../src/domain/result.js"; it.each([[-5, -2, null, null], [7.9, 3.2, null, null], [0, 0, 0, 0], [7, 3, 7, 3]])("preserves source offsets %s/%s", async (lineNumber, columnNumber, line, column) => { const browser = await startFakeCdpBrowser({ commandEvents(command, origin) { const session = command.sessionId === undefined ? {} : { sessionId: command.sessionId }; const frame = { url: `${origin}/app.js`, lineNumber, columnNumber }; if (command.method === "Runtime.enable") return [{ ...session, method: "Runtime.consoleAPICalled", params: { type: "log", timestamp: 1, args: [], stackTrace: { callFrames: [frame] } } }]; if (command.method === "Network.enable") return [{ ...session, method: "Network.requestWillBeSent", params: { requestId: "offset-probe", timestamp: 1, type: "Script", request: { url: `${origin}/app.js`, method: "GET" }, initiator: { type: "script", stack: { callFrames: [frame] } } } }]; return undefined; } }); const session = createTestBinarySession(() => ({ execute: () => Promise.resolve(observed(null)), close: () => Promise.resolve(ok(null)) })); const server = createServer({ kind: "session", session }, { environment: { ...process.env }, browserObservation: new CdpBrowserProvider(), availabilityPolicy: () => ({ processCaptureEnabled: false, investigationInputRoots: 0 }) }); const client = new Client({ name: "offset-verifier", version: "1" }); const [ct, st] = InMemoryTransport.createLinkedPair(); try { await server.connect(st); await client.connect(ct); const result = await client.callTool({ name: "inspect_web_page", arguments: { cdp_endpoint: browser.endpoint, target_id: "allowed-page", observation_ms: 0 } }); if (result.isError === true) throw new Error(JSON.stringify(result)); const value = (result.structuredContent as any).normalized_result; const event = value.console.events.find((e: any) => e.timestamp === 1); const request = value.network.requests.find((r: any) => r.request_id === "offset-probe"); console.log(JSON.stringify({ input: { lineNumber, columnNumber }, console: event, initiator: request?.initiator, completeness: value.completeness })); expect.soft(event).toMatchObject({ line, column }); expect.soft(request?.initiator).toMatchObject({ line, column }); for (const section of ["console_events", "network_initiators"]) expect.soft(value.completeness.excluded.some((e: any) => e.section === section && e.reason === "invalid_protocol_value")).toBe(line === null); } finally { await client.close(); await server.close(); await session.close(); await browser.close(); } }, 30_000); TS npx vitest run tests/boundary/mcp/consoleOffsets.test.ts rm tests/boundary/mcp/consoleOffsets.test.ts ~~~ ## Expected behavior Keep malformed source offsets unknown and record invalid protocol coverage for the affected section, while preserving observed zero and valid integer positions. AGENTS.md requires source evidence and uncertainty to survive boundaries, including explicit unknowns when normalization loses information. At the stated commit, docs/browser-observation.md:454,486 promises retained initiator/console source locations and documents invalid-value handling for other malformed frame and console facts; src/domain/browserObservationSchemas.ts:127-128,151-152 already permits null coordinates. Returning another definite source position without disclosing the substitution loses the producer's evidence. ## Impact and scope P2: the capture presents a different precise source position as an observation. The scope demonstrated here is malformed finite CDP offsets in retained console and network-initiator frames; valid integer controls are unaffected. Frequency with real browsers and downstream analyst consequences are NOT_ENOUGH_INFO. No tested caller workaround recovers the discarded offsets from this result. No code execution, confidentiality, privilege or resource-exhaustion effect was observed. ## REA version Exact output of node scripts/rea.mjs --version: 6.8.0. Source commit ded6542d93a05321b582934c804a91b76a32a0b8, rebuilt with npm run build:cached. ## Runtime and platform Node.js v24.22.1; macOS, Darwin kernel 27.0.0; arm64. Locked checkout dependencies; simulated CDP producer. ## Target and artifact context A synthetic allowed loopback page and one source frame in app.js. Runtime.consoleAPICalled has a valid timestamp and empty argument inventory. Network.requestWillBeSent has a valid GET request and script initiator. Only the two source offsets vary. ## Interface MCP ## Earliest failure stage Capture or observation ## Relevant output or evidence The executed public MCP calls returned the following fields for both console.events and network.requests[].initiator: | Producer lineNumber / columnNumber | Returned line / column | Invalid-value exclusion in either affected section | | --- | --- | --- | | -5 / -2 | 0 / 0 | absent | | 7.9 / 3.2 | 7 / 3 | absent | | 0 / 0 | 0 / 0 | absent | | 7 / 3 | 7 / 3 | absent | The runner reported 2 failed and 2 passed tests. All four MCP calls succeeded; the failures are assertions against the required evidence semantics. Evidence provenance: observed for these calls, output fields and assertions; source-derived for the contract and cause below. ## Typed outcome or recovery expected Return null for each malformed coordinate, with invalid_protocol_value coverage identifying the affected console_events or network_initiators section, or an equivalently explicit unknown outcome. Preserve valid 0/0 and 7/3 coordinates. ## Analysis or suspected cause Source-derived: src/browser/CdpCaptureEventHelpers.ts:60-63 accepts any finite number and clamps/truncates it. src/browser/CdpCaptureEventHandlers.ts:360-361,534-535 applies that helper to both positions. The preceding stack checks validate container shape, but no coordinate-validity check adds a completeness exclusion. ## Additional context Related closed issue https://github.com/morluto/rea/issues/5934 and merged PR https://github.com/morluto/rea/pull/6016 cover malformed argument inventories and stack/frame container shapes; they do not cover numeric offset admission. Closed https://github.com/morluto/rea/issues/5656 covers console timestamp substitution. Open https://github.com/morluto/rea/issues/7329 concerns comparison of unavailable WebMCP sources, with different fields and acceptance criteria. No matching open report was found in the issue/PR and title/body snapshot searches. Real-browser occurrence frequency, other platforms, published-package behavior and regression onset were not tested. Restoration proposal: rerun the same four cases; both malformed cases should satisfy the unknown/coverage assertions, and the two integer controls should continue passing. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The public MCP result replaces malformed source evidence with definite different coordinates without an invalid-value marker, a bounded behavior defect beyond cosmetic output; valid controls pass and no severe wider consequence was established. - Workaround status: unverified - Workaround basis: No caller-side procedure was demonstrated that recovers or reliably identifies the discarded producer offsets from the returned capture.
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#4378
No description provided.