{"record":{"id":"80a703f2a0fe27b7","repo":"zeroclaw-labs/zeroclaw","slug":"host-host-is-not-in-http-request-allowed-domai","errorCode":null,"errorMessage":"Host '{host}' is not in http_request.allowed_domains","messagePattern":"Host '(.+?)' is not in http_request\\.allowed_domains","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/http_request.rs","lineNumber":165,"sourceCode":"                    \"Blocked link-local host: {host}; 169.254.0.0/16 is blocked unconditionally \\\n                     because cloud metadata services are hosted in that range\"\n                );\n            }\n        }\n        let port = extract_port(url)?;\n\n        let private_host = domain_guard::is_private_or_local_host(&host);\n        let private_host_explicitly_allowed = private_host\n            && domain_guard::host_matches_allowlist(&host, &self.allowed_private_hosts);\n\n        if private_host && !private_host_explicitly_allowed && !self.allow_private_hosts {\n            anyhow::bail!(\"Blocked local/private host: {host}\");\n        }\n\n        if !private_host_explicitly_allowed\n            && !domain_guard::host_matches_allowlist(&host, &self.allowed_domains)\n        {\n            anyhow::bail!(\"Host '{host}' is not in http_request.allowed_domains\");\n        }\n\n        let private_resolution_allowed = self.allow_private_hosts\n            || domain_guard::host_matches_allowlist(&host, &self.allowed_private_hosts);\n\n        let canonical_url = if host.parse::<IpAddr>().is_ok() {\n            url.to_string()\n        } else {\n            let mut parsed = reqwest::Url::parse(url)\n                .map_err(|e| anyhow::Error::msg(format!(\"Invalid URL format: {e}\")))?;\n            parsed\n                .set_host(Some(&host))\n                .map_err(|_| anyhow::Error::msg(\"URL contains an invalid host\"))?;\n            parsed.to_string()\n        };\n\n        Ok(HttpRequestUrlPolicy {\n            url: canonical_url,","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/http_request.rs#L147-L183","documentation":"Thrown by HttpRequestTool::validate_url_policy (crates/zeroclaw-tools/src/http_request.rs:165) when the URL host does not match any entry in http_request.allowed_domains. Matching is case-insensitive, ignores a trailing dot, and supports parent-domain entries (allowlisting \"example.com\" also permits \"api.example.com\") plus the \"*\" wildcard. A host in allowed_private_hosts is exempt from this check.","triggerScenarios":"allowed_domains = [\"example.com\"] but requesting \"https://google.com\"; allowlisting the subdomain \"api.example.com\" and then requesting the apex \"https://example.com\" (parent matching is one-directional); requesting a literal IP that is not allowlisted; typos or www vs bare domain mismatches (\"www.example.com\" matches an \"example.com\" entry, but not vice versa when the entry is \"www.example.com\").","commonSituations":"Forgetting to allowlist a new API endpoint after rollout; environments (prod vs staging) with different allowlists; agents following redirects to CDN or auth domains not in the list; assuming allowlist is path- or port-based (it is host-only).","solutions":["Add the exact host (or its registrable parent domain) to [http_request] allowed_domains and restart.","Set allowed_domains = [\"*\"] if all public hosts should be reachable (private/metadata hosts are still blocked separately).","Check for near-misses: scheme vs host, www prefix, staging vs prod domain, trailing dot, and Punycode/IDN spellings."],"exampleFix":"# before\n[http_request]\nallowed_domains = [\"example.com\"]\n# request to https://api.github.com fails\n\n# after\n[http_request]\nallowed_domains = [\"example.com\", \"api.github.com\"]","handlingStrategy":"validation","validationCode":"fn host_in_allowlist(host: &str, allowed: &[String]) -> bool {\n    let h = host.trim_end_matches('.').to_lowercase();\n    allowed.iter().any(|a| a == \"*\" || h == *a || h.ends_with(&format!(\".{a}\")))\n}","typeGuard":null,"tryCatchPattern":"let result = tool.execute(args).await?;\nif let Some(err) = &result.error {\n    if err.contains(\"is not in http_request.allowed_domains\") {\n        // extract host, propose allowlist addition or reject the target\n    }\n}","preventionTips":["Allowlist the registrable parent domain (example.com) rather than each subdomain.","Diff the domains your workflows actually call against the allowlist during integration tests.","Watch redirects: they are not followed (Policy::none), but agents may re-issue the redirect target, which must also be allowlisted."],"tags":["http","allowlist","url","configuration","zeroclaw"],"backgroundTag":"host-not-allowlisted","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}