zeroclaw-labs/zeroclaw · error · anyhow::Error
Blocked link-local host: {host}; 169.254.0.0/16 is blocked u
Error message
Blocked link-local host: {host}; 169.254.0.0/16 is blocked unconditionally because cloud metadata services are hosted in that range What it means
Thrown by HttpRequestTool::validate_url_policy (crates/zeroclaw-tools/src/http_request.rs:146) when the URL host is a literal IP inside 169.254.0.0/16. The entire link-local range is blocked unconditionally because cloud metadata services live there; the tool does not attempt to distinguish metadata addresses from other link-local hosts. There is deliberately no config override for this range.
Source
Thrown at crates/zeroclaw-tools/src/http_request.rs:146
}
if !url.starts_with("http://") && !url.starts_with("https://") {
anyhow::bail!("Only http:// and https:// URLs are allowed");
}
if self.allowed_domains.is_empty() {
anyhow::bail!(
"HTTP request tool is enabled but no allowed_domains are configured. Add [http_request].allowed_domains in config.toml"
);
}
let host = extract_host(url)?;
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)
{View on GitHub (pinned to 88bb9c8533)
Solutions
- Move the target service to a routable (private or public) address and use that host/IP instead.
- If the service must stay link-local, front it with a proxy on a routable address and allowlist that proxy's host.
- Never attempt to bypass this block; if you believe you need 169.254/16 access, re-examine the design (the guard exists to stop credential theft).
Example fix
# before url = "http://169.254.170.2/v2/credentials" # ECS agent on link-local # after url = "http://10.0.20.15:8080/v2/credentials" # routable internal proxy, allowlisted in allowed_private_hosts
Defensive patterns
Strategy: validation
Validate before calling
fn is_link_literal(host: &str) -> bool {
host.parse::<std::net::IpAddr>().is_ok_and(|ip| {
matches!(ip, std::net::IpAddr::V4(v4) if v4.is_link_local())
})
} Try / catch
let result = tool.execute(args).await?;
if let Some(err) = &result.error {
if err.contains("Blocked link-local host") {
// point the integration at a routable address; this block has no override
}
} Prevention
- Avoid 169.254/16 for service addresses; reserve it for link-local protocols.
- Front link-local-only services with a proxy on a routable address.
- Document in runbooks that this range is unconditionally blocked by design.
When it happens
Trigger: Requesting any 169.254.x.x literal IP, e.g. "http://169.254.170.2/v2/credentials" (ECS agent), "https://169.254.1.1", or any self-assigned link-local address; services discovered via link-local addressing (keepalived VIPs, IoT discovery) requested by literal IP.
Common situations: AWS ECS tasks trying to reach the ECS credentials endpoint; on-prem networks that intentionally use 169.254/16 for infrastructure services; debugging against link-local hosts during network setup.
Related errors
- Blocked cloud metadata host: {host}
- Blocked local/private host: {host}
- Only http:// and https:// URLs are allowed
- URL userinfo is not allowed
- Cross-host redirects are blocked so DNS validation remains p
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/c1afc636802c3524.
Report an issue: GitHub.