BerriAI/litellm · error · SSRFError

URL scheme '{parsed.scheme}' is not allowed

Error message

URL scheme '{parsed.scheme}' is not allowed

What it means

Raised by litellm's SSRF validator (validate_url) when a user-supplied URL's scheme is not in the allowed set (http/https only, per _ALLOWED_SCHEMES). Before doing any DNS resolution or IP validation, litellm restricts fetchable URLs to plain web schemes, so schemes like file://, ftp://, gopher://, or javascript:// are rejected outright. This is a security control protecting the proxy from being used to reach non-web resources.

Source

Thrown at litellm/litellm_core_utils/url_utils.py:265

    validated, not the hostname that could rebind. Callers should also disable
    follow_redirects to prevent redirect-based SSRF bypasses.

    Args:
        url: The user-supplied URL to validate.

    Returns:
        Tuple of (rewritten_url, host_header).
        The rewritten URL has the hostname replaced with the validated IP.
        The host_header value should be sent as the Host header.

    Raises:
        SSRFError: If the URL scheme is invalid or the hostname resolves
            to a private/internal IP address.
    """
    parsed: Final = urlparse(url)

    if parsed.scheme not in _ALLOWED_SCHEMES:
        raise SSRFError(f"URL scheme '{parsed.scheme}' is not allowed")

    hostname: Final = parsed.hostname
    if not hostname:
        raise SSRFError("URL has no hostname")

    port: Final = parsed.port
    default_port: Final = _default_port_for_scheme(parsed.scheme)
    effective_port: Final = port if port is not None else default_port
    host_header: Final = _format_host_header(hostname, effective_port, default_port)

    is_allowlisted: Final = _is_host_allowlisted(hostname, effective_port)

    # Resolve hostname and validate ALL addresses
    try:
        addrinfo: Final = socket.getaddrinfo(hostname, effective_port, proto=socket.IPPROTO_TCP)
    except socket.gaierror as e:
        raise SSRFError(f"DNS resolution failed for '{hostname}': {e}")

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Change the URL to use http:// or https://.
  2. If the scheme came from user input, validate/normalize it before passing it into litellm (urlparse(url).scheme in {'http','https'}).
  3. For WebSocket endpoints, check whether the integration expects the http(s) upgrade form rather than ws://.
  4. Strip or quote a leading path like 'C:\...' that is being misparsed as a scheme.

Example fix

# before
resp = safe_get(client, "ftp://files.example.com/data.csv")

# after
resp = safe_get(client, "https://files.example.com/data.csv")
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def is_http_url(url: str) -> bool:
    return urlparse(url).scheme in {"http", "https"} and bool(urlparse(url).hostname)

Type guard

def is_http_url(url) -> bool:
    try:
        p = urlparse(url)
    except Exception:
        return False
    return p.scheme in {"http", "https"}

Try / catch

from litellm.litellm_core_utils.url_utils import SSRFError

try:
    resp = safe_get(client, url)
except SSRFError as e:
    return bad_request(f"URL rejected: {e}")

Prevention

When it happens

Trigger: Calling validate_url / safe_get / async_safe_get with a URL whose scheme is anything other than http or https — e.g. passing 'file:///etc/passwd', 'ftp://host/file', or a malformed URL where urlparse extracts an unexpected scheme (e.g. 'gopher://127.0.0.1:70').

Common situations: Users configuring api_base or a user-supplied URL field (e.g. file fetch, media URL, MCP/OAuth callback) with a non-HTTP scheme; accidentally including a Windows drive letter ('C:\path') that urlparse reads as scheme 'c'; copy-paste of URLs with custom schemes like 'unix://' or 'ws://'.

Related errors


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