zeroclaw-labs/zeroclaw · error

domain '{}' is blocked by browser_delegate policy

Error message

domain '{}' is blocked by browser_delegate policy

What it means

browser_delegate checks the blocked_domains denylist first, and a match aborts immediately — deny always wins, even when the domain is also allowlisted or the allowlist is empty. This is a hard policy boundary: the subprocess never receives tasks referencing blocked domains.

Source

Thrown at crates/zeroclaw-tools/src/browser_delegate.rs:102

            anyhow::Error::msg(format!("invalid URL '{}': {}", url, e))
        })?;

        // Only allow http/https schemes
        let scheme = parsed.scheme();
        if scheme != "http" && scheme != "https" {
            anyhow::bail!("unsupported URL scheme: {}", scheme);
        }

        let domain = parsed.host_str().unwrap_or("").to_string();

        if domain.is_empty() {
            anyhow::bail!("URL has no host: {}", url);
        }

        // Check blocked domains first (deny takes precedence)
        for blocked in &self.config.blocked_domains {
            if domain_matches(&domain, blocked) {
                anyhow::bail!("domain '{}' is blocked by browser_delegate policy", domain);
            }
        }

        // If allowed_domains is non-empty, it acts as an allowlist
        if !self.config.allowed_domains.is_empty() {
            let allowed = self
                .config
                .allowed_domains
                .iter()
                .any(|d| domain_matches(&domain, d));
            if !allowed {
                anyhow::bail!(
                    "domain '{}' is not in browser_delegate allowed_domains",
                    domain
                );
            }
        }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. If access is legitimately required, remove or narrow the entry in browser_delegate blocked_domains configuration
  2. Otherwise drop the blocked URL from the task
  3. Remember precedence: adding the domain to allowed_domains will NOT unblock it

Example fix

# config.toml — before
[browser_delegate]
blocked_domains = ["example.com"]

# after (access granted deliberately)
blocked_domains = []
Defensive patterns

Strategy: try-catch

Try / catch

match delegate.execute(args).await {
    Ok(res) if res.success => { /* ... */ }
    Ok(res) => {
        if res.error.as_deref().unwrap_or_default().contains("blocked by browser_delegate policy") {
            // policy denial: drop the blocked URL; do not retry or try to bypass
        }
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: A task URL (or a URL embedded in task text) whose domain matches a blocked_domains entry. Matching is exact or parent-domain: docs.example.com matches an entry of example.com.

Common situations: Policy configs blocking webmail, social, or internal domains; agents embedding a blocked link in otherwise benign task prose; stale config still blocking a domain a team now needs.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/23a1af17669e6c6b. Report an issue: GitHub.