{"record":{"id":"739d752dd4c72d8c","repo":"zeroclaw-labs/zeroclaw","slug":"host-host-is-not-in-browser-allowed-domains","errorCode":null,"errorMessage":"Host '{host}' is not in browser.allowed_domains","messagePattern":"Host '(.+?)' is not in browser\\.allowed_domains","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/browser_open.rs","lineNumber":78,"sourceCode":"                \"Browser tool is enabled but no allowed_domains are configured. Add [browser].allowed_domains in config.toml\"\n            );\n        }\n\n        let host = extract_host(url)?;\n        let private_host = domain_guard::is_private_or_local_host(&host);\n        let private_host_allowed = private_host\n            && domain_guard::host_matches_allowlist(&host, &self.allowed_private_hosts);\n\n        if private_host && !private_host_allowed {\n            anyhow::bail!(\"Blocked local/private host: {host}\");\n        }\n\n        if private_host_allowed {\n            return Ok(url.to_string());\n        }\n\n        if !domain_guard::host_matches_allowlist(&host, &self.allowed_domains) {\n            anyhow::bail!(\"Host '{host}' is not in browser.allowed_domains\");\n        }\n\n        Ok(url.to_string())\n    }\n}\n\n#[async_trait]\nimpl Tool for BrowserOpenTool {\n    fn name(&self) -> &str {\n        \"browser_open\"\n    }\n\n    fn description(&self) -> &str {\n        \"Open an approved HTTP/HTTPS URL in the system browser. Security constraints: allowlist-only domains, no local/private hosts, no scraping.\"\n    }\n\n    fn parameters_schema(&self) -> serde_json::Value {\n        json!({","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/browser_open.rs#L60-L96","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Add the host (exact or wildcard) to [browser].allowed_domains in config","Check whether the pattern covers subdomains — docs.example.com needs an entry for example.com or a *.example.com pattern","Keep the allowlist aligned with the sites the agent is actually authorized to open"],"exampleFix":"# config.toml — before\n[browser]\nallowed_domains = [\"example.com\"]\n\n# after\n[browser]\nallowed_domains = [\"example.com\", \"sso.provider.com\"]","handlingStrategy":"validation","validationCode":"let host_matches = |h: &str, p: &str| {\n    let (h, p) = (h.to_lowercase(), p.to_lowercase());\n    h == p || h.ends_with(&format!(\".{p}\")) || wildcard_covers(&h, &p)\n};\nif !allowed_domains.iter().any(|p| host_matches(&host, p)) {\n    return Err(format!(\"host '{host}' is not in browser.allowed_domains\"));\n}","typeGuard":null,"tryCatchPattern":"match open_tool.execute(args).await {\n    Ok(res) if res.success => { /* ... */ }\n    Ok(res) => {\n        if res.error.as_deref().unwrap_or_default().contains(\"not in browser.allowed_domains\") {\n            // add the host (exact or wildcard) to config, or drop the URL\n        }\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Keep allowed_domains aligned with sites the agent is authorized to open","Add auth/CDN domains (e.g. sso.provider.com) discovered during login flows","Review the allowlist whenever onboarding new integrations"],"tags":["browser-open","allowlist","domain","policy","security"],"backgroundTag":"domain-not-in-allowlist","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}