{"record":{"id":"0c5139923df328c8","repo":"zed-industries/zed","slug":"oauth-endpoint-must-use-https-got","errorCode":null,"errorMessage":"OAuth endpoint must use HTTPS (got {}://{})","messagePattern":"OAuth endpoint must use HTTPS \\(got (.+?)://(.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/context_server/src/oauth.rs","lineNumber":58,"sourceCode":"///\n/// OAuth endpoints carry sensitive material (authorization codes, PKCE\n/// verifiers, tokens) and must use TLS. Plain HTTP is only permitted for\n/// loopback addresses, per RFC 8252 Section 8.3.\nfn require_https_or_loopback(url: &Url) -> Result<()> {\n    if url.scheme() == \"https\" {\n        return Ok(());\n    }\n    if url.scheme() == \"http\" {\n        if let Some(host) = url.host() {\n            match host {\n                url::Host::Ipv4(ip) if ip.is_loopback() => return Ok(()),\n                url::Host::Ipv6(ip) if ip.is_loopback() => return Ok(()),\n                url::Host::Domain(d) if d.eq_ignore_ascii_case(\"localhost\") => return Ok(()),\n                _ => {}\n            }\n        }\n    }\n    bail!(\n        \"OAuth endpoint must use HTTPS (got {}://{})\",\n        url.scheme(),\n        url.host_str().unwrap_or(\"?\")\n    )\n}\n\n/// Validate that a URL is safe to use as an OAuth endpoint, including SSRF\n/// protections against private/reserved IP ranges.\n///\n/// This wraps [`require_https_or_loopback`] and adds IP-range checks to prevent\n/// an attacker-controlled MCP server from directing Zed to fetch internal\n/// network resources via metadata URLs.\n///\n/// **Known limitation:** Domain-name URLs that resolve to private IPs are *not*\n/// blocked here — full mitigation requires resolver-level validation (e.g. a\n/// custom `Resolve` implementation). This function only blocks IP-literal URLs.\nfn validate_oauth_url(url: &Url) -> Result<()> {\n    require_https_or_loopback(url)?;","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/zed-industries/zed/blob/f4178619acd0d47ea1f76a2025c42962c6d6638c/crates/context_server/src/oauth.rs#L40-L76","documentation":"Zed's MCP OAuth client refuses to talk to any OAuth endpoint (metadata, authorization, token, or registration URL) that is not HTTPS. The only exception is plain http:// whose host is a loopback IPv4/IPv6 address or the literal 'localhost' domain, which the source carves out for local development. Any other scheme/host combination reaches the bail! with the offending scheme and host in the message. This is both a security requirement (tokens must not cross the network in cleartext) and an SSRF guard preceding the private-IP checks in validate_oauth_url.","triggerScenarios":"validate_oauth_url()/require_https_or_loopback() is called with a Url whose scheme is 'http' and whose host is not 127.0.0.0/8, ::1, or 'localhost' (e.g. http://192.168.1.50:8080/oauth/token), or whose scheme is neither http nor https (ws://, file://). In practice this happens when an MCP server's advertised WWW-Authenticate resource_metadata URL, Protected Resource Metadata authorization_servers entry, or auth-server metadata endpoints use http on a LAN host.","commonSituations":"Running a local MCP server bound to 0.0.0.0 or a LAN IP and referencing it by that IP instead of localhost; a reverse proxy terminating TLS but the backend advertising internal http:// URLs in its metadata document; a misconfigured authorization server whose issuer/token_endpoint are http; copy-pasting a server URL with http:// into the MCP settings while testing on another machine.","solutions":["Serve the OAuth endpoints over HTTPS (put the server behind a TLS reverse proxy or use a certificate via mkcert/letsencrypt) so every advertised endpoint URL starts with https://","If the server is truly local, reference it exactly as http://localhost:PORT or http://127.0.0.1:PORT — the loopback carve-out in require_https_or_loopbox accepts only these hosts","Fix the server's metadata documents so resource_metadata, authorization_servers, token_endpoint, and registration_endpoint all use the public https origin rather than internal http URLs","If you control the proxy, enable TLS pass-through or rewrite the advertised URLs in the Protected Resource Metadata JSON to their https public forms"],"exampleFix":"// before (server metadata advertises internal http)\n{ \"authorization_servers\": [\"http://10.0.0.5:9000\"] }\n\n// after (public https origin)\n{ \"authorization_servers\": [\"https://mcp.example.com\"] }","handlingStrategy":"validation","validationCode":"use url::Url;\n\nfn endpoint_acceptable(url: &Url) -> bool {\n    if url.scheme() == \"https\" {\n        return true;\n    }\n    if url.scheme() == \"http\" {\n        return matches!(\n            url.host(),\n            Some(url::Host::Ipv4(ip)) if ip.is_loopback()\n        ) || matches!(\n            url.host(),\n            Some(url::Host::Ipv6(ip)) if ip.is_loopback()\n        ) || matches!(\n            url.host(),\n            Some(url::Host::Domain(d)) if d.eq_ignore_ascii_case(\"localhost\")\n        );\n    }\n    false\n}\n\n// before starting the OAuth flow:\nlet endpoint = Url::parse(&configured)?;\nanyhow::ensure!(endpoint_acceptable(&endpoint),\n    \"endpoint {} will be rejected: needs https or loopback\", endpoint);","typeGuard":"fn is_https_or_loopback(url: &Url) -> bool {\n    url.scheme() == \"https\"\n        || (url.scheme() == \"http\"\n            && matches!(url.host(),\n                Some(url::Host::Ipv4(ip)) if ip.is_loopback()\n                | Some(url::Host::Ipv6(ip)) if ip.is_loopback()\n                | Some(url::Host::Domain(d)) if d.eq_ignore_ascii_case(\"localhost\")))\n}","tryCatchPattern":"match validate_oauth_url(&endpoint) {\n    Ok(()) => { /* proceed with discovery */ }\n    Err(err) if err.to_string().contains(\"must use HTTPS\") => {\n        // surface actionable UI: endpoint must be https or http://localhost\n        show_config_error(&endpoint, err);\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Standardize every advertised OAuth URL in your MCP server metadata on https origins","In automated test harnesses, always use http://127.0.0.1:PORT or http://localhost:PORT rather than LAN IPs or 0.0.0.0","Add a CI check that validates all metadata documents' endpoint URLs with the same scheme/host rules before deploy"],"tags":["oauth","mcp","ssrf","https","url-validation"],"backgroundTag":"oauth-endpoint-requires-https","analyzedSha":"f4178619acd0d47ea1f76a2025c42962c6d6638c","analyzedAt":"2026-08-20T19:29:52.058Z","contentChangedAt":"2026-08-20T19:29:52.058Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}