zeroclaw-labs/zeroclaw · error
Cross-host redirects are blocked so DNS validation remains p
Error message
Cross-host redirects are blocked so DNS validation remains pinned
What it means
web_fetch pins its DNS/security validation to the host of the originally requested URL. When the HTTP client follows a redirect, validate_redirect_target re-parses the Location header and requires its host to equal the pinned host exactly (string comparison — 'www.example.com' and 'example.com.' are different). Any host change is refused so that a redirect cannot move the request to a host that never went through validation, which would defeat the DNS-pinning anti-rebinding design.
Source
Thrown at crates/zeroclaw-tools/src/web_fetch.rs:645
}
}
fn proxy_conflicts_with_dns_pinning(config: &ProxyConfig) -> bool {
(config.enabled && config.scope == ProxyScope::Environment)
|| (config.has_any_proxy_url() && config.should_apply_to_service("tool.web_fetch"))
}
fn validate_redirect_target(
raw_url: &str,
pinned_host: &str,
allowed_domains: &[String],
blocked_domains: &[String],
allowed_private_hosts: &[String],
) -> anyhow::Result<()> {
let redirect_url = reqwest::Url::parse(raw_url)
.map_err(|e| anyhow::Error::msg(format!("Invalid URL format: {e}")))?;
if redirect_url.host_str() != Some(pinned_host) {
anyhow::bail!("Cross-host redirects are blocked so DNS validation remains pinned");
}
validate_target_url_with_dns_check(
raw_url,
allowed_domains,
blocked_domains,
allowed_private_hosts,
"web_fetch",
|_, _| Ok(()),
)?;
Ok(())
}
fn resolve_target_url(
raw_url: &str,
allowed_domains: &[String],
blocked_domains: &[String],
allowed_private_hosts: &[String],View on GitHub (pinned to 88bb9c8533)
Solutions
- Resolve the final URL yourself (e.g. a HEAD/GET without redirect following, or a link-expander) and fetch that target directly — it will then be fully validated against allow/block domains on its own merits
- If the redirect is legitimate and constant, update the stored source URL to the destination host
- Do not loosen the same-host rule for convenience; it is the anti-rebinding boundary
Example fix
// before
fetch("https://bit.ly/3xyz123") // redirects to example.org -> bails
// after
let final = resolve_without_following("https://bit.ly/3xyz123"); // "https://example.org/post"
fetch(final) // validated + fetched on its own Defensive patterns
Strategy: validation
Validate before calling
// Expand redirects yourself, then fetch the final URL directly
async fn final_url(u: &str) -> anyhow::Result<String> {
let client = reqwest::Client::builder().redirect(reqwest::redirect::Policy::none()).build()?;
let resp = client.head(u).send().await?;
Ok(resp.headers().get("location")
.and_then(|l| l.to_str().ok()).map(str::to_string).unwrap_or_else(|| u.into()))
} Type guard
fn same_host(a: &str, b: &str) -> bool {
match (reqwest::Url::parse(a), reqwest::Url::parse(b)) {
(Ok(x), Ok(y)) => x.host_str().is_some() && x.host_str() == y.host_str(),
_ => false,
}
} Try / catch
Err(e) if e.to_string().starts_with("Cross-host redirects are blocked") => {
// resolve the redirect chain manually, then issue a fresh, fully-validated fetch of the final URL
} Prevention
- Store final destination URLs rather than short links in long-lived data
- Expand links at ingestion time so redirects never happen during security-sensitive fetches
- Remember the host match is exact-string: normalize www/trailing-dot variants before comparing
- Treat a redirect to an unexpected host in untrusted content as a red flag, not an inconvenience
When it happens
Trigger: Short links (bit.ly, t.co), canonicalization redirects from www to apex or vice versa, CDN failovers, http-to-https redirects that also change hostname, and redirects from a pinned host to an attacker-chosen host (the case the guard exists for).
Common situations: Fetching news articles or shared links that redirect; operators enabling redirect following for convenience and then hitting legitimate infrastructural redirects; security reviews confirming redirect-based SSRF is contained (the cited tests pin exactly this).
Related errors
- PermissionDenied
- Blocked cloud metadata host: {host}
- Blocked link-local host: {host}; 169.254.0.0/16 is blocked u
- Blocked local/private host: {host}
- Blocked marker redirect to private or local host ({host}); r
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/d01450ae74830882.
Report an issue: GitHub.