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

  1. Move the target service to a routable (private or public) address and use that host/IP instead.
  2. If the service must stay link-local, front it with a proxy on a routable address and allowlist that proxy's host.
  3. 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

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/c1afc636802c3524. Report an issue: GitHub.