[PR #2957] [CLOSED] fix(browser): enforce and release attached service-worker policy #5516

Closed
opened 2026-10-31 01:07:15 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/2957
Author: @dakjdakd
Created: 10/13/2026
Status: ❌ Closed

Base: main ← Head: fix/browser-service-worker-policy


📝 Commits (4)

  • edaf40f fix(browser): enforce and release attached service-worker policy
  • 68cc9f8 fix(browser): release owned service-worker policy across navigation
  • 0462f6c fix(browser): reject uninstalled service-worker policies
  • c909778 fix(browser): cancel and bound service-worker policy setup

📊 Changes

7 files changed (+1519 additions, -40 deletions)

View changed files

📝 docs/browser-scenario-contract.md (+7 -0)
➕ scripts/lib/browser-scenario-service-worker-setup-e2e.mjs (+260 -0)
➕ scripts/lib/browser-scenario-service-workers-e2e.mjs (+605 -0)
📝 scripts/verify-real-browser.mjs (+7 -0)
➕ src/browser/PlaywrightScenarioServiceWorkers.ts (+307 -0)
📝 src/browser/PlaywrightScenarioSession.test.ts (+300 -9)
📝 src/browser/PlaywrightScenarioSession.ts (+33 -31)

📄 Description

Summary

An attached browser scenario could accept service_workers: "block" while collecting a page controlled by an existing worker. The admission check ran against the old document before navigation; starting from about:blank therefore bypassed registrations belonging to the actual destination. The same policy also left a nonconfigurable register replacement in the caller's live document after disconnect.

This change validates the current frames before accepting their capture and gives the temporary registration wrapper an explicit owner. Worker-influenced captures are rejected, caller-owned registrations survive, and cleanup restores the property state the scenario replaced. Cleanup removes the owned future-document scripts before restoring live frames, and cancelled navigation is stopped even when the scenario has no storage seeds.

Fixes #2956

Problem and expected behavior

The smallest trigger is an external Chrome profile with a worker registered for a local origin, whose selected tab has moved to about:blank. A block scenario reconnects to that tab and navigates back to the origin. Previously, scenario_start and the following wait step completed even though navigator.serviceWorker.controller was non-null and a probe request returned a worker-generated response.

The existing policy already treats caller-owned registrations as a reason to reject attachment rather than something REA may remove. That decision must apply to the documents actually observed, including destinations reached by later actions and child frames. A block request should not become silently permissive after the first navigation.

Policy cleanup has the corresponding ownership invariant: a scenario may temporarily replace registration behavior, but must release its own replacement when it disconnects. The original descriptor, a deliberately absent property, and a subsequent caller replacement are distinct states. Cleanup must preserve that distinction rather than always assigning the browser's native method.

Change and scope

PlaywrightScenarioServiceWorkers owns the existing attached registration policy. It checks controller and registrations in every current frame before installation. Each wrapper keeps the original descriptor inside its closure; its public symbol marker contains only an ownership boolean. A restoration function requires a separate, randomly generated session token and does not return the original descriptor. Enumerating the wrapper's symbols therefore does not expose the native registration method. The wrapper remains configurable so its owner can restore it.

The owner reuses the existing CDP endpoint and target-session helpers to attach to the caller-selected page. It installs a tracked Page.addScriptToEvaluateOnNewDocument script in that page and its out-of-process iframe targets. Iframe-only auto-attachment preserves pre-script registration blocking across renderer boundaries; workers and popups are excluded. Each owned script has its own protocol identifier. A frame is resumed after installation, including when installation fails, and repeated installation in the same realm recognizes the existing wrapper. The owner also verifies the actual registration wrapper in the current realm before accepting initialization or capture. A CDP script identifier alone is insufficient: if the page prevents property replacement, such as through a non-configurable descriptor, attached block observation is rejected instead of silently accepting an uninstalled policy.

PlaywrightScenarioSession creates the owner only for attached block scenarios. It validates immediately after initial navigation and at the shared capture boundary used after each action. This catches ordinary goto, click navigation, and embedded-frame cases without distributing URL-specific guards through the action implementation. An explicit allow request does not create this owner. Launch continues using the existing Playwright-owned context policy.

The capture check deliberately rejects an existing registration even if its controller is currently null. Registration existence and current control are separate browser facts; either is already incompatible with the attached block admission rule. No worker is unregistered to make the check pass.

Cleanup restores a property only when its current function is still the wrapper saved in that realm's closure. If the caller replaced register afterward, that replacement wins. If the original property did not exist, restoration deletes the temporary property rather than inventing a descriptor. The ownership marker is used to detect unresolved restoration, not to store the original method. A wrong restoration token leaves the block in place.

Policy setup has its own abort controller and uses the existing 10-second CDP discovery deadline across the initial read-only frame check, endpoint discovery, WebSocket handshake, and target attachment. Closing the owner aborts that setup lifetime before awaiting initialization. A peer that never sends the WebSocket upgrade response therefore cannot hold initialization open indefinitely, and a stalled frame preflight cannot start a late attachment after cleanup.

That setup signal is deliberately not passed to Page.addScriptToEvaluateOnNewDocument: once a page mutation has been sent, the owner must receive its script identifier and remove that exact script before releasing ownership. Cleanup first closes admission for new policy installation and waits for already-sent installation requests to settle. For cancellation or failed navigation, this policy owner sends Page.stopLoading independently of storage seeding. Restoration does not wait for the page or a child frame to emit load, so an image or child response that never finishes cannot strand cleanup behind that event.

The owner then removes every tracked future-document script before restoring any live frame. A main-frame navigation during child-frame restoration consequently creates a document without the wrapper. Caller-owned init scripts remain installed. Finally, cleanup detaches the owned target session and closes its socket before the scenario releases its transport and target lease. It uses the existing execution boundary and retains the owner when protocol or live-document restoration cannot be confirmed. Retry preserves completed cleanup stages rather than attempting to detach a session that was already released. Initialization-failure cleanup also awaits the primary browser owner using its independent cleanup lifetime, rather than the already-aborted scenario signal. Confirmed release then makes the same selected target available to a subsequent scenario.

The seven files have specific responsibilities:

  • src/browser/PlaywrightScenarioServiceWorkers.ts owns bounded and cancellable setup, frame checks, private registration state, page/iframe script identifiers, stopped navigation, exact property restoration, and cleanup retry state.
  • src/browser/PlaywrightScenarioSession.ts connects that owner to initialization, initial navigation, per-step capture, and cleanup.
  • src/browser/PlaywrightScenarioSession.test.ts covers restoration failures, cancellation during pending installation or a stalled preflight, suppression of late setup, stopped navigation without storage seeds, installation-before-restoration ordering, and retry after protocol cleanup failure.
  • scripts/lib/browser-scenario-service-workers-e2e.mjs exercises the policy against actual Chromium, including caller ownership and future-document cleanup.
  • scripts/lib/browser-scenario-service-worker-setup-e2e.mjs uses a real loopback proxy to withhold policy discovery or a WebSocket upgrade, then checks connection release and reuse of the selected target.
  • scripts/verify-real-browser.mjs includes that verifier in the existing browser lane.
  • docs/browser-scenario-contract.md states the attached rejection rule and its navigation boundary.

Contribution scope follows the existing browser scenario workflow. A separate maintainer scope decision is not applicable: this is a correction to existing policy enforcement and cleanup, with the same selected target, navigation authority, and temporary registration injection. It adds no general script-execution operation or worker-removal capability.

Contract and boundary impact

  • Semantic owner and earliest changed stage: the attached service-worker policy owner; initialization and post-navigation validation now describe the current documents, followed by capture validation and restoration.
  • Contribution scope fit and linked maintainer decision: ordinary provider bug fix within existing boundaries; a new-capability scope decision is not applicable.
  • CLI and MCP/tool-catalog contract: request schema, tool names, action vocabulary, and catalog are unchanged. Both adapters use the shared provider; incompatible attached block captures now return the existing target_not_allowed failure.
  • Provider, bridge, target-format, or platform compatibility: Chromium connect mode; launch and explicit allow retain their existing paths. No bridge or target-format changes.
  • Evidence, artifact, provenance, or reconstruction contract: existing partial observations and cleanup status remain available. No evidence identifiers, artifact schema, source identity, or reconstruction changes.
  • Target selection, authority, upstream behavior, and cleanup details: the caller-selected endpoint and target remain authoritative. Existing workers are preserved. The policy owner removes its exact page/iframe script identifiers before restoring current frames and releasing its own session/socket, followed by the scenario transport. Caller scripts and registrations remain intact. Unconfirmed restoration retains the owner for retry.
  • Generated metadata, package, or installation impact: no schema, package dependency, installation, authored skill, or generated catalog changes; build outputs remain uncommitted.

Expected effects reflect the existing workflow and its verification:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

The checked mutation effect is the temporary JavaScript registration replacement already present in this workflow, plus the existing disposable profile behavior in verification. Ordinary browsing contacts the requested application's services. No protected-code recovery or new execution authority is introduced.

Evidence and regression coverage

  • Tests added or updated: session regressions cover restoration failure retaining the connection, successful cleanup retry, cancellation during pending script installation, cancellation without storage seeds, and retry after protocol cleanup failure. The real-browser verifier covers fresh registration blocking, initial and action navigation, cross-origin frames, allow, live-document restoration, reload, failure/cancellation cleanup, caller replacement, and original property absence. Additional real-Chrome scenarios cover stalled image and child-frame cancellation, target reuse afterward, private original descriptors, wrong restoration tokens, iframe transitions between processes, navigation between frame restorations, and preservation of caller-owned init scripts. Real Chrome also verifies rejection when the main page or an existing out-of-process iframe prevents registration replacement, when the original property is an accessor, and when a new iframe replaces the policy after injection; cleanup preserves the original descriptor or caller replacement. A real loopback proxy forwards the scenario transport to Chrome while withholding only policy discovery or the policy socket's HTTP 101 response. The production provider passes cancellation for both stalled discovery and handshake, confirms cleanup, and reacquires the same target afterward. A separate run without caller cancellation reaches the existing 10-second deadline with reason timeout, closes the withheld socket, and successfully reuses the target. The direct production policy owner additionally passes a stalled browser-WebSocket handshake case; this is owner-level coverage and adds no endpoint variant to the public scenario schema.
  • Base reproduction or other evidence: executed against the exact Session source from ebaaa79ca02bde4a73013a2a9a781d0b07a57ee4, with unchanged supporting production modules and a real local worker. Block returned completed steps while the controller remained active; the wrapper remained nonconfigurable after disconnect. This establishes the affected Session behavior rather than claiming a complete baseline qualification run.
  • User-visible CLI/MCP output: the fresh attached block CLI scenario still completes with browser.cleanup: "disconnected-external". For the worker-controlled destination, the production provider now returns BrowserObservationError with reason target_not_allowed, retaining cleanup and partial-observation semantics instead of returning completed capture steps. Shared-provider behavior applies beneath CLI/MCP; direct MCP execution is not claimed for this new regression.
  • Remaining proof gaps: the real-provider coverage is the focused Windows x64/Chrome service-worker workflow described here. Complete browser-lane qualification, other operating systems, and other Chrome releases remain outside that coverage.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

The final focused checks used Node.js 24.18.0 and Chrome 154.0.8037.98 on Windows x64:

  • npm run test:focused -- src/browser/PlaywrightScenarioSession.test.ts src/browser/PlaywrightScenarioBrowser.test.ts src/browser/PlaywrightBrowserScenarioProvider.test.ts — 41 tests passed.
  • npm run docs:check — passed, including 5 README consistency tests.
  • npm run typecheck, npm run lint, and npm run knip — passed.
  • npm run check — passed on upstream base 8fef2aa64ec1ee742e8b70b825938d30a0ea7319 with this independent fix applied.
  • npm run verify:test-discovery and npm run verify:architecture-guards — passed.
  • Changed-file oxfmt --check and git diff --check — passed.

The executed focused real-browser harness invoked the new service-worker verifier module with the built CLI and production provider. It passed the observed policy and ownership cases listed above using self-owned loopback fixtures and an isolated disposable Chrome profile. Caller registrations and the external target survived scenario cleanup. The complete verify:browser lane is not reported as passing qualification.

Compatibility, safety, and release

  • Breaking changes or migration steps: no request-shape migration. A previously accepted worker-influenced block capture now rejects; callers intentionally collecting worker-driven behavior can use the existing allow setting.
  • Real Hopper/Ghidra, browser, or OS coverage: actual Chrome 154.0.8037.98 on Windows x64; native analysis engines are outside this change.
  • Package or release metadata impact: no dependency or release metadata changes.
  • Security, privacy, process, or containment review: normal navigation may execute destination application code and worker behavior before validation rejects capture. This is fail-closed observation, not a guarantee that no worker ran, a rollback mechanism, or a browser sandbox. Restoration removes future injection first and does not wait for load events; an execution context destroyed before restoration can be confirmed still produces cleanup uncertainty and a retained retry owner. The closure prevents the direct descriptor-enumeration bypass without turning the attached browser into a sandbox. Caller-owned browser state is never removed to satisfy block.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 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/2957 **Author:** [@dakjdakd](https://github.com/dakjdakd) **Created:** 10/13/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/browser-service-worker-policy` --- ### 📝 Commits (4) - [`edaf40f`](https://github.com/morluto/rea/commit/edaf40ff0b593d3acc09262539253eed8bf27680) fix(browser): enforce and release attached service-worker policy - [`68cc9f8`](https://github.com/morluto/rea/commit/68cc9f8214f951d496f539a3fc8aa63312cabd7e) fix(browser): release owned service-worker policy across navigation - [`0462f6c`](https://github.com/morluto/rea/commit/0462f6ca70f41b4a33126aedaab512cc4235036e) fix(browser): reject uninstalled service-worker policies - [`c909778`](https://github.com/morluto/rea/commit/c909778bd42bd64225e2dbb24a465fc63dc2e52d) fix(browser): cancel and bound service-worker policy setup ### 📊 Changes **7 files changed** (+1519 additions, -40 deletions) <details> <summary>View changed files</summary> 📝 `docs/browser-scenario-contract.md` (+7 -0) ➕ `scripts/lib/browser-scenario-service-worker-setup-e2e.mjs` (+260 -0) ➕ `scripts/lib/browser-scenario-service-workers-e2e.mjs` (+605 -0) 📝 `scripts/verify-real-browser.mjs` (+7 -0) ➕ `src/browser/PlaywrightScenarioServiceWorkers.ts` (+307 -0) 📝 `src/browser/PlaywrightScenarioSession.test.ts` (+300 -9) 📝 `src/browser/PlaywrightScenarioSession.ts` (+33 -31) </details> ### 📄 Description ## Summary An attached browser scenario could accept `service_workers: "block"` while collecting a page controlled by an existing worker. The admission check ran against the old document before navigation; starting from `about:blank` therefore bypassed registrations belonging to the actual destination. The same policy also left a nonconfigurable register replacement in the caller's live document after disconnect. This change validates the current frames before accepting their capture and gives the temporary registration wrapper an explicit owner. Worker-influenced captures are rejected, caller-owned registrations survive, and cleanup restores the property state the scenario replaced. Cleanup removes the owned future-document scripts before restoring live frames, and cancelled navigation is stopped even when the scenario has no storage seeds. Fixes #2956 ## Problem and expected behavior The smallest trigger is an external Chrome profile with a worker registered for a local origin, whose selected tab has moved to `about:blank`. A block scenario reconnects to that tab and navigates back to the origin. Previously, `scenario_start` and the following wait step completed even though `navigator.serviceWorker.controller` was non-null and a probe request returned a worker-generated response. The existing policy already treats caller-owned registrations as a reason to reject attachment rather than something REA may remove. That decision must apply to the documents actually observed, including destinations reached by later actions and child frames. A block request should not become silently permissive after the first navigation. Policy cleanup has the corresponding ownership invariant: a scenario may temporarily replace registration behavior, but must release its own replacement when it disconnects. The original descriptor, a deliberately absent property, and a subsequent caller replacement are distinct states. Cleanup must preserve that distinction rather than always assigning the browser's native method. ## Change and scope `PlaywrightScenarioServiceWorkers` owns the existing attached registration policy. It checks controller and registrations in every current frame before installation. Each wrapper keeps the original descriptor inside its closure; its public symbol marker contains only an ownership boolean. A restoration function requires a separate, randomly generated session token and does not return the original descriptor. Enumerating the wrapper's symbols therefore does not expose the native registration method. The wrapper remains configurable so its owner can restore it. The owner reuses the existing CDP endpoint and target-session helpers to attach to the caller-selected page. It installs a tracked `Page.addScriptToEvaluateOnNewDocument` script in that page and its out-of-process iframe targets. Iframe-only auto-attachment preserves pre-script registration blocking across renderer boundaries; workers and popups are excluded. Each owned script has its own protocol identifier. A frame is resumed after installation, including when installation fails, and repeated installation in the same realm recognizes the existing wrapper. The owner also verifies the actual registration wrapper in the current realm before accepting initialization or capture. A CDP script identifier alone is insufficient: if the page prevents property replacement, such as through a non-configurable descriptor, attached block observation is rejected instead of silently accepting an uninstalled policy. `PlaywrightScenarioSession` creates the owner only for attached block scenarios. It validates immediately after initial navigation and at the shared capture boundary used after each action. This catches ordinary goto, click navigation, and embedded-frame cases without distributing URL-specific guards through the action implementation. An explicit allow request does not create this owner. Launch continues using the existing Playwright-owned context policy. The capture check deliberately rejects an existing registration even if its controller is currently null. Registration existence and current control are separate browser facts; either is already incompatible with the attached block admission rule. No worker is unregistered to make the check pass. Cleanup restores a property only when its current function is still the wrapper saved in that realm's closure. If the caller replaced register afterward, that replacement wins. If the original property did not exist, restoration deletes the temporary property rather than inventing a descriptor. The ownership marker is used to detect unresolved restoration, not to store the original method. A wrong restoration token leaves the block in place. Policy setup has its own abort controller and uses the existing 10-second CDP discovery deadline across the initial read-only frame check, endpoint discovery, WebSocket handshake, and target attachment. Closing the owner aborts that setup lifetime before awaiting initialization. A peer that never sends the WebSocket upgrade response therefore cannot hold initialization open indefinitely, and a stalled frame preflight cannot start a late attachment after cleanup. That setup signal is deliberately not passed to `Page.addScriptToEvaluateOnNewDocument`: once a page mutation has been sent, the owner must receive its script identifier and remove that exact script before releasing ownership. Cleanup first closes admission for new policy installation and waits for already-sent installation requests to settle. For cancellation or failed navigation, this policy owner sends `Page.stopLoading` independently of storage seeding. Restoration does not wait for the page or a child frame to emit `load`, so an image or child response that never finishes cannot strand cleanup behind that event. The owner then removes every tracked future-document script before restoring any live frame. A main-frame navigation during child-frame restoration consequently creates a document without the wrapper. Caller-owned init scripts remain installed. Finally, cleanup detaches the owned target session and closes its socket before the scenario releases its transport and target lease. It uses the existing execution boundary and retains the owner when protocol or live-document restoration cannot be confirmed. Retry preserves completed cleanup stages rather than attempting to detach a session that was already released. Initialization-failure cleanup also awaits the primary browser owner using its independent cleanup lifetime, rather than the already-aborted scenario signal. Confirmed release then makes the same selected target available to a subsequent scenario. The seven files have specific responsibilities: - `src/browser/PlaywrightScenarioServiceWorkers.ts` owns bounded and cancellable setup, frame checks, private registration state, page/iframe script identifiers, stopped navigation, exact property restoration, and cleanup retry state. - `src/browser/PlaywrightScenarioSession.ts` connects that owner to initialization, initial navigation, per-step capture, and cleanup. - `src/browser/PlaywrightScenarioSession.test.ts` covers restoration failures, cancellation during pending installation or a stalled preflight, suppression of late setup, stopped navigation without storage seeds, installation-before-restoration ordering, and retry after protocol cleanup failure. - `scripts/lib/browser-scenario-service-workers-e2e.mjs` exercises the policy against actual Chromium, including caller ownership and future-document cleanup. - `scripts/lib/browser-scenario-service-worker-setup-e2e.mjs` uses a real loopback proxy to withhold policy discovery or a WebSocket upgrade, then checks connection release and reuse of the selected target. - `scripts/verify-real-browser.mjs` includes that verifier in the existing browser lane. - `docs/browser-scenario-contract.md` states the attached rejection rule and its navigation boundary. Contribution scope follows the existing browser scenario workflow. A separate maintainer scope decision is not applicable: this is a correction to existing policy enforcement and cleanup, with the same selected target, navigation authority, and temporary registration injection. It adds no general script-execution operation or worker-removal capability. ## Contract and boundary impact - Semantic owner and earliest changed stage: the attached service-worker policy owner; initialization and post-navigation validation now describe the current documents, followed by capture validation and restoration. - Contribution scope fit and linked maintainer decision: ordinary provider bug fix within existing boundaries; a new-capability scope decision is not applicable. - CLI and MCP/tool-catalog contract: request schema, tool names, action vocabulary, and catalog are unchanged. Both adapters use the shared provider; incompatible attached block captures now return the existing `target_not_allowed` failure. - Provider, bridge, target-format, or platform compatibility: Chromium connect mode; launch and explicit allow retain their existing paths. No bridge or target-format changes. - Evidence, artifact, provenance, or reconstruction contract: existing partial observations and cleanup status remain available. No evidence identifiers, artifact schema, source identity, or reconstruction changes. - Target selection, authority, upstream behavior, and cleanup details: the caller-selected endpoint and target remain authoritative. Existing workers are preserved. The policy owner removes its exact page/iframe script identifiers before restoring current frames and releasing its own session/socket, followed by the scenario transport. Caller scripts and registrations remain intact. Unconfirmed restoration retains the owner for retry. - Generated metadata, package, or installation impact: no schema, package dependency, installation, authored skill, or generated catalog changes; build outputs remain uncommitted. Expected effects reflect the existing workflow and its verification: - [x] Read existing files or captured data. - [x] Launch or attach to a process. - [x] Inject instrumentation or run scripts/code. - [x] Change process memory, registers, files, or configuration. - [x] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. The checked mutation effect is the temporary JavaScript registration replacement already present in this workflow, plus the existing disposable profile behavior in verification. Ordinary browsing contacts the requested application's services. No protected-code recovery or new execution authority is introduced. ## Evidence and regression coverage - Tests added or updated: session regressions cover restoration failure retaining the connection, successful cleanup retry, cancellation during pending script installation, cancellation without storage seeds, and retry after protocol cleanup failure. The real-browser verifier covers fresh registration blocking, initial and action navigation, cross-origin frames, allow, live-document restoration, reload, failure/cancellation cleanup, caller replacement, and original property absence. Additional real-Chrome scenarios cover stalled image and child-frame cancellation, target reuse afterward, private original descriptors, wrong restoration tokens, iframe transitions between processes, navigation between frame restorations, and preservation of caller-owned init scripts. Real Chrome also verifies rejection when the main page or an existing out-of-process iframe prevents registration replacement, when the original property is an accessor, and when a new iframe replaces the policy after injection; cleanup preserves the original descriptor or caller replacement. A real loopback proxy forwards the scenario transport to Chrome while withholding only policy discovery or the policy socket's HTTP 101 response. The production provider passes cancellation for both stalled discovery and handshake, confirms cleanup, and reacquires the same target afterward. A separate run without caller cancellation reaches the existing 10-second deadline with reason `timeout`, closes the withheld socket, and successfully reuses the target. The direct production policy owner additionally passes a stalled browser-WebSocket handshake case; this is owner-level coverage and adds no endpoint variant to the public scenario schema. - Base reproduction or other evidence: executed against the exact Session source from `ebaaa79ca02bde4a73013a2a9a781d0b07a57ee4`, with unchanged supporting production modules and a real local worker. Block returned completed steps while the controller remained active; the wrapper remained nonconfigurable after disconnect. This establishes the affected Session behavior rather than claiming a complete baseline qualification run. - User-visible CLI/MCP output: the fresh attached block CLI scenario still completes with `browser.cleanup: "disconnected-external"`. For the worker-controlled destination, the production provider now returns `BrowserObservationError` with reason `target_not_allowed`, retaining cleanup and partial-observation semantics instead of returning completed capture steps. Shared-provider behavior applies beneath CLI/MCP; direct MCP execution is not claimed for this new regression. - Remaining proof gaps: the real-provider coverage is the focused Windows x64/Chrome service-worker workflow described here. Complete browser-lane qualification, other operating systems, and other Chrome releases remain outside that coverage. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed The final focused checks used Node.js 24.18.0 and Chrome 154.0.8037.98 on Windows x64: - `npm run test:focused -- src/browser/PlaywrightScenarioSession.test.ts src/browser/PlaywrightScenarioBrowser.test.ts src/browser/PlaywrightBrowserScenarioProvider.test.ts` — 41 tests passed. - `npm run docs:check` — passed, including 5 README consistency tests. - `npm run typecheck`, `npm run lint`, and `npm run knip` — passed. - `npm run check` — passed on upstream base `8fef2aa64ec1ee742e8b70b825938d30a0ea7319` with this independent fix applied. - `npm run verify:test-discovery` and `npm run verify:architecture-guards` — passed. - Changed-file `oxfmt --check` and `git diff --check` — passed. The executed focused real-browser harness invoked the new service-worker verifier module with the built CLI and production provider. It passed the observed policy and ownership cases listed above using self-owned loopback fixtures and an isolated disposable Chrome profile. Caller registrations and the external target survived scenario cleanup. The complete `verify:browser` lane is not reported as passing qualification. ## Compatibility, safety, and release - Breaking changes or migration steps: no request-shape migration. A previously accepted worker-influenced block capture now rejects; callers intentionally collecting worker-driven behavior can use the existing allow setting. - Real Hopper/Ghidra, browser, or OS coverage: actual Chrome 154.0.8037.98 on Windows x64; native analysis engines are outside this change. - Package or release metadata impact: no dependency or release metadata changes. - Security, privacy, process, or containment review: normal navigation may execute destination application code and worker behavior before validation rejects capture. This is fail-closed observation, not a guarantee that no worker ran, a rollback mechanism, or a browser sandbox. Restoration removes future injection first and does not wait for load events; an execution context destroyed before restoration can be confirmed still produces cleanup uncertainty and a retained retry owner. The closure prevents the direct descriptor-enumeration bypass without turning the attached browser into a sandbox. Caller-owned browser state is never removed to satisfy block. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:07:15 +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#5516
No description provided.