zeroclaw-labs/zeroclaw · error · anyhow::Error

Blocked cloud metadata host: {host}

Error message

Blocked cloud metadata host: {host}

What it means

Thrown by HttpRequestTool::validate_url_policy (crates/zeroclaw-tools/src/http_request.rs:143) when the URL host is a literal IP address that domain_guard::is_known_cloud_metadata_endpoint recognizes as a cloud metadata service endpoint (e.g. 169.254.169.254 and equivalent well-known addresses). This is an unconditional SSRF guard: no allowlist entry, wildcard, or allow_private_hosts setting can override it, because hitting the metadata service would leak instance credentials.

Source

Thrown at crates/zeroclaw-tools/src/http_request.rs:143

        if url.chars().any(char::is_whitespace) {
            anyhow::bail!("URL cannot contain whitespace");
        }

        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}");
        }

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Do not target the metadata service from http_request; obtain credentials via the cloud provider SDK, instance profile, or environment variables instead.
  2. If you control the URL pipeline, reject known metadata IPs before the tool call and log the attempt as a security event.
  3. If this fires unexpectedly, treat it as a prompt-injection or SSRF signal and audit where the URL came from.

Example fix

// before
let args = json!({"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"});

// after
// fetch credentials via the provider SDK / instance profile, not via http_request
let creds = aws_sdk_default_chain().provide_credentials().await?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_known_metadata_target(host: &str) -> bool {
    host.parse::<std::net::IpAddr>().is_ok_and(|ip| {
        ip.to_string().starts_with("169.254.")
    })
}
// reject before calling the tool and alert on it

Try / catch

let result = tool.execute(args).await?;
if let Some(err) = &result.error {
    if err.contains("Blocked cloud metadata host") {
        // security event: audit URL provenance; never retry or bypass
    }
}

Prevention

When it happens

Trigger: Passing url = "http://169.254.169.254/latest/meta-data/" or the equivalent known metadata IP in any cloud VM/container; an agent following a prompt-injection link that points at the metadata endpoint; integrations that try to read IAM role credentials over HTTP instead of the provider SDK.

Common situations: AWS/GCP/Azure/Aliyun instances where tooling tries to fetch instance metadata manually; penetration tests exercising the agent; SSRF attempts through LLM-driven URL input.

Related errors


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