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
- Do not target the metadata service from http_request; obtain credentials via the cloud provider SDK, instance profile, or environment variables instead.
- If you control the URL pipeline, reject known metadata IPs before the tool call and log the attempt as a security event.
- 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
- Strip or reject literal IPs of 169.254.169.254 and known metadata addresses at URL-intake time.
- Give the agent its credentials via env/instance profile so it never needs the metadata endpoint.
- Alert on this error: it frequently indicates prompt injection or SSRF probing.
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
- Blocked link-local host: {host}; 169.254.0.0/16 is blocked u
- Blocked local/private host: {host}
- Only http:// and https:// URLs are allowed
- URL userinfo is not allowed
- Generated image URL targets a cloud metadata host
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/3b9944c109f94b4c.
Report an issue: GitHub.