zeroclaw-labs/zeroclaw · error

Host '{host}' is not in browser.allowed_domains

Error message

Host '{host}' is not in browser.allowed_domains

What it means

For public (non-private) hosts, browser_open checks the URL host against [browser].allowed_domains, which supports exact and wildcard patterns after normalization. A host matching no pattern is rejected — the allowlist is the only route in for public hosts.

Source

Thrown at crates/zeroclaw-tools/src/browser_open.rs:78

                "Browser tool is enabled but no allowed_domains are configured. Add [browser].allowed_domains in config.toml"
            );
        }

        let host = extract_host(url)?;
        let private_host = domain_guard::is_private_or_local_host(&host);
        let private_host_allowed = private_host
            && domain_guard::host_matches_allowlist(&host, &self.allowed_private_hosts);

        if private_host && !private_host_allowed {
            anyhow::bail!("Blocked local/private host: {host}");
        }

        if private_host_allowed {
            return Ok(url.to_string());
        }

        if !domain_guard::host_matches_allowlist(&host, &self.allowed_domains) {
            anyhow::bail!("Host '{host}' is not in browser.allowed_domains");
        }

        Ok(url.to_string())
    }
}

#[async_trait]
impl Tool for BrowserOpenTool {
    fn name(&self) -> &str {
        "browser_open"
    }

    fn description(&self) -> &str {
        "Open an approved HTTP/HTTPS URL in the system browser. Security constraints: allowlist-only domains, no local/private hosts, no scraping."
    }

    fn parameters_schema(&self) -> serde_json::Value {
        json!({

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Add the host (exact or wildcard) to [browser].allowed_domains in config
  2. Check whether the pattern covers subdomains — docs.example.com needs an entry for example.com or a *.example.com pattern
  3. Keep the allowlist aligned with the sites the agent is actually authorized to open

Example fix

# config.toml — before
[browser]
allowed_domains = ["example.com"]

# after
[browser]
allowed_domains = ["example.com", "sso.provider.com"]
Defensive patterns

Strategy: validation

Validate before calling

let host_matches = |h: &str, p: &str| {
    let (h, p) = (h.to_lowercase(), p.to_lowercase());
    h == p || h.ends_with(&format!(".{p}")) || wildcard_covers(&h, &p)
};
if !allowed_domains.iter().any(|p| host_matches(&host, p)) {
    return Err(format!("host '{host}' is not in browser.allowed_domains"));
}

Try / catch

match open_tool.execute(args).await {
    Ok(res) if res.success => { /* ... */ }
    Ok(res) => {
        if res.error.as_deref().unwrap_or_default().contains("not in browser.allowed_domains") {
            // add the host (exact or wildcard) to config, or drop the URL
        }
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: url="https://other.com" while allowed_domains=["example.com"]; subdomains pass only when the configured pattern covers them (exact entry or a wildcard such as *.example.com).

Common situations: Agents following links beyond the originally scoped site; new CDN or auth domains (sso.provider.com) appearing during login flows; wildcard patterns not covering a different TLD.

Related errors


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