zed-industries/zed · error
OAuth endpoint must not point to private/reserved IP: {}
Error message
OAuth endpoint must not point to private/reserved IP: {} What it means
The SSRF guard in validate_oauth_url rejects OAuth endpoint URLs whose host is an IP-literal in a private or reserved IPv4 range: RFC 1918 private space (10/8, 172.16/12, 192.168/16), link-local 169.254/16 (including cloud metadata addresses), broadcast 255.255.255.255, and unspecified 0.0.0.0. Loopback was already permitted by require_https_or_loopback, so seeing this message means a non-loopback internal address was reached. The goal is to stop a malicious MCP server from directing Zed's HTTP client at internal network resources such as cloud instance-metadata endpoints.
Source
Thrown at crates/context_server/src/oauth.rs:84
/// protections against private/reserved IP ranges.
///
/// This wraps [`require_https_or_loopback`] and adds IP-range checks to prevent
/// an attacker-controlled MCP server from directing Zed to fetch internal
/// network resources via metadata URLs.
///
/// **Known limitation:** Domain-name URLs that resolve to private IPs are *not*
/// blocked here — full mitigation requires resolver-level validation (e.g. a
/// custom `Resolve` implementation). This function only blocks IP-literal URLs.
fn validate_oauth_url(url: &Url) -> Result<()> {
require_https_or_loopback(url)?;
if let Some(host) = url.host() {
match host {
url::Host::Ipv4(ip) => {
// Loopback is already allowed by require_https_or_loopback.
if ip.is_private() || ip.is_link_local() || ip.is_broadcast() || ip.is_unspecified()
{
bail!(
"OAuth endpoint must not point to private/reserved IP: {}",
ip
);
}
}
url::Host::Ipv6(ip) => {
// Check for IPv4-mapped IPv6 addresses (::ffff:a.b.c.d) which
// could bypass the IPv4 checks above.
if let Some(mapped_v4) = ip.to_ipv4_mapped() {
if mapped_v4.is_private()
|| mapped_v4.is_link_local()
|| mapped_v4.is_broadcast()
|| mapped_v4.is_unspecified()
{
bail!(
"OAuth endpoint must not point to private/reserved IP: ::ffff:{}",
mapped_v4
);View on GitHub (pinned to f4178619ac)
Solutions
- Give the OAuth endpoint a public DNS name reachable over HTTPS instead of a raw private IP
- If the endpoint is on the same machine, use localhost/127.0.0.1 which the earlier require_https_or_loopback check already allows
- Expose the internal service through an ingress/proxy with a public hostname so the advertised URL contains a domain, not the private IP
- If you are auditing a security report, treat the trigger URL as malicious — this bail is the SSRF mitigation working as designed
Example fix
// before "token_endpoint": "https://10.0.0.8:8443/token" // after "token_endpoint": "https://auth.internal.example.com:8443/token"
Defensive patterns
Strategy: validation
Validate before calling
use url::Url;
fn ipv4_host_allowed(url: &Url) -> bool {
match url.host() {
Some(url::Host::Ipv4(ip)) => {
!(ip.is_private() || ip.is_link_local() || ip.is_broadcast() || ip.is_unspecified())
}
_ => true, // domains/IPv6 handled by their own checks
}
}
let endpoint = Url::parse(&server_advertised)?;
if !ipv4_host_allowed(&endpoint) {
return bail_user_config("endpoint uses private/reserved IPv4 literal: {}", endpoint);
} Type guard
fn is_public_ipv4_literal(url: &Url) -> Option<bool> {
match url.host() {
Some(url::Host::Ipv4(ip)) => Some(
!(ip.is_private() || ip.is_link_local() || ip.is_broadcast() || ip.is_unspecified()),
),
_ => None, // not an IPv4 literal
}
} Try / catch
match validate_oauth_url(&endpoint) {
Err(err) if err.to_string().contains("private/reserved IP") => {
// log as potential SSRF attempt / fix server metadata to use a domain name
log::warn!("blocked internal-IP OAuth endpoint: {endpoint}");
return Err(err);
}
other => other,
} Prevention
- Never advertise raw internal IPs in OAuth metadata; use DNS names
- Remember domains resolving to private IPs are NOT caught (documented limitation) — pair with egress controls if that matters to you
- When self-hosting for LAN use, put the service behind a local DNS name and be aware IP-literal forms are blocked by design
When it happens
Trigger: validate_oauth_url() receives an endpoint URL with an IPv4 literal host that satisfies ip.is_private() || ip.is_link_local() || ip.is_broadcast() || ip.is_unspecified() — e.g. http://169.254.169.254/latest/meta-data or https://10.1.2.3/token (note https does not bypass this check; only the value of the host matters).
Common situations: An MCP server under test runs inside a docker/k8s cluster and advertises its cluster-internal IP (10.x or 172.17.x) in authorization_servers or token_endpoint; an attacker-controlled server returns resource_metadata pointing at 169.254.169.254 to harvest cloud credentials; home-lab setups that legitimately use 192.168.x addresses for OAuth and get blocked by the guard.
Related errors
- OAuth endpoint must not point to private/reserved IP: ::ffff
- OAuth endpoint must not point to reserved IPv6 address: {}
- OAuth endpoint must not point to IPv6 unique-local address:
- OAuth endpoint must use HTTPS (got {}://{})
- WWW-Authenticate header does not use Bearer scheme
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/3d93d0881743dccf.
Report an issue: GitHub.