Skip to content

[fix]: close popups that violate domain policy - #2294

Merged
seanmcguire12 merged 8 commits into
mainfrom
seanmcguire/stg-2441-handle-windowopen-events-for-domain-policy
Jun 30, 2026
Merged

[fix]: close popups that violate domain policy#2294
seanmcguire12 merged 8 commits into
mainfrom
seanmcguire/stg-2441-handle-windowopen-events-for-domain-policy

Conversation

@seanmcguire12

@seanmcguire12 seanmcguire12 commented Jun 30, 2026

Copy link
Copy Markdown
Member

why

domain policy enforcement currently relies on Fetch.requestPaused, but popups opened with window.open() can reach their destination before Fetch interception is installed on the new target. in that race, the blocked popup may successfully open, & therefore break the domain policy

what changed

this PR adds a fallback close path for popup targets whose URL violates the active domain policy:

  • this PR adds listening for Target.targetCreated, Target.targetInfoChanged, and attach-time target metadata for popup targets
  • if a popup reaches a blocked/disallowed domain before request interception catches it, the popup gets closed via Target.closeTarget`

test plan

  • added unit coverage for closing popup targets that already reached a blocked domain, including popups whose opener target is not locally tracked
  • added unit coverage for duplicate and late target events so a successfully closed popup is not closed/logged repeatedly
  • added unit coverage for the attach race where a targetCreated close is still in flight when attached handling runs; attach now continues if the close fails
  • added unit coverage for close failures, including treating No target with given id found as a successful already-closed outcome for this domain-policy fallback only
  • added integration coverage using the existing external popup fixture to verify a window.open() popup that reaches news.ycombinator.com is closed and not retained in context.pages()

Summary by cubic

Fixes a race where window.open() popups could reach blocked domains before interception by closing them immediately when their URL violates the active domain policy. Prevents blocked popups from appearing or lingering in context.pages().

  • Bug Fixes

    • Listen to Target.targetCreated, Target.targetInfoChanged, and at attach-time; close popup via Target.closeTarget if its URL is disallowed.
    • Deduplicate close attempts across events and let attach wait for an in-flight close; continue attach if the close fails.
    • Treat “No target with given id found” as a successful already-closed outcome for this fallback; improve logging with rule reason and source.
    • Skip non-popup targets and persist successful close dedupe across late events.
  • Dependencies

    • Add changeset to publish a patch for @browserbasehq/stagehand.

Written for commit a0dae3b. Summary will update on new commits.

Review in cubic

@changeset-bot

changeset-bot Bot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a0dae3b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@browserbasehq/stagehand Patch
@browserbasehq/stagehand-evals Patch
@browserbasehq/stagehand-server-v3 Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@seanmcguire12

Copy link
Copy Markdown
Member Author

@cubic-dev-ai

@cubic-dev-ai

cubic-dev-ai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai

@seanmcguire12 I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files

Confidence score: 4/5

  • In packages/core/tests/integration/context-domain-policy.spec.ts, waitForTargetUrlDestroyed may miss popups opened directly at a blocked URL because it doesn’t subscribe to Target.targetCreated, which can leave targetId null and cause timeout-based failures; this is mainly a test reliability and coverage risk rather than a runtime break. Add handling for Target.targetCreated (alongside targetInfoChanged) before merging to de-flake this path and ensure the blocked-popup scenario is actually validated.
Architecture diagram
sequenceDiagram
    participant Browser as Browser / Page
    participant CDP as Chrome DevTools Protocol
    participant V3C as V3Context
    participant Policy as Domain Policy
    participant Logger

    Note over Browser,Logger: NEW: Popup domain policy fallback close

    Browser->>CDP: window.open(url)
    CDP-->>V3C: Target.targetCreated(targetInfo)
    V3C->>V3C: Check if popup (openerId/openerFrameId)
    alt Popup detected
        V3C->>Policy: getDomainPolicyDecision(url)
        alt Decision = "block"
            V3C->>V3C: Dedupe check (domainPolicyClosingTargets)
            Note over V3C: NEW: Launch close attempt
            V3C->>V3C: Store in-flight promise in domainPolicyClosePromises
            V3C->>CDP: Target.closeTarget(targetId)
            alt Close succeeds
                CDP-->>V3C: OK
                V3C->>V3C: Add to domainPolicyClosingTargets
                V3C->>Logger: Log popup closed
            else Close fails (not missing target)
                CDP-->>V3C: Error
                V3C->>V3C: Remove in-flight promise (allow retry)
                V3C->>Logger: Log failure
            else Close fails with "No target with given id found"
                CDP-->>V3C: Error
                V3C->>V3C: Treat as already closed (success)
                V3C->>V3C: Add to domainPolicyClosingTargets
            end
        else Decision = "continue"
            V3C-->>V3C: No action
        end
    end

    Note over Browser,V3C: Later events (if popup still alive)

    CDP-->>V3C: Target.targetInfoChanged(updatedInfo)
    V3C->>V3C: Check dedupe (domainPolicyClosingTargets)
    alt Not already closed
        V3C->>Policy: getDomainPolicyDecision(url)
        alt Decision = "block"
            V3C->>CDP: Target.closeTarget(targetId)
            Note over V3C: Same close flow as above
        end
    else Already closed
        V3C-->>V3C: Skip (dedup)
    end

    CDP-->>V3C: Target.attached(targetInfo, sessionId)
    V3C->>V3C: Check if in-flight close promise exists
    alt In-flight close exists
        V3C->>V3C: Await existing close promise
        alt Close succeeded
            V3C-->>V3C: Skip normal page setup (return early)
        else Close failed
            V3C->>V3C: Continue with normal attach
        end
    else No in-flight close
        V3C->>Policy: getDomainPolicyDecision(url)
        alt Decision = "block"
            V3C->>CDP: Target.closeTarget(targetId)
            Note over V3C: Same close flow, then return early
        else Decision = "continue"
            V3C->>V3C: Normal attached handling (page setup)
        end
    end
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/core/tests/integration/context-domain-policy.spec.ts
@seanmcguire12
seanmcguire12 merged commit 3938590 into main Jun 30, 2026
235 checks passed
seanmcguire12 pushed a commit that referenced this pull request Jul 13, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @browserbasehq/stagehand@3.7.0

### Minor Changes

- [#2283](#2283)
[`871ca7e`](871ca7e)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add
`context.setDomainPolicy({ allowedDomains: ["allowed.domain"] })` which
allows users to define a set of domains that are accessible to stagehand

- [#2274](#2274)
[`f31980f`](f31980f)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add
`context.setDomainPolicy({blockedDomains: ["some.domain"]})` which
allows users to define a list of domains that will be blocked by
stagehand

### Patch Changes

- [#2305](#2305)
[`cd1daad`](cd1daad)
Thanks [@shrey150](https://github.com/shrey150)! - Remove the noisy AI
SDK "system message in messages" warning logged on every hybrid/DOM
`agent.execute()` call.

- [#2328](#2328)
[`d287ff4`](d287ff4)
Thanks [@miguelg719](https://github.com/miguelg719)! - Allow modelName
"auto" in the constructor and per-primitive model overrides when running
through the Stagehand API

- [#2294](#2294)
[`3938590`](3938590)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! -
automatically close popups that violate user defined domain policy

- [#2298](#2298)
[`892701a`](892701a)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Fix CUA
`keypress` actions to press key combinations as a single chord.

- [#2345](#2345)
[`21826c7`](21826c7)
Thanks [@monadoid](https://github.com/monadoid)! - Repair malformed
UTF-16 snapshot text before it reaches model prompts.

- [#2306](#2306)
[`8dcef1b`](8dcef1b)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Use the
screenshot provider's declared media type when sending CUA image
payloads. The `setScreenshotProvider` callback now returns
`ScreenshotProviderResult` (`{ base64, mediaType }`) instead of a bare
base64 string.

- [#2273](#2273)
[`93a23d3`](93a23d3)
Thanks [@miguelg719](https://github.com/miguelg719)! - Add support for
the new `google/gemini-3.5-flash` computer-use tools model

- [#2278](#2278)
[`022d68f`](022d68f)
Thanks [@shrey150](https://github.com/shrey150)! - Fix `TypeError:
Converting circular structure to JSON` when creating an agent with MCP
`integrations` that include a `Client` instance (e.g. a local/stdio
server from `connectToMCPServer`). The agent-creation log serialized the
raw `integrations` array, and a live MCP `Client` is circular. It now
logs a safe descriptor (URL strings kept, client instances summarized)
so `agent({ integrations: [client] })` works.

- [#2288](#2288)
[`bb5ffa6`](bb5ffa6)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - clean up
cdp session event handlers on target detach

## @browserbasehq/stagehand-evals@2.0.4

### Patch Changes

- Updated dependencies
\[[`cd1daad`](cd1daad),
[`d287ff4`](d287ff4),
[`3938590`](3938590),
[`892701a`](892701a),
[`21826c7`](21826c7),
[`8dcef1b`](8dcef1b),
[`93a23d3`](93a23d3),
[`871ca7e`](871ca7e),
[`022d68f`](022d68f),
[`bb5ffa6`](bb5ffa6),
[`f31980f`](f31980f)]:
    -   @browserbasehq/stagehand@3.7.0

## @browserbasehq/stagehand-server-v3@3.7.2

### Patch Changes

- Updated dependencies
\[[`cd1daad`](cd1daad),
[`d287ff4`](d287ff4),
[`3938590`](3938590),
[`892701a`](892701a),
[`21826c7`](21826c7),
[`8dcef1b`](8dcef1b),
[`93a23d3`](93a23d3),
[`871ca7e`](871ca7e),
[`022d68f`](022d68f),
[`bb5ffa6`](bb5ffa6),
[`f31980f`](f31980f)]:
    -   @browserbasehq/stagehand@3.7.0

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
felipeofdev-ai pushed a commit to felipeofdev-ai/stagehand that referenced this pull request Aug 4, 2026
# why

domain policy enforcement currently relies on `Fetch.requestPaused`, but
popups opened with `window.open()` can reach their destination before
Fetch interception is installed on the new target. in that race, the
blocked popup may successfully open, & therefore break the domain policy

# what changed

this PR adds a fallback close path for popup targets whose URL violates
the active domain policy:

- this PR adds listening for `Target.targetCreated`,
`Target.targetInfoChanged`, and attach-time target metadata for popup
targets
- if a popup reaches a blocked/disallowed domain before request
interception catches it, the popup gets closed via Target.closeTarget`

# test plan

- added unit coverage for closing popup targets that already reached a
blocked domain, including popups whose opener target is not locally
tracked
- added unit coverage for duplicate and late target events so a
successfully closed popup is not closed/logged repeatedly
- added unit coverage for the attach race where a `targetCreated` close
is still in flight when `attached` handling runs; attach now continues
if the close fails
- added unit coverage for close failures, including treating `No target
with given id found` as a successful already-closed outcome for this
domain-policy fallback only
- added integration coverage using the existing external popup fixture
to verify a `window.open()` popup that reaches `news.ycombinator.com` is
closed and not retained in `context.pages()`

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes a race where `window.open()` popups could reach blocked domains
before interception by closing them immediately when their URL violates
the active domain policy. Prevents blocked popups from appearing or
lingering in `context.pages()`.

- **Bug Fixes**
- Listen to `Target.targetCreated`, `Target.targetInfoChanged`, and at
attach-time; close popup via `Target.closeTarget` if its URL is
disallowed.
- Deduplicate close attempts across events and let attach wait for an
in-flight close; continue attach if the close fails.
- Treat “No target with given id found” as a successful already-closed
outcome for this fallback; improve logging with rule reason and source.
- Skip non-popup targets and persist successful close dedupe across late
events.

- **Dependencies**
  - Add changeset to publish a patch for `@browserbasehq/stagehand`.

<sup>Written for commit a0dae3b.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/browserbase/stagehand/pull/2294?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
felipeofdev-ai pushed a commit to felipeofdev-ai/stagehand that referenced this pull request Aug 4, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @browserbasehq/stagehand@3.7.0

### Minor Changes

- [browserbase#2283](browserbase#2283)
[`871ca7e`](browserbase@871ca7e)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add
`context.setDomainPolicy({ allowedDomains: ["allowed.domain"] })` which
allows users to define a set of domains that are accessible to stagehand

- [browserbase#2274](browserbase#2274)
[`f31980f`](browserbase@f31980f)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - add
`context.setDomainPolicy({blockedDomains: ["some.domain"]})` which
allows users to define a list of domains that will be blocked by
stagehand

### Patch Changes

- [browserbase#2305](browserbase#2305)
[`cd1daad`](browserbase@cd1daad)
Thanks [@shrey150](https://github.com/shrey150)! - Remove the noisy AI
SDK "system message in messages" warning logged on every hybrid/DOM
`agent.execute()` call.

- [browserbase#2328](browserbase#2328)
[`d287ff4`](browserbase@d287ff4)
Thanks [@miguelg719](https://github.com/miguelg719)! - Allow modelName
"auto" in the constructor and per-primitive model overrides when running
through the Stagehand API

- [browserbase#2294](browserbase#2294)
[`3938590`](browserbase@3938590)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! -
automatically close popups that violate user defined domain policy

- [browserbase#2298](browserbase#2298)
[`892701a`](browserbase@892701a)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Fix CUA
`keypress` actions to press key combinations as a single chord.

- [browserbase#2345](browserbase#2345)
[`21826c7`](browserbase@21826c7)
Thanks [@monadoid](https://github.com/monadoid)! - Repair malformed
UTF-16 snapshot text before it reaches model prompts.

- [browserbase#2306](browserbase#2306)
[`8dcef1b`](browserbase@8dcef1b)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - Use the
screenshot provider's declared media type when sending CUA image
payloads. The `setScreenshotProvider` callback now returns
`ScreenshotProviderResult` (`{ base64, mediaType }`) instead of a bare
base64 string.

- [browserbase#2273](browserbase#2273)
[`93a23d3`](browserbase@93a23d3)
Thanks [@miguelg719](https://github.com/miguelg719)! - Add support for
the new `google/gemini-3.5-flash` computer-use tools model

- [browserbase#2278](browserbase#2278)
[`022d68f`](browserbase@022d68f)
Thanks [@shrey150](https://github.com/shrey150)! - Fix `TypeError:
Converting circular structure to JSON` when creating an agent with MCP
`integrations` that include a `Client` instance (e.g. a local/stdio
server from `connectToMCPServer`). The agent-creation log serialized the
raw `integrations` array, and a live MCP `Client` is circular. It now
logs a safe descriptor (URL strings kept, client instances summarized)
so `agent({ integrations: [client] })` works.

- [browserbase#2288](browserbase#2288)
[`bb5ffa6`](browserbase@bb5ffa6)
Thanks [@seanmcguire12](https://github.com/seanmcguire12)! - clean up
cdp session event handlers on target detach

## @browserbasehq/stagehand-evals@2.0.4

### Patch Changes

- Updated dependencies
\[[`cd1daad`](browserbase@cd1daad),
[`d287ff4`](browserbase@d287ff4),
[`3938590`](browserbase@3938590),
[`892701a`](browserbase@892701a),
[`21826c7`](browserbase@21826c7),
[`8dcef1b`](browserbase@8dcef1b),
[`93a23d3`](browserbase@93a23d3),
[`871ca7e`](browserbase@871ca7e),
[`022d68f`](browserbase@022d68f),
[`bb5ffa6`](browserbase@bb5ffa6),
[`f31980f`](browserbase@f31980f)]:
    -   @browserbasehq/stagehand@3.7.0

## @browserbasehq/stagehand-server-v3@3.7.2

### Patch Changes

- Updated dependencies
\[[`cd1daad`](browserbase@cd1daad),
[`d287ff4`](browserbase@d287ff4),
[`3938590`](browserbase@3938590),
[`892701a`](browserbase@892701a),
[`21826c7`](browserbase@21826c7),
[`8dcef1b`](browserbase@8dcef1b),
[`93a23d3`](browserbase@93a23d3),
[`871ca7e`](browserbase@871ca7e),
[`022d68f`](browserbase@022d68f),
[`bb5ffa6`](browserbase@bb5ffa6),
[`f31980f`](browserbase@f31980f)]:
    -   @browserbasehq/stagehand@3.7.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants