zed-industries/zed · error · anyhow::Error
refusing to follow redirect to non-HTTP(S) URL {target}
Error message
refusing to follow redirect to non-HTTP(S) URL {target} What it means
The fetch tool received a 3xx response whose Location header, resolved against the current URL, yields a scheme other than http/https (e.g. file://, ftp:, data:). As a defense against protocol smuggling and local-file exfiltration the tool refuses to follow the redirect and errors, naming the refused target URL.
Source
Thrown at crates/agent/src/tools/fetch_tool.rs:99
let normalized = normalize_url(url);
let mut response = http_client
.get(&normalized, AsyncBody::default(), false)
.await?;
let status = response.status();
if status.is_redirection() {
let location = response
.headers()
.get("location")
.context("redirect response is missing a Location header")?
.to_str()
.context("redirect response has an invalid Location header")?;
let target = url::Url::parse(&normalized)
.with_context(|| format!("could not parse URL {normalized:?}"))?
.join(location)
.with_context(|| format!("invalid redirect target {location:?}"))?;
anyhow::ensure!(
matches!(target.scheme(), "http" | "https"),
"refusing to follow redirect to non-HTTP(S) URL {target}"
);
return Ok(FetchStep::Redirect(target.to_string()));
}
let mut body = Vec::new();
response
.body_mut()
.read_to_end(&mut body)
.await
.context("error reading response body")?;
if status.is_client_error() {
let text = String::from_utf8_lossy(body.as_slice());
bail!("status error {}, response: {text:?}", status.as_u16());
}
View on GitHub (pinned to bc538def45)
Solutions
- If the resource is genuinely available over http(s), fetch that final URL directly and fix the server's redirect.
- If you own the server, emit absolute http(s) Location headers.
- Never relax the scheme check — it is a security boundary; only http and https are allowed.
- Treat occurrences on external sites as suspicious (possible exfiltration attempt) and stop following the chain.
Example fix
# before: server responds with HTTP/3 301 Location: ftp://example.com/file # after: server responds with HTTP/3 301 Location: https://example.com/file
Defensive patterns
Strategy: validation
Validate before calling
// Resolve redirects yourself and verify every hop before fetching:
let mut current = url.to_string();
for _ in 0..MAX_REDIRECTS {
let response = issue_request(¤t).await?;
if !response.status().is_redirection() { break; }
let location = header_str(response.headers(), "location").unwrap_or("");
let next = url::Url::parse(¤t)?.join(location)?;
ensure!(
matches!(next.scheme(), "http" | "https"),
"refusing to follow redirect to non-HTTP(S) URL {next}"
);
current = next.to_string();
} Type guard
fn is_http_url(url: &str) -> bool {
url::Url::parse(url)
.map(|parsed| matches!(parsed.scheme(), "http" | "https"))
.unwrap_or(false)
} Try / catch
match fetch_tool_run(url).await {
Err(err) if err.to_string().contains("non-HTTP(S) URL") => {
// Redirect target refused by design — fetch an approved http(s) URL instead.
report_refused_redirect(err.to_string());
}
other => other,
} Prevention
- Never instruct the agent to follow redirects to file:// or ftp:// URLs.
- Fix your own servers to emit absolute http(s) Location headers.
- Treat this error on external sites as a possible SSRF/exfiltration attempt and stop.
When it happens
Trigger: A fetched URL returns a redirect with `Location: file:///...`, `ftp://...`, or any non-HTTP(S) URI; the parsed-and-joined target fails the http/https scheme check.
Common situations: Adversarial or compromised sites redirecting to local files; misconfigured servers emitting ftp:// links; test fixtures with exotic Location headers; model-supplied URLs that hop protocols.
Related errors
- cannot fetch {host:?}: loopback and IP-literal hosts can't b
- archive links are not supported: {member.name}
- archive member escapes destination: {member.name}
- Sentry API returned HTTP {error.code} for {path}: {detail}
- Sentry API returned HTTP {err.code} for {path}: {detail}
AI-assisted analysis of zed-industries/zed@bc538def45 (2026-08-16).
Data as JSON: /api/errors/7e6b3f460e79269d.
Report an issue: GitHub.