BerriAI/litellm · error · SSRFError

Origin mismatch on port

Error message

Origin mismatch on port

What it means

Raised during litellm's SSRF-protected fetch when a redirect target keeps the same scheme and host but changes the effective port (explicit port or scheme-default difference, e.g. https://host:8443 -> https://host:443). Port is part of the origin contract enforced on every redirect hop, so any port change is rejected before the hop is fetched. The specific ports are not included in the message.

Source

Thrown at litellm/litellm_core_utils/url_utils.py:360

    candidate: Final = urlparse(candidate_url)
    expected: Final = urlparse(expected_url)

    if candidate.scheme not in _ALLOWED_SCHEMES:
        raise SSRFError("URL scheme is not allowed")

    if candidate.scheme != expected.scheme:
        raise SSRFError("Origin mismatch on scheme")

    candidate_host: Final = _normalize_host(candidate.hostname or "")
    expected_host: Final = _normalize_host(expected.hostname or "")
    if not candidate_host or candidate_host != expected_host:
        raise SSRFError("Origin mismatch on host")

    default_port: Final = 443 if candidate.scheme == "https" else 80
    candidate_port: Final = candidate.port if candidate.port is not None else default_port
    expected_port: Final = expected.port if expected.port is not None else default_port
    if candidate_port != expected_port:
        raise SSRFError("Origin mismatch on port")


_MAX_REDIRECTS: Final = 10


def _extract_redirect_url(response: Any, request_url: str) -> str:
    """Extract and resolve the redirect target from a response's Location header."""
    location: Final = response.headers.get("location")
    if not isinstance(location, str) or not location:
        raise SSRFError("Redirect response has no Location header")
    # Resolve relative URLs against the request URL
    return str(httpx.URL(request_url).join(location))


def safe_get(client: Any, url: str, **kwargs: Any) -> Any:
    """
    Fetch a user-supplied URL with SSRF protection on every redirect hop.

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Call safe_get directly on the canonical final URL (the redirect target's port) so no port-changing hop occurs.
  2. Fix the origin server to keep Location on the same port as the requested URL.
  3. If the port change is legitimate, perform two independent safe_get calls — one per origin — instead of relying on the redirect.

Example fix

# before
resp = safe_get(client, "https://api.example.com:8443/v1/file")  # 301 -> :443
# SSRFError: Origin mismatch on port

# after
resp = safe_get(client, "https://api.example.com/v1/file")
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def canonical_origin_url(url: str) -> str:
    # ensure the port you pass matches the port the service canonicalizes to
    return url  # document/verify the canonical port before calling safe_get

Try / catch

from litellm.litellm_core_utils.url_utils import SSRFError

try:
    resp = safe_get(client, url)
except SSRFError as e:
    if "Origin mismatch on port" in str(e):
        return bad_request("redirect changes port; request the canonical port directly")
    raise

Prevention

When it happens

Trigger: safe_get('https://api.example.com:8443/x') where the server redirects to 'https://api.example.com/x' (port implicitly 443), or any 3xx whose Location carries a different explicit port — candidate_port != expected_port in _validate_same_origin.

Common situations: Services behind a non-standard port redirecting to their canonical 443 URL (or vice versa); internal tools on :8080 redirecting to a login page on :443; proxies that rewrite ports in Location headers.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/afbfc2822c3a705b. Report an issue: GitHub.