{"record":{"id":"652aec3fc010bd1d","repo":"zed-industries/zed","slug":"cannot-fetch-host-loopback-and-ip-literal-hos","errorCode":null,"errorMessage":"cannot fetch {host:?}: loopback and IP-literal hosts can't be granted network \\\n             access individually. They are only reachable once unsandboxed access has been \\\n             granted (for example, via a terminal command that requests it).","messagePattern":"cannot fetch (.+?): loopback and IP-literal hosts can't be granted network \\\\\n             access individually\\. They are only reachable once unsandboxed access has been \\\\\n             granted \\(for example, via a terminal command that requests it\\)\\.","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/agent/src/tools/fetch_tool.rs","lineNumber":205,"sourceCode":"        .port_or_known_default()\n        .unwrap_or(if parsed.scheme() == \"http\" { 80 } else { 443 });\n\n    http_proxy::PinnedHost::resolve(host, port).map(|_pinned| ())?;\n    Ok(())\n}\n\n/// Extracts the host from a fetch URL as a [`http_proxy::HostPattern`] so it can\n/// be matched against the shared network grants. Mirrors the scheme handling in\n/// [`normalize_url`] (defaulting to `https://` when none is given).\nfn host_pattern_for_url(url: &str) -> Result<http_proxy::HostPattern> {\n    let normalized = normalize_url(url);\n    let parsed =\n        url::Url::parse(&normalized).with_context(|| format!(\"could not parse URL {url:?}\"))?;\n    let host = parsed\n        .host_str()\n        .with_context(|| format!(\"URL {url:?} has no host to authorize network access for\"))?;\n    http_proxy::HostPattern::parse(host).map_err(|error| match error {\n        http_proxy::HostPatternError::IpLiteral(_) => anyhow::anyhow!(\n            \"cannot fetch {host:?}: loopback and IP-literal hosts can't be granted network \\\n             access individually. They are only reachable once unsandboxed access has been \\\n             granted (for example, via a terminal command that requests it).\"\n        ),\n        error => anyhow::anyhow!(\"cannot authorize network access to {host:?}: {error}\"),\n    })\n}\n\nimpl AgentTool for FetchTool {\n    type Input = FetchToolInput;\n    type Output = String;\n\n    const NAME: &'static str = \"fetch\";\n\n    fn kind() -> acp::ToolKind {\n        acp::ToolKind::Fetch\n    }\n","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/crates/agent/src/tools/fetch_tool.rs#L187-L223","documentation":"The fetch tool refuses to grant sandboxed network access to loopback or IP-literal hosts: `HostPattern::parse` fails with the IpLiteral variant, and by design such hosts cannot be granted network access individually — they are reachable only after unsandboxed access has been granted (for example via an approved terminal command). The fetch fails before any request is made.","triggerScenarios":"Asking the agent to fetch a URL whose host is an IP literal (127.0.0.1, 192.168.1.1, 169.254.169.254) or localhost while network access is sandboxed; `host_pattern_for_url` hits the IpLiteral error arm.","commonSituations":"Pointing the agent at local dev servers (http://localhost:3000); attempts to reach cloud metadata endpoints (169.254.169.254); internal services addressed by raw IP.","solutions":["Use a resolvable hostname instead of the raw IP (add a hosts-file entry or DNS name), then grant access to that host.","Alternatively obtain unsandboxed network access first — e.g. run and approve a terminal command that requests it — after which loopback/IP hosts are reachable.","For local dev servers, expose them under a hostname the proxy can authorize.","Do not attempt to bypass the check; it exists to block SSRF to loopback and metadata services."],"exampleFix":"# before\nfetch http://127.0.0.1:8080/api   # refused: IP-literal host\n\n# after (map a hostname in /etc/hosts: 127.0.0.1 myhost.test)\nfetch http://myhost.test:8080/api # hostname is individually grantable","handlingStrategy":"validation","validationCode":"let parsed = url::Url::parse(&normalize_url(url))?;\nlet host = parsed.host_str().unwrap_or_default();\nif host.eq_ignore_ascii_case(\"localhost\") || host.parse::<std::net::IpAddr>().is_ok() {\n    anyhow::bail!(\n        \"loopback/IP host {host} needs unsandboxed access — use a hostname or approve terminal network access first\"\n    );\n}","typeGuard":"fn is_ip_or_loopback_host(url: &str) -> bool {\n    match url::Url::parse(url) {\n        Ok(parsed) => parsed\n            .host_str()\n            .map(|host| {\n                host.eq_ignore_ascii_case(\"localhost\")\n                    || host.parse::<std::net::IpAddr>().is_ok()\n            })\n            .unwrap_or(false),\n        Err(_) => false,\n    }\n}","tryCatchPattern":null,"preventionTips":["Expose local services under hostnames (e.g. a hosts-file entry) instead of raw IPs.","Grant unsandboxed network access via an approved terminal command when loopback is truly required.","Never try to bypass the check — it blocks SSRF to loopback and metadata endpoints."],"tags":["fetch","network","security","sandbox","ssrf","loopback"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}