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
- 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.
- Treat occurrences of this error in logs as a security signal: inspect the prompt/response that produced the URL for injection attempts.
- 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
- Treat any metadata-literal image URL as a prompt-injection indicator; alert on it rather than routing around it.
- Never place instance credentials (metadata service) in the reach of URL-driven fetch paths.
- There is no opt-in: both public-only and private-allowed modes refuse metadata addresses.
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
- Generated image URL targets a local or non-global host
- Blocked cloud metadata host: {host}
- Generated image URL userinfo is not allowed
- PermissionDenied
- security.otp.cache_valid_secs must be greater than or equal
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/edc8a77e2b600487.
Report an issue: GitHub.