Skip to main content

Domain Allowlist

The domain allowlist restricts browser navigation and network requests to a set of trusted domains, preventing AI agents from navigating to unexpected sites or exfiltrating data.

Quick Start

# Restrict to a single domain
agent-browser --allowed-domains "example.com" open example.com

# Allow multiple domains (comma-separated)
agent-browser --allowed-domains "example.com,api.example.com" open example.com

# Use wildcards for subdomains
agent-browser --allowed-domains "*.example.com" open app.example.com

# Via environment variable
export AGENT_BROWSER_ALLOWED_DOMAINS="example.com,*.cdn.example.com"
agent-browser open example.com

How It Works

When enabled, the domain allowlist blocks:
  1. Document navigation - Prevents navigating to non-allowed domains
  2. Sub-resource requests - Blocks scripts, images, stylesheets, fetch/XHR to non-allowed domains
  3. WebSocket connections - Prevents WebSocket connections to non-allowed domains
  4. EventSource connections - Blocks Server-Sent Events to non-allowed domains
  5. navigator.sendBeacon - Prevents beacon requests to non-allowed domains
Blocked requests are aborted with blockedbyclient error.

Domain Pattern Syntax

Exact Match

# Only allows example.com (not subdomains)
agent-browser --allowed-domains "example.com" open example.com
Allows:
  • https://example.com
  • http://example.com
Blocks:
  • https://www.example.com
  • https://api.example.com
  • https://other.com

Wildcard Subdomains

# Allows all subdomains of example.com
agent-browser --allowed-domains "*.example.com" open app.example.com
Allows:
  • https://app.example.com
  • https://api.example.com
  • https://cdn.example.com
  • https://example.com (bare domain also matches)
Blocks:
  • https://other.com
Wildcard patterns like *.example.com automatically match the bare domain example.com for convenience.

Multiple Domains

# Allow multiple domains (comma-separated)
agent-browser --allowed-domains "example.com,api.example.com,*.cdn.net" \
  open example.com
Allows:
  • https://example.com
  • https://api.example.com
  • https://images.cdn.net
  • https://assets.cdn.net
Blocks:
  • https://other.com

Special URLs

Data and Blob URLs

Data URIs and blob URLs are handled specially:
  • Sub-resources: Allowed (images, scripts from data: or blob: URLs)
  • Navigation: Blocked (cannot navigate to data: or blob: URLs)
This prevents data exfiltration via data: URL navigation while allowing legitimate use of data URIs for images, etc.

About: and Chrome: URLs

Internal browser URLs (about:, chrome:, chrome-extension:) are blocked.

Implementation Details

Route-based Filtering

Domain filtering is implemented via Playwright’s route interception at the browser context level:
export async function installDomainFilter(
  context: BrowserContext,
  allowedDomains: string[]
): Promise<void> {
  if (allowedDomains.length === 0) return;

  await context.route('**/*', async (route: Route) => {
    const request = route.request();
    const urlStr = request.url();

    // Check if URL is http(s)
    if (!urlStr.startsWith('http://') && !urlStr.startsWith('https://')) {
      // Block non-http(s) navigation, allow other resource types
      if (request.resourceType() === 'document') {
        await route.abort('blockedbyclient');
      } else {
        await route.continue();
      }
      return;
    }

    // Check domain allowlist
    let hostname: string;
    try {
      hostname = new URL(urlStr).hostname.toLowerCase();
    } catch {
      await route.abort('blockedbyclient');
      return;
    }

    if (isDomainAllowed(hostname, allowedDomains)) {
      await route.continue();
    } else {
      await route.abort('blockedbyclient');
    }
  });
}
See domain-filter.ts:src/domain-filter.ts:121 for full implementation.

JavaScript Monkey-patching

WebSocket, EventSource, and navigator.sendBeacon are patched via an init script to enforce domain allowlist at the JavaScript API level:
export function buildWebSocketFilterScript(allowedDomains: string[]): string {
  const serialized = JSON.stringify(allowedDomains);
  return `(function() {
    var _allowedDomains = ${serialized};
    function _checkUrl(url) {
      try {
        var parsed = new URL(url);
        return _isDomainAllowed(parsed.hostname);
      } catch(e) {
        return false;
      }
    }
    if (typeof WebSocket !== 'undefined') {
      var _OrigWS = WebSocket;
      WebSocket = function(url, protocols) {
        if (!_checkUrl(url)) {
          throw new DOMException(
            'WebSocket connection to ' + url + ' blocked by domain allowlist',
            'SecurityError'
          );
        }
        return new _OrigWS(url, protocols);
      };
      // ... EventSource and sendBeacon patches
    }
  })();
}
See domain-filter.ts:src/domain-filter.ts:33 for full implementation.

Security Note

This JavaScript monkey-patching is a best-effort defense. If page scripts can use eval(), they could theoretically restore the original WebSocket/EventSource implementations. To close this loophole, deny the eval action category in your action policy:
{
  "default": "allow",
  "deny": ["eval"]
}

Common Patterns

Single Domain Application

# Only allow the main application domain
export AGENT_BROWSER_ALLOWED_DOMAINS="myapp.com,*.myapp.com"
agent-browser open https://myapp.com

Multi-Domain Application with CDN

# Application + CDN + analytics
export AGENT_BROWSER_ALLOWED_DOMAINS="myapp.com,*.myapp.com,cdn.example.net,*.analytics.com"
agent-browser open https://myapp.com

Development Environment

# Allow localhost and staging
export AGENT_BROWSER_ALLOWED_DOMAINS="localhost,127.0.0.1,*.staging.myapp.com"
agent-browser open http://localhost:3000

Best Practices

1. Include CDN Domains

Many applications load resources from CDNs. Include CDN domains in the allowlist:
# Include CDN domains
agent-browser --allowed-domains "myapp.com,*.cloudfront.net,*.cdn.example.com" \
  open https://myapp.com
Without CDN domains, the page may fail to load resources (images, scripts, stylesheets).

2. Use Wildcards for Subdomains

Instead of listing all subdomains:
# Good: use wildcard
agent-browser --allowed-domains "*.myapp.com"

# Bad: list all subdomains
agent-browser --allowed-domains "api.myapp.com,cdn.myapp.com,app.myapp.com"

3. Test Before Deploying

Test the domain allowlist in headed mode to identify missing domains:
# Run in headed mode to see blocked requests
agent-browser --headed \
  --allowed-domains "myapp.com" \
  open https://myapp.com

# Check network requests to identify blocked resources
agent-browser network requests

4. Combine with Action Policy

For maximum security, combine domain allowlist with action policy:
# Restrict both domains and actions
agent-browser \
  --allowed-domains "myapp.com,*.myapp.com" \
  --action-policy ./policy.json \
  open https://myapp.com
With policy.json:
{
  "default": "allow",
  "deny": ["eval", "download"]
}

Configuration File

Set domain allowlist in agent-browser.json:
{
  "allowedDomains": "myapp.com,*.myapp.com,cdn.example.net"
}
Priority order:
  1. CLI flag --allowed-domains
  2. Environment variable AGENT_BROWSER_ALLOWED_DOMAINS
  3. Config file allowedDomains

Environment Variables

VariableDescriptionExample
AGENT_BROWSER_ALLOWED_DOMAINSComma-separated list of allowed domain patternsexample.com,*.cdn.example.com

Programmatic API

Install domain filter programmatically:
import { BrowserManager } from 'agent-browser';
import { installDomainFilter, parseDomainList } from 'agent-browser/domain-filter';

const browser = new BrowserManager();
await browser.launch();

const context = await browser.context();
const allowedDomains = parseDomainList('example.com,*.cdn.example.com');

await installDomainFilter(context, allowedDomains);

await browser.navigate('https://example.com');

Helper Functions

import { isDomainAllowed, parseDomainList } from 'agent-browser/domain-filter';

// Parse comma-separated domain list
const domains = parseDomainList('example.com, *.cdn.net');
console.log(domains);
// ['example.com', '*.cdn.net']

// Check if hostname matches allowlist
const allowed = isDomainAllowed('app.example.com', ['*.example.com']);
console.log(allowed); // true

const blocked = isDomainAllowed('other.com', ['*.example.com']);
console.log(blocked); // false

Troubleshooting

Page fails to load resources

Symptom: Page loads but images, styles, or scripts are missing. Solution: Check network requests to identify blocked domains:
# Run with domain allowlist
agent-browser --allowed-domains "myapp.com" open https://myapp.com

# Check blocked requests
agent-browser network requests
Add missing CDN domains to allowlist:
agent-browser --allowed-domains "myapp.com,*.cdn.example.com" \
  open https://myapp.com

WebSocket connection blocked

Symptom: Error in console: WebSocket connection to wss://... blocked by domain allowlist Solution: Add the WebSocket server domain to allowlist:
agent-browser --allowed-domains "myapp.com,ws.myapp.com" \
  open https://myapp.com
Symptom: Cannot navigate to a new page after initial load. Solution: The target domain is not in the allowlist. Add it:
# Allow navigation to both domains
agent-browser --allowed-domains "myapp.com,docs.myapp.com" \
  open https://myapp.com

# Or use wildcard
agent-browser --allowed-domains "*.myapp.com" \
  open https://myapp.com

See Also

Build docs developers (and LLMs) love