zeroclaw-labs/zeroclaw · critical

Generated image URL targets a cloud metadata host

Error message

Generated image URL targets a cloud metadata host

What it means

When the generated image URL's host is an IP literal, parse_public_https_url checks it against domain_guard::is_cloud_metadata_ip, which covers the entire 169.254.0.0/16 IPv4 link-local range (AWS/GCP/ECS task metadata at 169.254.169.254, ECS 169.254.170.2/23), Alibaba's metadata address, Azure's WireServer platform address, AWS fd00:ec2::/64, the GCP IPv6 metadata address, and IPv4-mapped/NAT64-embedded forms of those addresses. Cloud metadata endpoints hand out instance credentials, so they are refused unconditionally — even the private-resolution opt-in that exists for http_request never re-opens them.

Source

Thrown at crates/zeroclaw-tools/src/image_gen.rs:51

    }
    if !url.username().is_empty() || url.password().is_some() {
        anyhow::bail!("Generated image URL userinfo is not allowed");
    }

    let request_host = url
        .host_str()
        .ok_or_else(|| anyhow::Error::msg("Generated image URL must include a host"))?;
    if request_host.ends_with('.') {
        anyhow::bail!("Generated image URL host must not end with a dot");
    }
    let host = domain_guard::normalize_domain(request_host)
        .ok_or_else(|| anyhow::Error::msg("Generated image URL host is invalid"))?;
    let ip_literal = host.parse::<IpAddr>().ok();
    if domain_guard::is_private_or_local_host(&host) {
        anyhow::bail!("Generated image URL targets a local or non-global host");
    }
    if ip_literal.is_some_and(domain_guard::is_cloud_metadata_ip) {
        anyhow::bail!("Generated image URL targets a cloud metadata host");
    }

    // Request the host that was validated, not the raw spelling. Normalization
    // can change the host (a leading dot is stripped), and the DNS pin applied
    // by `generated_image_client_with_builder` is keyed on the normalized host.
    // Without this, `resolve_to_addrs` never matches the request host and
    // reqwest silently falls back to its own unvalidated lookup. IP-literal
    // hosts are left alone: they carry no DNS pin, and `set_host` rejects the
    // unbracketed IPv6 form that normalization produces.
    if ip_literal.is_none() {
        url.set_host(Some(&host))
            .map_err(|_| anyhow::Error::msg("Generated image URL host is invalid"))?;
    }

    let port = url
        .port_or_known_default()
        .ok_or_else(|| anyhow::Error::msg("Generated image URL must include a valid port"))?;
    Ok((url, host, port))

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Never target metadata addresses from the image pipeline; if a legitimate service uses 169.254.x.x, give it a routable address or public name.
  2. Treat occurrences of this error in logs as a security signal: inspect the prompt/response that produced the URL for injection attempts.
  3. Check URL-building code that mangles addresses into link-local form.
Defensive patterns

Strategy: validation

Validate before calling

fn is_metadata_literal(host: &str) -> bool {
    if let Ok(ip) = host.trim_end_matches('.').parse::<std::net::IpAddr>() {
        let v4 = match ip { std::net::IpAddr::V4(v) => Some(v), std::net::IpAddr::V6(_) => None };
        return v4.is_some_and(|v| v.octets()[0] == 169 && v.octets()[1] == 254);
    }
    false
}

Type guard

fn is_safe_image_url_host(u: &reqwest::Url) -> bool {
    u.host_str().is_some_and(|h| !is_metadata_literal(h))
}

Try / catch

if let Err(e) = validate_image_target(url, nat64).await {
    if e.to_string().contains("cloud metadata host") {
        // security event: log the originating prompt/response and fail closed; do not retry or rewrite the URL
    }
}

Prevention

When it happens

Trigger: The image URL host is a metadata IP literal such as https://169.254.169.254/... , https://100.100.100.200/..., https://168.63.129.16/..., or an IPv6/NAT64 form embedding one of those; typically the result of prompt injection trying to exfiltrate instance credentials, or a misconstructed test URL.

Common situations: Adversarial prompts embedding metadata URLs to test SSRF hardening, security scans of agent deployments, and accidentally pasted cloud-debug links into image fields.

Related errors


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