{"record":{"id":"c1afc636802c3524","repo":"zeroclaw-labs/zeroclaw","slug":"blocked-link-local-host-host-169-254-0-0-16-is","errorCode":null,"errorMessage":"Blocked link-local host: {host}; 169.254.0.0/16 is blocked unconditionally because cloud metadata services are hosted in that range","messagePattern":"Blocked link-local host: (.+?); 169\\.254\\.0\\.0/16 is blocked unconditionally because cloud metadata services are hosted in that range","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-tools/src/http_request.rs","lineNumber":146,"sourceCode":"        }\n\n        if !url.starts_with(\"http://\") && !url.starts_with(\"https://\") {\n            anyhow::bail!(\"Only http:// and https:// URLs are allowed\");\n        }\n\n        if self.allowed_domains.is_empty() {\n            anyhow::bail!(\n                \"HTTP request tool is enabled but no allowed_domains are configured. Add [http_request].allowed_domains in config.toml\"\n            );\n        }\n\n        let host = extract_host(url)?;\n        if let Ok(ip) = host.parse::<IpAddr>() {\n            if domain_guard::is_known_cloud_metadata_endpoint(ip) {\n                anyhow::bail!(\"Blocked cloud metadata host: {host}\");\n            }\n            if domain_guard::is_cloud_metadata_ip(ip) {\n                anyhow::bail!(\n                    \"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        {","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-tools/src/http_request.rs#L128-L164","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"# before\nurl = \"http://169.254.170.2/v2/credentials\"  # ECS agent on link-local\n\n# after\nurl = \"http://10.0.20.15:8080/v2/credentials\"  # routable internal proxy, allowlisted in allowed_private_hosts","handlingStrategy":"validation","validationCode":"fn is_link_literal(host: &str) -> bool {\n    host.parse::<std::net::IpAddr>().is_ok_and(|ip| {\n        matches!(ip, std::net::IpAddr::V4(v4) if v4.is_link_local())\n    })\n}","typeGuard":null,"tryCatchPattern":"let result = tool.execute(args).await?;\nif let Some(err) = &result.error {\n    if err.contains(\"Blocked link-local host\") {\n        // point the integration at a routable address; this block has no override\n    }\n}","preventionTips":["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."],"tags":["http","security","ssrf","link-local","zeroclaw"],"backgroundTag":"ssrf-link-local-blocked","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}