[PR #1411] [CLOSED] fix(browser): resolve authorized scope when Chrome masks frame URLs #2865

Closed
opened 2026-10-17 21:11:11 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1411
Author: @BaaRai
Created: 10/10/2026
Status: ❌ Closed

Base: main ← Head: fix/cdp-masked-main-frame-url


📝 Commits (3)

  • 2374466 fix(browser): resolve authorized scope when Chrome masks frame URLs
  • 6ff26b2 fix(browser): keep navigation-commit wait ahead of masked-URL fallback
  • 08a6aab fix(browser): match every masked storage rejection wording

📊 Changes

6 files changed (+198 additions, -29 deletions)

View changed files

📝 src/browser/CdpAuthorizedMainFrame.ts (+20 -3)
📝 src/browser/CdpCaptureDocuments.ts (+55 -4)
📝 src/browser/CdpCaptureStorage.ts (+60 -16)
📝 src/browser/CdpCaptureValues.ts (+14 -0)
📝 src/browser/CdpPageCapture.ts (+23 -4)
📝 src/browser/CdpWebMcpDiscovery.ts (+26 -2)

📄 Description

Implements the recovery described in #1384 at the gating surfaces. Linking the issue for the full protocol matrix; summary below.

Summary

Official Chrome and Chromium-for-Testing builds, at least 150 and 154, report masked values where CDP consumers expect URLs: Page.getFrameTree returns ":" (150) or "" (154) as the main-frame URL with securityOrigin: "://", Page.getNavigationHistory returns empty strings, DOMSnapshot documents carry no documentURL, and the storage agents reject or stall on every otherwise-valid request. Fedora's distro build of 154 reports real URLs, which is why this only reproduces on official builds. Every origin gate rejected these targets as out of scope, so inspect_web_page failed on the first call with target_not_allowed.

Scope resolution now falls back to the target URL that authorizeCdpTarget already gated the attach on, but only when a frame-tree URL carries no URL evidence at all; a parsed but out-of-scope origin still rejects, and parseable non-HTTP pages such as about:blank keep their existing wait semantics.

Problem and expected behavior

Smallest trigger: inspect_web_page (or npm run verify:browser) against any official Chrome-family build with an allowed origin. Reproduced on Kali Chromium 150.0.7871.181, Chrome-for-Testing 150.0.7871.124, and Chrome-for-Testing 154.0.8037.57; each rejected at authorizedMainFrame or the discovery gate before any capture.

Violated invariant: absence of URL evidence was treated as out-of-scope evidence. A frame tree that reports no parseable URL cannot prove the target left the authorized scope, and the attach was already authorized against the target URL, so rejecting discards an authorized observation without a contradicting fact.

Change and scope

  • CdpCaptureValues.ts: isUnparseableFrameUrl recognizes the masked placeholders (":", the empty string) while treating an absent field and parseable non-HTTP URLs as before.
  • CdpWebMcpDiscovery.ts: both gates resolve through authorizedMainFrameUrl, which falls back to the attach-authorized target URL only for URL-less evidence.
  • CdpAuthorizedMainFrame.ts: the same fallback ends the 80-poll wait that could never succeed; parseable non-HTTP URLs keep waiting.
  • CdpCaptureDocuments.ts: captureFrames recovers the root frame from the authorized target URL (child frames are unaffected); resolvedMainFrameUrl provides the attached/completed main-frame URLs, which previously crashed new URL(":") in captureStorage; captureDom recovers the leading DOM document's URL the same way.
  • CdpPageCapture.ts: threads the authorized target URL to the call sites above.
  • CdpCaptureStorage.ts: storage probes disclose browser-side command rejections as limitations (matching the sibling optional-command semantics) and skip the deeper fingerprint probes once their own discovery was unavailable, which also removes the stall where a fingerprint probe never responds.

Intentionally not included: the script and resource capture surfaces filter by masked URLs the same way and remain excluded with disclosed reasons on masked builds; finishing them is mechanical per surface or a unified authorized-origin pass, whichever you prefer. No contract or schema changes; the allowlist enforcement is unchanged, only its URL source when the frame tree reports none.

Contract and boundary impact

  • Semantic owner and earliest changed stage: browser provider capture (src/browser); no domain or contract changes.
  • CLI and MCP/tool-catalog contract: none.
  • Provider, bridge, target-format, or platform compatibility: restores official Chrome-family builds as usable inspection targets; no change when frame trees report real URLs.
  • Evidence, artifact, provenance, or reconstruction contract: masked-run captures now disclose storage unavailability as limitations instead of failing the observation.
  • Process execution, authorization, cleanup, or containment impact: none; authorization semantics unchanged.
  • Generated metadata, package, or installation impact: none.

Evidence and regression coverage

  • Tests added or updated: none; the recovery is exercised end to end by the real-browser lane and the behavior on healthy builds is unchanged by construction (every fallback triggers only on URL-less evidence, which previously could only fail).
  • Base reproduction and other evidence, all on Linux x64, --headless=new:
    • Base: inspect_web_page fails target_not_allowed at the first gate on all three official builds; unmodified main also fails verify:browser on Chrome-for-Testing 154.
    • This change: on Chrome-for-Testing 154 the lane now runs the entire pipeline with the root frame and DOM document captured, storage unavailability disclosed as limitations, and no stall; it currently stops at the script-metadata assertion, which is the remaining script-surface work noted above.
    • Probe matrix across builds (frame-tree placeholder, securityOrigin, navigation history, target info) is in #1384; Target.getTargetInfo and the /json endpoint still report real URLs, which is what the fallback uses.
    • npm run check:fast passes.
  • User-visible CLI/MCP output: inspect_web_page returns complete observations on official Chrome builds instead of target_not_allowed; masked storage dimensions appear as disclosed limitations.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/1411 **Author:** [@BaaRai](https://github.com/BaaRai) **Created:** 10/10/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/cdp-masked-main-frame-url` --- ### 📝 Commits (3) - [`2374466`](https://github.com/morluto/rea/commit/23744663a18c7d82a279565b09296686a9b43afb) fix(browser): resolve authorized scope when Chrome masks frame URLs - [`6ff26b2`](https://github.com/morluto/rea/commit/6ff26b2adf89763e605f0fdc7a7f35525e951ff5) fix(browser): keep navigation-commit wait ahead of masked-URL fallback - [`08a6aab`](https://github.com/morluto/rea/commit/08a6aab51c68505e9de4586c77d4bc1a1beb0876) fix(browser): match every masked storage rejection wording ### 📊 Changes **6 files changed** (+198 additions, -29 deletions) <details> <summary>View changed files</summary> 📝 `src/browser/CdpAuthorizedMainFrame.ts` (+20 -3) 📝 `src/browser/CdpCaptureDocuments.ts` (+55 -4) 📝 `src/browser/CdpCaptureStorage.ts` (+60 -16) 📝 `src/browser/CdpCaptureValues.ts` (+14 -0) 📝 `src/browser/CdpPageCapture.ts` (+23 -4) 📝 `src/browser/CdpWebMcpDiscovery.ts` (+26 -2) </details> ### 📄 Description Implements the recovery described in #1384 at the gating surfaces. Linking the issue for the full protocol matrix; summary below. ## Summary Official Chrome and Chromium-for-Testing builds, at least 150 and 154, report masked values where CDP consumers expect URLs: `Page.getFrameTree` returns `":"` (150) or `""` (154) as the main-frame URL with `securityOrigin: "://"`, `Page.getNavigationHistory` returns empty strings, `DOMSnapshot` documents carry no documentURL, and the storage agents reject or stall on every otherwise-valid request. Fedora's distro build of 154 reports real URLs, which is why this only reproduces on official builds. Every origin gate rejected these targets as out of scope, so `inspect_web_page` failed on the first call with `target_not_allowed`. Scope resolution now falls back to the target URL that `authorizeCdpTarget` already gated the attach on, but only when a frame-tree URL carries no URL evidence at all; a parsed but out-of-scope origin still rejects, and parseable non-HTTP pages such as `about:blank` keep their existing wait semantics. ## Problem and expected behavior Smallest trigger: `inspect_web_page` (or `npm run verify:browser`) against any official Chrome-family build with an allowed origin. Reproduced on Kali Chromium 150.0.7871.181, Chrome-for-Testing 150.0.7871.124, and Chrome-for-Testing 154.0.8037.57; each rejected at `authorizedMainFrame` or the discovery gate before any capture. Violated invariant: absence of URL evidence was treated as out-of-scope evidence. A frame tree that reports no parseable URL cannot prove the target left the authorized scope, and the attach was already authorized against the target URL, so rejecting discards an authorized observation without a contradicting fact. ## Change and scope - `CdpCaptureValues.ts`: `isUnparseableFrameUrl` recognizes the masked placeholders (`":"`, the empty string) while treating an absent field and parseable non-HTTP URLs as before. - `CdpWebMcpDiscovery.ts`: both gates resolve through `authorizedMainFrameUrl`, which falls back to the attach-authorized target URL only for URL-less evidence. - `CdpAuthorizedMainFrame.ts`: the same fallback ends the 80-poll wait that could never succeed; parseable non-HTTP URLs keep waiting. - `CdpCaptureDocuments.ts`: `captureFrames` recovers the root frame from the authorized target URL (child frames are unaffected); `resolvedMainFrameUrl` provides the attached/completed main-frame URLs, which previously crashed `new URL(":")` in `captureStorage`; `captureDom` recovers the leading DOM document's URL the same way. - `CdpPageCapture.ts`: threads the authorized target URL to the call sites above. - `CdpCaptureStorage.ts`: storage probes disclose browser-side command rejections as limitations (matching the sibling optional-command semantics) and skip the deeper fingerprint probes once their own discovery was unavailable, which also removes the stall where a fingerprint probe never responds. Intentionally not included: the script and resource capture surfaces filter by masked URLs the same way and remain excluded with disclosed reasons on masked builds; finishing them is mechanical per surface or a unified authorized-origin pass, whichever you prefer. No contract or schema changes; the allowlist enforcement is unchanged, only its URL source when the frame tree reports none. ## Contract and boundary impact - Semantic owner and earliest changed stage: browser provider capture (src/browser); no domain or contract changes. - CLI and MCP/tool-catalog contract: none. - Provider, bridge, target-format, or platform compatibility: restores official Chrome-family builds as usable inspection targets; no change when frame trees report real URLs. - Evidence, artifact, provenance, or reconstruction contract: masked-run captures now disclose storage unavailability as limitations instead of failing the observation. - Process execution, authorization, cleanup, or containment impact: none; authorization semantics unchanged. - Generated metadata, package, or installation impact: none. ## Evidence and regression coverage - Tests added or updated: none; the recovery is exercised end to end by the real-browser lane and the behavior on healthy builds is unchanged by construction (every fallback triggers only on URL-less evidence, which previously could only fail). - Base reproduction and other evidence, all on Linux x64, `--headless=new`: - Base: `inspect_web_page` fails `target_not_allowed` at the first gate on all three official builds; unmodified main also fails `verify:browser` on Chrome-for-Testing 154. - This change: on Chrome-for-Testing 154 the lane now runs the entire pipeline with the root frame and DOM document captured, storage unavailability disclosed as limitations, and no stall; it currently stops at the script-metadata assertion, which is the remaining script-surface work noted above. - Probe matrix across builds (frame-tree placeholder, securityOrigin, navigation history, target info) is in #1384; `Target.getTargetInfo` and the `/json` endpoint still report real URLs, which is what the fallback uses. - `npm run check:fast` passes. - User-visible CLI/MCP output: `inspect_web_page` returns complete observations on official Chrome builds instead of `target_not_allowed`; masked storage dimensions appear as disclosed limitations. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-17 21:11:11 +00:00
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#2865
No description provided.