[GH-ISSUE #4089] [Bug] Connected browser scenarios can clear caller-owned platform emulation through CDP detach #1612

Open
opened 2026-10-16 21:07:59 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @N0zoM1z0 on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4089

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 connected browser scenario can clear a caller's existing navigator.platform override, while preserving its custom user agent and language. This happens on unmodified main, independently of the attempted locale fix in #3395. The external browser/page remain alive and REA reports disconnected-external; the platform override is nevertheless lost, including in the captured page.

A separate control using raw Node WebSockets reproduced the reset without calling REA or Playwright's CDP-session API: opening and closing a second page-CDP connection is sufficient. This therefore appears to expose a Chromium backend limitation through REA's attachment lifecycle; it is not evidence that REA explicitly issues a platform-reset command.

Reproduction

Executed public-CLI probe on main 1b8a9c831b9e01c413156956a0e646c2132e75c4:

  1. Use the isolated browser/local HTTP fixture from #3395. Keep the original caller's page CDP session alive throughout the capture; do not detach it after target discovery.
  2. Through that caller session, send Emulation.setUserAgentOverride with userAgent: "ReaCallerUA/9.0", acceptLanguage: "de-DE,de", platform: "ReaCallerPlatform", and custom UA client hints. Navigate the owned fixture and observe navigator.platform === "ReaCallerPlatform".
  3. Run the public capture-browser-scenario INPUT_JSON --json CLI, with that loopback endpoint/exact target ID, the local HTTP URL, environment.locale: "fr-FR", a 100 ms wait action and final DOM capture. The fixture records navigator language, Intl locale and navigator platform in body data attributes.
  4. Inspect the returned DOM and evaluate the original page immediately after REA disconnects, without navigating. Both now report native Linux x86_64 rather than ReaCallerPlatform. The original caller session/browser/page are still alive.

Independent native control, executed three times (empty connection twice, Target.getTargetInfo once):

  1. Open raw WebSocket connection A to a disposable page's webSocketDebuggerUrl.
  2. A sends Emulation.setUserAgentOverride with { "userAgent": "ReaCallerUA/1.0", "acceptLanguage": "de-DE,de", "platform": "ReaCallerPlatform" }.
  3. A evaluates JSON.stringify({platform:navigator.platform,language:navigator.language,ua:navigator.userAgent}) with Runtime.evaluate/returnByValue:true.
  4. Open raw WebSocket connection B to the same URL. Close B without any CDP command (or after Target.getTargetInfo). Wait for its close event, then repeat A's evaluation. Platform resets; UA/language do not. No REA capture or locale-emulation command runs in this control.

Expected behavior

Connecting/disconnecting an observer should preserve caller-owned browser state where supported. If Chromium cannot provide that guarantee, REA should explicitly identify this attachment limitation rather than imply that preserving the browser process also preserves its prior platform emulation. A native reset should not be worked around by blindly replaying incomplete caller UA/client-hint metadata.

Impact and scope

Observed on Linux x86_64 with a disposable caller browser and explicit platform emulation. Default native platform is unchanged in the ordinary control. Other Chromium versions, operating systems and passive observation tools are not verified. The known effect is changed application-visible platform after/during attachment; no executable-byte mutation, browser termination or external data loss was observed. An owning caller can reapply its own override, as repeated controls did; that requires caller cooperation.

REA version

6.5.0; unmodified main 1b8a9c831b9e01c413156956a0e646c2132e75c4 for public CLI reproduction.

Runtime and platform

Linux x86_64, Node.js 24.18.0, npm 11.16.0, locked playwright-core 1.58.2. Chrome for Testing 156.0.8078.4, CDP revision f842f52ef39fd97b449eb27cff414bf05a98ddce. The independent control uses Node's native WebSocket and keeps the caller connection alive.

Target and artifact context

Owned local HTTP fixture; disposable browser/profile; no personal browser, remote target or credentials. Raw control uses the same disposable browser and page CDP endpoint.

Interface

CLI

Earliest failure stage

Capture or observation

Relevant output or evidence

Public capture:

before: platform=ReaCallerPlatform, language=de-DE, UA=ReaCallerUA/9.0
captured DOM: platform=Linux x86_64, language=de-DE, Intl=fr-FR
after (no navigation): platform=Linux x86_64, language=de-DE, UA=ReaCallerUA/9.0
cleanup: disconnected-external; browser/page/caller session preserved

All three raw-WebSocket controls returned:

before: {platform: ReaCallerPlatform, language: de-DE, ua: ReaCallerUA/1.0}
after:  {platform: Linux x86_64, language: de-DE, ua: ReaCallerUA/1.0}

Typed outcome or recovery expected

Preserve known caller state, or report an explicit provider/Chromium limitation with a supported recovery path. A fresh launch avoids relying on prior external emulation state. An upstream/native correction is preferable to unsafe snapshot replay.

Analysis or suspected cause

Source-derived corroboration: Chromium's InspectorEmulationAgent clears user-agent overrides in disable(), and setUserAgentOverride() writes the page-global navigator-platform setting even for an omitted platform. This HEAD source is corroborating evidence, not a source/build match or a complete root-cause proof. The raw control establishes that REA is not required to trigger the reset.

Additional context

Discovered while validating #3395. PR #4088 only discloses attached locale coverage and does not introduce UA/platform emulation or claim to fix this separate platform-state limitation. No known-good browser version or regression range has been established.

Originally created by @N0zoM1z0 on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4089 ### 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 connected browser scenario can clear a caller's existing `navigator.platform` override, while preserving its custom user agent and language. This happens on unmodified main, independently of the attempted locale fix in #3395. The external browser/page remain alive and REA reports `disconnected-external`; the platform override is nevertheless lost, including in the captured page. A separate control using raw Node WebSockets reproduced the reset **without calling REA or Playwright's CDP-session API**: opening and closing a second page-CDP connection is sufficient. This therefore appears to expose a Chromium backend limitation through REA's attachment lifecycle; it is not evidence that REA explicitly issues a platform-reset command. ### Reproduction Executed public-CLI probe on main `1b8a9c831b9e01c413156956a0e646c2132e75c4`: 1. Use the isolated browser/local HTTP fixture from #3395. Keep the original caller's page CDP session alive throughout the capture; do not detach it after target discovery. 2. Through that caller session, send `Emulation.setUserAgentOverride` with `userAgent: "ReaCallerUA/9.0"`, `acceptLanguage: "de-DE,de"`, `platform: "ReaCallerPlatform"`, and custom UA client hints. Navigate the owned fixture and observe `navigator.platform === "ReaCallerPlatform"`. 3. Run the public `capture-browser-scenario INPUT_JSON --json` CLI, with that loopback endpoint/exact target ID, the local HTTP URL, `environment.locale: "fr-FR"`, a 100 ms wait action and final DOM capture. The fixture records navigator language, Intl locale and navigator platform in body data attributes. 4. Inspect the returned DOM and evaluate the original page immediately after REA disconnects, without navigating. Both now report native `Linux x86_64` rather than `ReaCallerPlatform`. The original caller session/browser/page are still alive. Independent native control, executed three times (empty connection twice, `Target.getTargetInfo` once): 1. Open raw WebSocket connection A to a disposable page's `webSocketDebuggerUrl`. 2. A sends `Emulation.setUserAgentOverride` with `{ "userAgent": "ReaCallerUA/1.0", "acceptLanguage": "de-DE,de", "platform": "ReaCallerPlatform" }`. 3. A evaluates `JSON.stringify({platform:navigator.platform,language:navigator.language,ua:navigator.userAgent})` with `Runtime.evaluate`/`returnByValue:true`. 4. Open raw WebSocket connection B to the same URL. Close B without any CDP command (or after `Target.getTargetInfo`). Wait for its close event, then repeat A's evaluation. Platform resets; UA/language do not. No REA capture or locale-emulation command runs in this control. ### Expected behavior Connecting/disconnecting an observer should preserve caller-owned browser state where supported. If Chromium cannot provide that guarantee, REA should explicitly identify this attachment limitation rather than imply that preserving the browser process also preserves its prior platform emulation. A native reset should not be worked around by blindly replaying incomplete caller UA/client-hint metadata. ### Impact and scope Observed on Linux x86_64 with a disposable caller browser and explicit platform emulation. Default native platform is unchanged in the ordinary control. Other Chromium versions, operating systems and passive observation tools are **not verified**. The known effect is changed application-visible platform after/during attachment; no executable-byte mutation, browser termination or external data loss was observed. An owning caller can reapply its own override, as repeated controls did; that requires caller cooperation. ### REA version 6.5.0; unmodified main `1b8a9c831b9e01c413156956a0e646c2132e75c4` for public CLI reproduction. ### Runtime and platform Linux x86_64, Node.js 24.18.0, npm 11.16.0, locked playwright-core 1.58.2. Chrome for Testing **156.0.8078.4**, CDP revision `f842f52ef39fd97b449eb27cff414bf05a98ddce`. The independent control uses Node's native WebSocket and keeps the caller connection alive. ### Target and artifact context Owned local HTTP fixture; disposable browser/profile; no personal browser, remote target or credentials. Raw control uses the same disposable browser and page CDP endpoint. ### Interface CLI ### Earliest failure stage Capture or observation ### Relevant output or evidence Public capture: ```text before: platform=ReaCallerPlatform, language=de-DE, UA=ReaCallerUA/9.0 captured DOM: platform=Linux x86_64, language=de-DE, Intl=fr-FR after (no navigation): platform=Linux x86_64, language=de-DE, UA=ReaCallerUA/9.0 cleanup: disconnected-external; browser/page/caller session preserved ``` All three raw-WebSocket controls returned: ```text before: {platform: ReaCallerPlatform, language: de-DE, ua: ReaCallerUA/1.0} after: {platform: Linux x86_64, language: de-DE, ua: ReaCallerUA/1.0} ``` ### Typed outcome or recovery expected Preserve known caller state, or report an explicit provider/Chromium limitation with a supported recovery path. A fresh launch avoids relying on prior external emulation state. An upstream/native correction is preferable to unsafe snapshot replay. ### Analysis or suspected cause Source-derived corroboration: Chromium's [InspectorEmulationAgent](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/core/inspector/inspector_emulation_agent.cc) clears user-agent overrides in `disable()`, and `setUserAgentOverride()` writes the page-global navigator-platform setting even for an omitted platform. This HEAD source is corroborating evidence, not a source/build match or a complete root-cause proof. The raw control establishes that REA is not required to trigger the reset. ### Additional context Discovered while validating #3395. PR #4088 only discloses attached **locale** coverage and does not introduce UA/platform emulation or claim to fix this separate platform-state limitation. No known-good browser version or regression range has been established.
Author
Owner

@SoGeRiEn commented on GitHub (Oct 15, 2026):

Documented on fix/reliability-pack-20261015 at 12309d27. Chromium owns this reset; REA does not replay incomplete caller emulation. Use launch or reapply the override after disconnect.

<!-- gh-comment-id:6192272677 --> @SoGeRiEn commented on GitHub (Oct 15, 2026): Documented on `fix/reliability-pack-20261015` at 12309d27. Chromium owns this reset; REA does not replay incomplete caller emulation. Use launch or reapply the override after disconnect.
Author
Owner

@CassieFox23 commented on GitHub (Oct 18, 2026):

TL;DR: I can reproduce this on main. The platform reset comes from Chromium. A second page connection that only opens and closes is enough.

Results

I ran it on main 32f0e5120 with REA 6.7.0 and Chromium 154.0.8037.57.

Check Node 22.23.1 Node 24.18.0 Node 26.11.1
Raw control: empty second connection closes platform reset platform reset platform reset
Raw control: one Target.getTargetInfo first platform reset platform reset platform reset
Public capture-browser-scenario in connect mode platform reset platform reset platform reset
  • Before the capture, the page reports ReaCallerPlatform.
  • After it, the page reports Linux x86_64.
  • The user agent and de-DE language are kept.
  • The captured DOM already shows Linux x86_64.
  • The caller sent its override again on Node 22. The platform came back.
  • A browser-level attach and detach also resets it (Node 24 only).
  • The output now carries the limitation text from 2ff086796.

Not run

  • Other Chromium versions, and other systems.
  • I did not read the Chromium source.
Commands
  • Raw control: my own script opened connection A to a page, set the override, opened and closed connection B, then re-read the platform. Each Node ran it once per variant.
  • Public run: rea capture-browser-scenario INPUT_JSON --json with browser.mode set to connect, the exact target id, environment.locale set to fr-FR, a 100 ms wait and a final DOM capture.
  • Build: npm ci, npm run prepare, npm run build:cached, npm run test:prepare.
Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 22.23.1, 24.18.0, 26.11.1
  • Head / base: 32f0e51209 / none
  • Other: Chromium 154.0.8037.57 (BSD-3-Clause)

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6255605281 --> @CassieFox23 commented on GitHub (Oct 18, 2026): **TL;DR:** I can reproduce this on main. The platform reset comes from Chromium. A second page connection that only opens and closes is enough. ### Results I ran it on main 32f0e5120 with REA 6.7.0 and Chromium `154.0.8037.57`. | Check | Node 22.23.1 | Node 24.18.0 | Node 26.11.1 | |---|---|---|---| | Raw control: empty second connection closes | platform reset | platform reset | platform reset | | Raw control: one `Target.getTargetInfo` first | platform reset | platform reset | platform reset | | Public `capture-browser-scenario` in connect mode | platform reset | platform reset | platform reset | - Before the capture, the page reports `ReaCallerPlatform`. - After it, the page reports `Linux x86_64`. - The user agent and `de-DE` language are kept. - The captured DOM already shows `Linux x86_64`. - The caller sent its override again on Node 22. The platform came back. - A browser-level attach and detach also resets it (Node 24 only). - The output now carries the limitation text from 2ff086796. ### Not run - Other Chromium versions, and other systems. - I did not read the Chromium source. <details> <summary>Commands</summary> - Raw control: my own script opened connection A to a page, set the override, opened and closed connection B, then re-read the platform. Each Node ran it once per variant. - Public run: `rea capture-browser-scenario INPUT_JSON --json` with `browser.mode` set to `connect`, the exact target id, `environment.locale` set to `fr-FR`, a 100 ms wait and a final DOM capture. - Build: `npm ci`, `npm run prepare`, `npm run build:cached`, `npm run test:prepare`. </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 22.23.1, 24.18.0, 26.11.1 - Head / base: 32f0e5120966be59d17a1e092353816c76a9c5e2 / none - Other: Chromium 154.0.8037.57 (BSD-3-Clause) </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

@morluto commented on GitHub (Oct 26, 2026):

Thanks for isolating the Chromium behavior with a raw-CDP control. #7188 has since changed connected-browser initialization to avoid implicit default-context overrides, but its reported validation does not establish that the separate platform reset on page-CDP detach is resolved. I am keeping this issue open for that limitation and its recovery guidance; I did not rerun Chromium in this triage.

<!-- gh-comment-id:6428640176 --> @morluto commented on GitHub (Oct 26, 2026): Thanks for isolating the Chromium behavior with a raw-CDP control. #7188 has since changed connected-browser initialization to avoid implicit default-context overrides, but its reported validation does not establish that the separate platform reset on page-CDP detach is resolved. I am keeping this issue open for that limitation and its recovery guidance; I did not rerun Chromium in this triage.
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#1612
No description provided.