Skip to content

[feat]: add domain blocklist#2274

Merged
seanmcguire12 merged 14 commits into
mainfrom
add-domain-blocklist
Jun 26, 2026
Merged

[feat]: add domain blocklist#2274
seanmcguire12 merged 14 commits into
mainfrom
add-domain-blocklist

Conversation

@seanmcguire12

@seanmcguire12 seanmcguire12 commented Jun 25, 2026

Copy link
Copy Markdown
Member

why

users need a way to block known domains during browser sessions

what changed

  • users can now call stagehand.context.setDomainPolicy({ blockedDomains }) to block requests to specific domains across the whole context
  • stagehand turns those domains into cdp Fetch request patterns, so only matching blocklist requests are paused instead of intercepting all traffic
  • blocked requests are failed with BlockedByClient, which surfaces in chrome as a client-side network block
  • the policy is applied to already-open pages & automatically applied to new pages, popups, & attached frame targets
  • users can clear the policy with setDomainPolicy(null) or { blockedDomains: [] }; clearing disables policy interception & removes stagehand's request listener
  • invalid domain inputs like full urls, paths, ports, queries, or malformed wildcards throw an StagehandInvalidArgumentError

fast follow:

  • will follow up with a PR to add a domain allowlist, eg stagehand.context.setDomainPolicy({ allowedDomains })

behavioural notes:

  • setDomainPolicy({ blockedDomains: [...] }) applies to active context sessions & future pages/targets
  • exact domains like ads.example.com block only that hostname
  • wildcard domains like *.example.com block subdomains, but not the apex domain
  • when no policy is set, stagehand does not enable Fetch interception for this feature
  • clearing with setDomainPolicy(null) or { blockedDomains: [] } disables the policy & removes stagehand's Fetch.requestPaused listener

test plan

  • packages/core/tests/unit/domain-policy.test.ts validates domain normalization, exact/wildcard matching, invalid domain rejection & generated Fetch.RequestPattern values
  • packages/core/tests/unit/context-domain-policy.test.ts validates context.setDomainPolicy() enables/disables Fetch, removes only its own Fetch.requestPaused listener on clear, fails blocked requests & continues unexpected non-blocked paused requests
  • packages/core/tests/integration/context-domain-policy.spec.ts validates blocked requests fail on an existing page & on a page created after the policy is set
  • packages/core/tests/unit/public-api/public-error-types.test.ts validates StagehandSetDomainPolicyError is exported as a public error type

Summary by cubic

Adds a context-wide domain blocklist that intercepts and blocks outgoing HTTP(S) requests by domain using CDP Fetch. Includes a small API, strict validation, and clearer error messages.

  • New Features
    • API: context.setDomainPolicy(policy | null) and context.getDomainPolicy().
    • Patterns: exact hosts and leading wildcards only (example.com, *.example.com); HTTP/HTTPS on any port; case-insensitive; trailing dots handled.
    • Scope: applies to existing and new pages/targets; clearing with null or [] disables and removes our handler on success.
    • Validation: domain-only strings; invalid inputs throw StagehandInvalidArgumentError.
    • Behavior: non-matches continue; matches fail with BlockedByClient.
    • Errors: if Fetch.enable fails, we uninstall the handler for that session, close new targets, and newPage() fails fast with StagehandSetDomainPolicyError that includes per-session details and CDP error text; if Fetch.disable fails, the handler stays installed and the same error is thrown.

Written for commit 54c22df. Summary will update on new commits.

Review in cubic

@changeset-bot

changeset-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 54c22df

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 Minor
@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 seanmcguire12 changed the title [WIP]: add domain blocklist [feat]: add domain blocklist Jun 25, 2026
@seanmcguire12 seanmcguire12 marked this pull request as ready for review June 25, 2026 22:58

@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.

6 issues found across 9 files

Confidence score: 2/5

  • In packages/core/lib/v3/understudy/domainPolicy.ts, hostname matching does not canonicalize trailing-dot hosts, so example.com. can evade block rules and weaken the core policy guarantee — normalize parsed hostnames (including trailing-dot handling) before blocklist comparison.
  • In packages/core/lib/v3/understudy/context.ts, attach flow swallows Fetch.enable failures, which can leave newly attached targets unprotected even after setDomainPolicy reports success — treat this as a hard failure (or retry/propagate) before merging so enforcement is consistent across sessions.
  • In packages/core/lib/v3/types/public/context.ts, the new public setDomainPolicy path is missing required flowLogger instrumentation, reducing traceability for a security-sensitive control and making incidents harder to audit — add the standard logging instrumentation for this API before merge.
  • In packages/core/lib/v3/understudy/context.ts, detach cleanup does not remove domain-policy listeners, so stale Fetch.requestPaused handlers can accumulate and cause memory/perf drift over time; packages/core/lib/v3/understudy/domainPolicy.ts also throws raw TypeError on non-string blockedDomains entries, and packages/core/tests/integration/context-domain-policy.spec.ts hides navigation failures — fix listener teardown, validate input types up front, and remove the silent test catch to de-risk rollout and debugging.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/types/public/context.ts">

<violation number="1" location="packages/core/lib/v3/types/public/context.ts:37">
P2: Custom agent: **Ensure all public methods added to the stagehand class, agent, or understudy (page, locator, etc.) interfaces are properly instrumented with the flowLogger**

New public `setDomainPolicy` method lacks flowLogger instrumentation</violation>
</file>

<file name="packages/core/lib/v3/understudy/context.ts">

<violation number="1" location="packages/core/lib/v3/understudy/context.ts:517">
P2: Domain-policy listener cleanup is ineffective on detach and leaks session event handlers. Detached sessions can accumulate stale `Fetch.requestPaused` closures in the CDP event handler registry.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Dev as Developer
    participant Context as V3Context
    participant DP as domainPolicy Module
    participant CDP as CDP Session (Chrome)
    participant Network

    Note over Dev,Network: Setting a Domain Blocklist

    Dev->>Context: setDomainPolicy({ blockedDomains: [...] })
    Context->>DP: normalizeDomainPolicy(policy)
    DP->>DP: Validate each domain pattern
    alt Invalid domain (URL, path, bad wildcard)
        DP-->>Context: throw StagehandInvalidArgumentError
        Context-->>Dev: Error thrown
    else Valid
        DP-->>Context: NormalizedDomainPolicy (rules + fetch patterns)
        Context->>Context: Store normalized policy
        Context->>CDP: Fetch.enable(patterns)
        CDP-->>Context: Acknowledged
        Context->>Context: installDomainPolicyHandler() – register Fetch.requestPaused listener
        Context-->>Dev: Success
    end

    Note over Dev,Network: Request from a Page or New Page/Target

    Developer->>Context: awaitActivePage() or newPage()
    Context->>CDP: (pre-resume) Fetch.enable(patterns) + install listener
    CDP-->>Context: Acknowledged
    Context-->>Dev: Page ready

    Note over Network: A page makes a network request

    CDP->>Context: Fetch.requestPaused(event)
    Context->>Context: handleDomainPolicyRequestPaused()
    Context->>DP: shouldBlockUrl(request.url, policy)
    alt URL host matches a blocked rule
        DP-->>Context: true
        Context->>CDP: Fetch.failRequest(BlockedByClient)
        CDP-->>Network: Request blocked (ERR_BLOCKED_BY_CLIENT)
    else URL does not match
        DP-->>Context: false
        Context->>CDP: Fetch.continueRequest()
        CDP-->>Network: Request continues normally
    end

    Note over Dev,Network: Clearing the Policy

    Dev->>Context: setDomainPolicy(null) or { blockedDomains: [] }
    Context->>DP: normalizeDomainPolicy() → null
    Context->>Context: Clear stored policy
    Context->>CDP: Fetch.disable()
    Context->>Context: uninstallDomainPolicyHandler() – remove listener
    CDP-->>Context: Acknowledged
    Context-->>Dev: Success (no policy active)
Loading

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread packages/core/lib/v3/understudy/domainPolicy.ts Outdated
Comment thread packages/core/lib/v3/understudy/context.ts Outdated
Comment thread packages/core/lib/v3/understudy/domainPolicy.ts
Comment thread packages/core/lib/v3/understudy/context.ts
Comment thread packages/core/lib/v3/types/public/context.ts
Comment thread packages/core/tests/integration/context-domain-policy.spec.ts Outdated

@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 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/types/public/context.ts">

<violation number="1" location="packages/core/lib/v3/types/public/context.ts:37">
P2: Custom agent: **Ensure all public methods added to the stagehand class, agent, or understudy (page, locator, etc.) interfaces are properly instrumented with the flowLogger**

New public `setDomainPolicy` method lacks flowLogger instrumentation</violation>
</file>

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

Fix all with cubic | Re-trigger cubic

Comment thread packages/core/lib/v3/understudy/context.ts
@seanmcguire12

Copy link
Copy Markdown
Member Author

@cubic-dev-ai

@cubic-dev-ai

cubic-dev-ai Bot commented Jun 26, 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.

4 issues found across 9 files

Confidence score: 2/5

  • In packages/core/lib/v3/understudy/context.ts, uninstalling the Fetch.pause handler even when Fetch.disable fails can leave paused requests with no handler, creating a real deadlock risk for network traffic if merged as-is — keep the pause handler installed on disable failure and add a failure-path unit/integration check before merging.
  • In packages/core/lib/v3/understudy/context.ts, the new public method setDomainPolicy appears to be missing required flowLogger instrumentation, which can break tracing/telemetry consistency for stagehand/agent/understudy public APIs — instrument this method to match existing public interface logging conventions before merge.
  • packages/core/tests/unit/domain-policy.test.ts is missing explicit coverage for deep wildcard subdomain matching and case-insensitive domains, so regressions in *.tracking.example.com behavior or normalization could slip through unnoticed — add focused tests for deep subdomains and mixed-case hostnames to de-risk future changes.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/understudy/context.ts">

<violation number="1" location="packages/core/lib/v3/understudy/context.ts:443">
P1: Custom agent: **Ensure all public methods added to the stagehand class, agent, or understudy (page, locator, etc.) interfaces are properly instrumented with the flowLogger**

New public method `setDomainPolicy` is not instrumented with flowLogger</violation>
</file>
Architecture diagram
sequenceDiagram
    participant User as User Code
    participant Context as V3Context
    participant DomainPolicy as domainPolicy.ts
    participant Session as CDPSession (per target)
    participant Browser as Browser (Chrome)

    Note over User,Browser: Setting Domain Policy (Initial Call)

    User->>Context: setDomainPolicy({ blockedDomains: ["ads.example.com"] })
    Context->>DomainPolicy: normalizeDomainPolicy(policy)
    DomainPolicy->>DomainPolicy: Validate domains, reject URLs/wildcards
    alt Invalid input
        DomainPolicy-->>Context: throw StagehandInvalidArgumentError
        Context-->>User: Error
    end
    DomainPolicy-->>Context: NormalizedDomainPolicy (rules + fetchPatterns)

    Context->>Context: Store policy (this.domainPolicy = nextPolicy)

    loop For each active session
        Context->>Session: Fetch.enable({ patterns: [...] })
        alt Success
            Context->>Session: installDomainPolicyHandler (Fetch.requestPaused)
            Session-->>Context: OK
        else Failure (e.g., CDP error)
            Context->>Session: uninstallDomainPolicyHandler
            Context-->>User: throw StagehandSetDomainPolicyError(failures)
        end
    end

    Context-->>User: void (success)

    Note over User,Browser: Request Blocking (Runtime)

    Browser->>Browser: HTTP(S) request initiated
    Browser->>Session: Fetch.requestPaused({ requestId, request: { url } })
    Session->>Context: Handler invoked
    Context->>Context: shouldBlockUrl(url, domainPolicy)
    alt URL matches a blocked domain
        Context->>Browser: Fetch.failRequest(requestId, errorReason="BlockedByClient")
        Browser->>Browser: ERR_BLOCKED_BY_CLIENT (asset fails)
    else URL does not match
        Context->>Browser: Fetch.continueRequest(requestId)
        Browser->>Browser: Request proceeds normally
    end

    Note over User,Browser: New Target / Page Created After Policy Set

    User->>Context: awaitActivePage() / newPage()
    Context->>Session: Target attached
    Context->>Context: onAttachedToTarget()
    Context->>Context: queuePreResume("Fetch.enable", { patterns })
    alt Fetch.enable succeeds (pre-resume step)
        Context->>Session: installDomainPolicyHandler (Fetch.requestPaused)
        Context-->>User: Return Page (with blocking active)
    else Fetch.enable fails
        Context->>Session: uninstallDomainPolicyHandler
        Context->>Browser: Target.closeTarget(targetId)
        Context->>Context: Store StagehandSetDomainPolicyError in pageCreationFailures
        Context-->>User: newPage() throws StagehandSetDomainPolicyError
    end

    Note over User,Browser: Clearing Policy

    User->>Context: setDomainPolicy(null) or { blockedDomains: [] }
    Context->>DomainPolicy: normalizeDomainPolicy(null/empty) returns null
    Context->>Context: Clear stored policy

    loop For each active session
        Context->>Session: Fetch.disable()
        Context->>Session: uninstallDomainPolicyHandler (removes only its listener)
    end

    Context-->>User: void

    Note over User,Browser: Reading Current Policy

    User->>Context: getDomainPolicy()
    Context-->>User: DomainPolicy | null (returns copy of stored policy)
Loading

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

Fix all with cubic | Re-trigger cubic

Comment thread packages/core/lib/v3/understudy/context.ts Outdated
Comment thread packages/core/tests/unit/domain-policy.test.ts
Comment thread packages/core/tests/unit/domain-policy.test.ts
@browserbase browserbase deleted a comment from cubic-dev-ai Bot Jun 26, 2026

@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 9 files

Confidence score: 3/5

  • In packages/core/lib/v3/understudy/context.ts, the new public setDomainPolicy path appears to be missing flowLogger instrumentation, which can create tracing gaps for this API and make behavior/regressions harder to diagnose after release; wire this method into the same flowLogger pattern used by other public stagehand/agent/understudy methods before merging.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/core/lib/v3/understudy/context.ts">

<violation number="1" location="packages/core/lib/v3/understudy/context.ts:443">
P1: Custom agent: **Ensure all public methods added to the stagehand class, agent, or understudy (page, locator, etc.) interfaces are properly instrumented with the flowLogger**

New public method `setDomainPolicy` is not instrumented with flowLogger</violation>
</file>
Architecture diagram
sequenceDiagram
    participant User as User Code
    participant Ctx as V3Context
    participant DP as DomainPolicy
    participant Sess as CDPSession
    participant CDP as Chrome (Fetch Domain)

    Note over User,CDP: NEW: Domain Blocklist via CDP Fetch Interception

    User->>Ctx: setDomainPolicy({blockedDomains:[...]})
    Ctx->>DP: normalizeDomainPolicy(policy)
    alt invalid domain input
        DP-->>Ctx: throw StagehandInvalidArgumentError
        Ctx-->>User: throw
    else valid
        DP-->>Ctx: NormalizedDomainPolicy (rules, patterns)
        Ctx->>Ctx: store domainPolicy
        loop each active session
            Ctx->>Sess: installDomainPolicyHandler (attach listener)
            Ctx->>Sess: send Fetch.enable({patterns})
            Sess->>CDP: Fetch.enable(patterns)
            alt success
                CDP-->>Sess: ok
            else failure
                CDP-->>Sess: error
                Sess-->>Ctx: error
                Ctx->>Sess: uninstallDomainPolicyHandler (remove listener)
                Ctx->>Ctx: record failure
            end
        end
        alt any failures
            Ctx-->>User: throw StagehandSetDomainPolicyError(failures)
        else all success
            Ctx-->>User: void
        end
    end

    Note over User,CDP: When a matching request is paused (triggered by Fetch.enable)
    CDP-->>Sess: Fetch.requestPaused(event)
    Sess->>Ctx: handler
    Ctx->>Ctx: get domainPolicy
    alt shouldBlockUrl returns true
        Ctx->>Sess: send Fetch.failRequest(BlockedByClient)
        Sess->>CDP: Fetch.failRequest
    else false
        Ctx->>Sess: send Fetch.continueRequest
        Sess->>CDP: Fetch.continueRequest
    end

    Note over User,CDP: Clearing policy
    User->>Ctx: setDomainPolicy(null) or {blockedDomains:[]}
    Ctx->>Ctx: clear stored policy
    loop each active session
        Ctx->>Sess: send Fetch.disable
        Sess->>CDP: Fetch.disable
        alt success
            Ctx->>Sess: uninstallDomainPolicyHandler
        else failure
            Sess-->>Ctx: error
            Ctx->>Ctx: keep listener, record failure
        end
    end
    alt any failures
        Ctx-->>User: throw StagehandSetDomainPolicyError
    else all success
        Ctx-->>User: void
    end

    Note over User,CDP: NEW: Policy auto-applied to new pages/targets
    User->>Ctx: newPage() (or target attach)
    Ctx->>Ctx: onAttachedToTarget
    alt domainPolicy is set
        Ctx->>Sess: installDomainPolicyHandler
        Ctx->>Sess: send Fetch.enable({patterns})
        alt success
            Ctx->>Ctx: continue normal attach
        else failure
            Sess-->>Ctx: error
            Ctx->>Sess: uninstallDomainPolicyHandler
            Ctx->>Ctx: close target
            Ctx->>Ctx: record pageCreationFailure
            Ctx-->>User: newPage() will throw StagehandSetDomainPolicyError
        end
    else no policy
        Ctx->>Ctx: normal attach (unchanged)
    end
Loading

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

Fix all with cubic | Re-trigger cubic

Comment thread packages/core/lib/v3/understudy/context.ts
Comment thread packages/core/lib/v3/understudy/context.ts
Comment thread packages/core/lib/v3/understudy/context.ts Outdated
Comment thread packages/core/lib/v3/understudy/context.ts Outdated
@seanmcguire12 seanmcguire12 merged commit f31980f into main Jun 26, 2026
461 of 462 checks passed
@github-actions github-actions Bot mentioned this pull request Jun 27, 2026
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