zed-industries/zed · error
OAuth endpoint must use HTTPS (got {}://{})
Error message
OAuth endpoint must use HTTPS (got {}://{}) What it means
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.
Source
Thrown at crates/context_server/src/oauth.rs:58
///
/// OAuth endpoints carry sensitive material (authorization codes, PKCE
/// verifiers, tokens) and must use TLS. Plain HTTP is only permitted for
/// loopback addresses, per RFC 8252 Section 8.3.
fn require_https_or_loopback(url: &Url) -> Result<()> {
if url.scheme() == "https" {
return Ok(());
}
if url.scheme() == "http" {
if let Some(host) = url.host() {
match host {
url::Host::Ipv4(ip) if ip.is_loopback() => return Ok(()),
url::Host::Ipv6(ip) if ip.is_loopback() => return Ok(()),
url::Host::Domain(d) if d.eq_ignore_ascii_case("localhost") => return Ok(()),
_ => {}
}
}
}
bail!(
"OAuth endpoint must use HTTPS (got {}://{})",
url.scheme(),
url.host_str().unwrap_or("?")
)
}
/// Validate that a URL is safe to use as an OAuth endpoint, including SSRF
/// 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)?;View on GitHub (pinned to f4178619ac)
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
Example fix
// before (server metadata advertises internal http)
{ "authorization_servers": ["http://10.0.0.5:9000"] }
// after (public https origin)
{ "authorization_servers": ["https://mcp.example.com"] } Defensive patterns
Strategy: validation
Validate before calling
use url::Url;
fn endpoint_acceptable(url: &Url) -> bool {
if url.scheme() == "https" {
return true;
}
if url.scheme() == "http" {
return matches!(
url.host(),
Some(url::Host::Ipv4(ip)) if ip.is_loopback()
) || matches!(
url.host(),
Some(url::Host::Ipv6(ip)) if ip.is_loopback()
) || matches!(
url.host(),
Some(url::Host::Domain(d)) if d.eq_ignore_ascii_case("localhost")
);
}
false
}
// before starting the OAuth flow:
let endpoint = Url::parse(&configured)?;
anyhow::ensure!(endpoint_acceptable(&endpoint),
"endpoint {} will be rejected: needs https or loopback", endpoint); Type guard
fn is_https_or_loopback(url: &Url) -> bool {
url.scheme() == "https"
|| (url.scheme() == "http"
&& matches!(url.host(),
Some(url::Host::Ipv4(ip)) if ip.is_loopback()
| Some(url::Host::Ipv6(ip)) if ip.is_loopback()
| Some(url::Host::Domain(d)) if d.eq_ignore_ascii_case("localhost")))
} Try / catch
match validate_oauth_url(&endpoint) {
Ok(()) => { /* proceed with discovery */ }
Err(err) if err.to_string().contains("must use HTTPS") => {
// surface actionable UI: endpoint must be https or http://localhost
show_config_error(&endpoint, err);
}
Err(err) => return Err(err),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- OAuth endpoint must not point to private/reserved IP: {}
- 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:
- Message id is undefined: ${JSON.stringify(message)}
AI-assisted analysis of zed-industries/zed@f4178619ac (2026-08-20).
Data as JSON: /api/errors/0c5139923df328c8.
Report an issue: GitHub.