zeroclaw-labs/zeroclaw · error · anyhow::Error
Blocked local/private host: {host}
Error message
Blocked local/private host: {host} What it means
Thrown by HttpRequestTool::validate_url_policy (crates/zeroclaw-tools/src/http_request.rs:159) when domain_guard::is_private_or_local_host(host) is true (localhost, loopback, RFC1918 ranges, ULA/link-local IPv6, etc.) AND the host is not listed in allowed_private_hosts AND the allow_private_hosts flag is false. This is the configurable half of the SSRF guard: private hosts are blocked by default but can be explicitly permitted, unlike the metadata/link-local checks.
Source
Thrown at crates/zeroclaw-tools/src/http_request.rs:159
if let Ok(ip) = host.parse::<IpAddr>() {
if domain_guard::is_known_cloud_metadata_endpoint(ip) {
anyhow::bail!("Blocked cloud metadata host: {host}");
}
if domain_guard::is_cloud_metadata_ip(ip) {
anyhow::bail!(
"Blocked link-local host: {host}; 169.254.0.0/16 is blocked unconditionally \
because cloud metadata services are hosted in that range"
);
}
}
let port = extract_port(url)?;
let private_host = domain_guard::is_private_or_local_host(&host);
let private_host_explicitly_allowed = private_host
&& domain_guard::host_matches_allowlist(&host, &self.allowed_private_hosts);
if private_host && !private_host_explicitly_allowed && !self.allow_private_hosts {
anyhow::bail!("Blocked local/private host: {host}");
}
if !private_host_explicitly_allowed
&& !domain_guard::host_matches_allowlist(&host, &self.allowed_domains)
{
anyhow::bail!("Host '{host}' is not in http_request.allowed_domains");
}
let private_resolution_allowed = self.allow_private_hosts
|| domain_guard::host_matches_allowlist(&host, &self.allowed_private_hosts);
let canonical_url = if host.parse::<IpAddr>().is_ok() {
url.to_string()
} else {
let mut parsed = reqwest::Url::parse(url)
.map_err(|e| anyhow::Error::msg(format!("Invalid URL format: {e}")))?;
parsed
.set_host(Some(&host))View on GitHub (pinned to 88bb9c8533)
Solutions
- Add the specific host to [http_request] allowed_private_hosts (e.g. allowed_private_hosts = ["192.168.1.5", "myservice.internal"]); this also satisfies the allowed_domains check for that host.
- If broad internal access is intended, set allow_private_hosts = true in [http_request] (metadata/link-local IPs remain blocked).
- For public SaaS endpoints, verify you are not accidentally hitting a private mirror or internal proxy hostname.
Example fix
# before [http_request] allowed_domains = ["localhost"] # does not help: private check fires first # after [http_request] allowed_domains = ["example.com"] allowed_private_hosts = ["localhost", "127.0.0.1"]
Defensive patterns
Strategy: validation
Validate before calling
fn private_host_allowed(host: &str, allowed_private: &[String], allow_all: bool) -> bool {
if allow_all { return true; }
let h = host.trim_end_matches('.').to_lowercase();
allowed_private.iter().any(|a| h == *a || h.ends_with(&format!(".{a}")))
} Try / catch
let result = tool.execute(args).await?;
if let Some(err) = &result.error {
if err.contains("Blocked local/private host") {
// suggest adding the host to allowed_private_hosts (not allowed_domains)
}
} Prevention
- Keep a per-environment [http_request] allowed_private_hosts list for internal services instead of flipping allow_private_hosts globally.
- Remember allowed_domains wildcards never unlock private hosts; only the private-host config does.
- Use hostnames, not raw RFC1918 IPs, so allowlist entries stay stable.
When it happens
Trigger: url = "https://localhost:8080" or "http://127.0.0.1:9000" with default config; "https://192.168.1.5" or "https://10.0.0.12:8443" (RFC1918 literals); "http://[::1]:8080/" (IPv6 loopback); internal DNS names like "https://myservice.internal" that resolve-to/are-classified private. Note: allowed_domains = ["*"] does NOT bypass this (private blocking runs first).
Common situations: Local development against a dev server on localhost while reusing production config; homelab/LAN service integrations; internal API gateways on RFC1918 space; Docker setups where the tool container reaches another container by its private IP.
Related errors
- Blocked cloud metadata host: {host}
- Blocked link-local host: {host}; 169.254.0.0/16 is blocked u
- security.otp.cache_valid_secs must be greater than or equal
- security.otp.gated_actions[{i}] contains invalid characters:
- Blocked local/private host: {host}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/04da72d46c65c324.
Report an issue: GitHub.