{"record":{"id":"75329a756f1bbab6","repo":"zeroclaw-labs/zeroclaw","slug":"domain-is-not-in-browser-delegate-allowed-dom","errorCode":null,"errorMessage":"domain '{}' is not in browser_delegate allowed_domains","messagePattern":"domain '(.+?)' is not in browser_delegate allowed_domains","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/browser_delegate.rs","lineNumber":114,"sourceCode":"            anyhow::bail!(\"URL has no host: {}\", url);\n        }\n\n        // Check blocked domains first (deny takes precedence)\n        for blocked in &self.config.blocked_domains {\n            if domain_matches(&domain, blocked) {\n                anyhow::bail!(\"domain '{}' is blocked by browser_delegate policy\", domain);\n            }\n        }\n\n        // If allowed_domains is non-empty, it acts as an allowlist\n        if !self.config.allowed_domains.is_empty() {\n            let allowed = self\n                .config\n                .allowed_domains\n                .iter()\n                .any(|d| domain_matches(&domain, d));\n            if !allowed {\n                anyhow::bail!(\n                    \"domain '{}' is not in browser_delegate allowed_domains\",\n                    domain\n                );\n            }\n        }\n\n        Ok(())\n    }\n}\n\n/// Check whether `domain` matches a pattern (exact or suffix match).\nfn domain_matches(domain: &str, pattern: &str) -> bool {\n    let d = domain.to_lowercase();\n    let p = pattern.to_lowercase();\n    d == p || d.ends_with(&format!(\".{}\", p))\n}\n\n/// Maximum stderr bytes to capture from the subprocess.","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/browser_delegate.rs#L96-L132","documentation":"When browser_delegate's allowed_domains is non-empty it acts as a strict allowlist: the URL's domain must match an entry exactly or as a subdomain (domain_matches: equal, or ends with \".pattern\"), otherwise the task is rejected. An empty allowed_domains list means no restriction beyond the denylist.","triggerScenarios":"A task referencing https://api.example.org while allowed_domains=[\"example.com\"] — example.org does not match; sibling domains and different TLDs never match a parent entry.","commonSituations":"Forgetting the allowlist is exact-or-child-domain: docs.example.com passes with entry example.com, but example.co.uk does not; new microservices on separate domains need config updates; typos in either the URL or the config entry.","solutions":["Add the exact domain (or its parent) to browser_delegate allowed_domains in config","Verify the matching semantics: suffix matching covers subdomains only, never sibling TLDs","Keep the allowlist maintained alongside the services the agent is authorized to touch"],"exampleFix":"# config.toml — before\n[browser_delegate]\nallowed_domains = [\"example.com\"]\n\n# after\nallowed_domains = [\"example.com\", \"api.example.org\"]","handlingStrategy":"validation","validationCode":"fn domain_matches(domain: &str, pattern: &str) -> bool {\n    let (d, p) = (domain.to_lowercase(), pattern.to_lowercase());\n    d == p || d.ends_with(&format!(\".{p}\"))\n}\nlet allowed = allowed_domains.iter().any(|p| domain_matches(&host, p));\nif !allowed_domains.is_empty() && !allowed {\n    return Err(format!(\"host '{host}' not allowlisted\"));\n}","typeGuard":null,"tryCatchPattern":"match delegate.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_delegate allowed_domains\") {\n            // add the domain to config or drop the URL from the task\n        }\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Keep the allowlist in sync with the services the agent may access","Remember matching is exact-or-subdomain; sibling TLDs need their own entries","Lowercase domains in config to avoid case-mismatch surprises"],"tags":["browser-delegate","domain","allowlist","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"}