BerriAI/litellm · error · SSRFError

URL has no hostname

Error message

URL has no hostname

What it means

Raised by litellm's SSRF validator when the URL passes the scheme check but urlparse finds no hostname — meaning the URL is scheme-only or malformed (e.g. 'https://', 'https:///path', 'https://:8443'). Without a hostname there is nothing to DNS-resolve and check against the blocklist, so validation fails closed with SSRFError. It almost always indicates a URL-construction bug in the caller rather than an attacker.

Source

Thrown at litellm/litellm_core_utils/url_utils.py:269

        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}")

    if not addrinfo:
        raise SSRFError(f"No addresses found for '{hostname}'")

    if not is_allowlisted:

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Print/log the exact URL being validated (at your boundary) and fix the construction so the host is always present.
  2. Default the host from config when the variable is empty, and fail request validation early if it cannot be resolved.
  3. Add a pre-check: if not urlparse(url).hostname: reject before calling litellm.

Example fix

# before
url = f"{os.getenv('API_SCHEME')}://{os.getenv('API_HOST')}/v1"
# API_HOST unset -> "https:///v1"

# after
host = os.environ["API_HOST"]  # fail fast if missing
url = f"https://{host}/v1"
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

if not urlparse(url).hostname:
    raise ValueError(f"URL is missing a hostname: {url!r}")

Type guard

def url_has_host(url) -> bool:
    try:
        return bool(urlparse(url).hostname)
    except Exception:
        return False

Try / catch

try:
    resp = safe_get(client, url)
except SSRFError as e:
    log.warning("URL rejected: %s", e)
    return bad_request("invalid target URL")

Prevention

When it happens

Trigger: Calling validate_url('https://') or a dynamically built URL where the host variable is empty — e.g. f"{scheme}://{host}/v1/chat" with host=""; also URLs like 'http:///health' where the authority component is missing.

Common situations: api_base config assembled from environment variables where the host part is unset; string templates that interpolate an empty string for the host; typos like an extra '/' after the scheme; YAML config where api_base lost its host during templating.

Related errors


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