zed-industries/zed · error

OAuth endpoint must not point to IPv6 unique-local address:

Error message

OAuth endpoint must not point to IPv6 unique-local address: {}

What it means

The final IPv6 guard in validate_oauth_url blocks unique-local addresses (fc00::/7), detected by masking the first segment with 0xfe00 and comparing to 0xfc00 because std's is_unique_local() is nightly-only. These are the IPv6 equivalents of RFC 1918 private ranges (fd00::/8 commonly used for internal networks), so the same SSRF rationale applies as for private IPv4.

Source

Thrown at crates/context_server/src/oauth.rs:115

                        || mapped_v4.is_unspecified()
                    {
                        bail!(
                            "OAuth endpoint must not point to private/reserved IP: ::ffff:{}",
                            mapped_v4
                        );
                    }
                }

                if ip.is_unspecified() || ip.is_multicast() {
                    bail!(
                        "OAuth endpoint must not point to reserved IPv6 address: {}",
                        ip
                    );
                }
                // IPv6 Unique Local Addresses (fc00::/7). is_unique_local() is
                // nightly-only, so check the prefix manually.
                if (ip.segments()[0] & 0xfe00) == 0xfc00 {
                    bail!(
                        "OAuth endpoint must not point to IPv6 unique-local address: {}",
                        ip
                    );
                }
            }
            url::Host::Domain(_) => {
                // Domain-based SSRF prevention requires resolver-level checks.
                // See known limitation in the doc comment above.
            }
        }
    }

    Ok(())
}

/// Parsed from the MCP server's WWW-Authenticate header or well-known endpoint
/// per RFC 9728 (OAuth 2.0 Protected Resource Metadata).
#[derive(Debug, Clone, Serialize, Deserialize)]

View on GitHub (pinned to f4178619ac)

Solutions

  1. Advertise a global (public) IPv6 address or, better, a DNS name for the endpoint
  2. Give the internal service a domain name resolvable by the client and put that name in the metadata document
  3. For same-machine testing stick to http://localhost or http://127.0.0.1, which the earlier checks allow

Example fix

// before
"token_endpoint": "https://[fdab:dead:beef::1]/token"

// after
"token_endpoint": "https://auth.internal.example.com/token"
Defensive patterns

Strategy: validation

Validate before calling

use url::Url;

fn is_ula_ipv6(url: &Url) -> bool {
    matches!(url.host(), Some(url::Host::Ipv6(ip))
        if (ip.segments()[0] & 0xfe00) == 0xfc00)
}

if is_ula_ipv6(&endpoint) {
    bail_user_config!("endpoint uses fc00::/7 unique-local address: {}", endpoint);
}

Type guard

fn is_global_ipv6_literal(url: &Url) -> Option<bool> {
    match url.host() {
        Some(url::Host::Ipv6(ip)) => Some((ip.segments()[0] & 0xfe00) != 0xfc00),
        _ => None,
    }
}

Try / catch

match validate_oauth_url(&endpoint) {
    Err(err) if err.to_string().contains("unique-local") => {
        // ULA literal in metadata — swap to a DNS name or global address
        report_metadata_bug(&endpoint, err);
        Err(err)
    }
    other => other,
}

Prevention

When it happens

Trigger: An endpoint URL whose host is an IPv6 literal whose first 7 bits are 1111110 — e.g. https://[fd12:3456:789a::1]/token or anything in fc00::/7 — reaching the manual prefix check after passing the mapped-v4, unspecified, and multicast checks.

Common situations: Internal IPv6-only networks (docker ipv6 ULA subnets default to fd00::/64-based pools) whose services advertise ULA literals in OAuth metadata; tailnet/mesh VPNs that hand out fdxx addresses; someone attempting the IPv6 form of a private-range SSRF.

Related errors


AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20). Data as JSON: /api/errors/8383f06f0a0425d9. Report an issue: GitHub.