{"record":{"id":"cf72c92e76a0a880","repo":"zed-industries/zed","slug":"oauth-endpoint-must-not-point-to-reserved-ipv6-add","errorCode":null,"errorMessage":"OAuth endpoint must not point to reserved IPv6 address: {}","messagePattern":"OAuth endpoint must not point to reserved IPv6 address: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/context_server/src/oauth.rs","lineNumber":107,"sourceCode":"            }\n            url::Host::Ipv6(ip) => {\n                // Check for IPv4-mapped IPv6 addresses (::ffff:a.b.c.d) which\n                // could bypass the IPv4 checks above.\n                if let Some(mapped_v4) = ip.to_ipv4_mapped() {\n                    if mapped_v4.is_private()\n                        || mapped_v4.is_link_local()\n                        || mapped_v4.is_broadcast()\n                        || mapped_v4.is_unspecified()\n                    {\n                        bail!(\n                            \"OAuth endpoint must not point to private/reserved IP: ::ffff:{}\",\n                            mapped_v4\n                        );\n                    }\n                }\n\n                if ip.is_unspecified() || ip.is_multicast() {\n                    bail!(\n                        \"OAuth endpoint must not point to reserved IPv6 address: {}\",\n                        ip\n                    );\n                }\n                // IPv6 Unique Local Addresses (fc00::/7). is_unique_local() is\n                // nightly-only, so check the prefix manually.\n                if (ip.segments()[0] & 0xfe00) == 0xfc00 {\n                    bail!(\n                        \"OAuth endpoint must not point to IPv6 unique-local address: {}\",\n                        ip\n                    );\n                }\n            }\n            url::Host::Domain(_) => {\n                // Domain-based SSRF prevention requires resolver-level checks.\n                // See known limitation in the doc comment above.\n            }\n        }","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/context_server/src/oauth.rs#L89-L125","documentation":"The IPv6 branch of validate_oauth_url rejects endpoint URLs whose host is the unspecified address (::) or a multicast address (ff00::/8), after the IPv4-mapped check has passed. An unspecified host has no real destination, and a multicast host would make the HTTP client send token traffic to a group of listeners, so both are treated as reserved and unsafe for OAuth.","triggerScenarios":"An endpoint URL with host :: (e.g. https://[::]:8080/token, typically produced by binding-address strings mistakenly copied into a URL) or a multicast literal such as https://[ff02::1]/oauth/token passed to validate_oauth_url().","commonSituations":"A server config that lists its bind address (0.0.0.0 or ::) as its advertised URL instead of a reachable host; malformed metadata documents generated by templating that inject the listener address; security probes that enumerate reserved IPv6 forms.","solutions":["Advertise a concrete reachable host or DNS name in the server's metadata, never the bind address","Fix the server config so the public-facing URL (scheme + host + port) is distinct from the listen specification","If local, use localhost/127.0.0.1 which are explicitly permitted"],"exampleFix":"# before (bind address used as URL)\n\"authorization_servers\": [\"https://[::]:8443\"]\n\n# after\n\"authorization_servers\": [\"https://auth.example.com:8443\"]","handlingStrategy":"validation","validationCode":"use url::Url;\n\nfn ipv6_host_allowed(url: &Url) -> bool {\n    match url.host() {\n        Some(url::Host::Ipv6(ip)) => !ip.is_unspecified() && !ip.is_multicast(),\n        _ => true,\n    }\n}\n\nif !ipv6_host_allowed(&endpoint) {\n    bail_user_config!(\"endpoint uses reserved IPv6 (unspecified/multicast): {}\", endpoint);\n}","typeGuard":"fn is_concrete_ipv6_literal(url: &Url) -> Option<bool> {\n    match url.host() {\n        Some(url::Host::Ipv6(ip)) => Some(!ip.is_unspecified() && !ip.is_multicast()),\n        _ => None,\n    }\n}","tryCatchPattern":"match validate_oauth_url(&endpoint) {\n    Err(err) if err.to_string().contains(\"reserved IPv6\") => {\n        // bind-address used as URL — fix the server's advertised origin\n        report_metadata_bug(&endpoint, err);\n        Err(err)\n    }\n    other => other,\n}","preventionTips":["Keep listen addresses (0.0.0.0, ::) strictly separate from advertised URLs in server configs","Lint generated metadata documents for IP-literal hosts of any kind before shipping","Prefer DNS names even for IPv6-only deployments"],"tags":["oauth","mcp","ssrf","ipv6","url-validation","security"],"backgroundTag":"ssrf-private-ip-blocked","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}