BerriAI/litellm · error · SSRFError

getaddrinfo returned non-string host: {host!r}

Error message

getaddrinfo returned non-string host: {host!r}

What it means

Raised by _sockaddr_host during litellm's SSRF validation when a getaddrinfo result's sockaddr does not start with a string host. mypy types sockaddr[0] as str | int because some address families carry ints, and this boundary check narrows the type; a non-string would mean there is no IP to check against the SSRF blocklist, so it fails closed with SSRFError. In practice this should never fire with CPython's IPPROTO_TCP getaddrinfo — it guards against stdlib changes or exotic platforms.

Source

Thrown at litellm/litellm_core_utils/url_utils.py:217

    bracketed: Final = f"[{hostname}]" if ":" in hostname else hostname
    if port == default_port:
        return bracketed
    return f"{bracketed}:{port}"


def _sockaddr_host(sockaddr: Any) -> str:
    """Return the host element of a ``getaddrinfo`` sockaddr as ``str``.

    ``getaddrinfo`` with ``IPPROTO_TCP`` returns AF_INET / AF_INET6 sockaddrs
    whose first element is always a host string. mypy types it as
    ``str | int`` (since sockaddrs for other families can hold ints), so we
    narrow at the boundary. Fail closed if the stdlib ever returns something
    unexpected — a non-string here would mean we have no IP to check against
    the SSRF blocklist.
    """
    host: Final = sockaddr[0]
    if not isinstance(host, str):
        raise SSRFError(f"getaddrinfo returned non-string host: {host!r}")
    return host


def _is_host_allowlisted(hostname: str, effective_port: int) -> bool:
    """Check whether a host is in the admin-configured allowlist.

    Admin entries may be ``hostname`` (any port) or ``hostname:port``. IPv6
    literals are written bracketed (``[::1]`` / ``[::1]:8080``). Matching
    is case-insensitive on the hostname.
    """
    configured: Final[list[str]] = getattr(litellm, "user_url_allowed_hosts", []) or []
    if not configured:
        return False
    normalized_host: Final = _normalize_host(hostname)
    host_repr: Final = f"[{normalized_host}]" if ":" in normalized_host else normalized_host
    candidates: Final[set[str]] = {host_repr, f"{host_repr}:{effective_port}"}
    allowlist: Final[set[str]] = {_normalize_host(entry) for entry in configured if entry}
    return bool(candidates & allowlist)

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. If you see this in tests, fix the mocked getaddrinfo to return proper (family, type, proto, canonname, (host, port)) tuples for AF_INET/AF_INET6.
  2. If it occurs in production, inspect the resolver environment (custom resolvers, LD_PRELOAD, patched socket) — stock CPython cannot produce this.
  3. Report it upstream to litellm with the platform and Python version, since it indicates stdlib behavior outside the documented contract.

Example fix

# before (broken test mock)
sock.getaddrinfo = lambda *a, **k: [(2, 1, 6, '', (80, 1))]  # port first, int host

# after
sock.getaddrinfo = lambda *a, **k: [(2, 1, 6, '', ('93.184.216.34', 80))]
Defensive patterns

Strategy: try-catch

Type guard

def is_inet_sockaddr(sockaddr) -> bool:
    return isinstance(sockaddr, tuple) and len(sockaddr) >= 1 and isinstance(sockaddr[0], str)

Try / catch

from litellm.litellm_core_utils.url_utils import SSRFError

try:
    validate_url(url)
except SSRFError:
    raise  # surface to caller

Prevention

When it happens

Trigger: validate_url() on a user-supplied URL whose DNS resolution returns an AF family whose sockaddr's first element is an int (non-INET/INET6 family from a patched or non-standard resolver). Essentially unreachable on stock CPython; realistic only with monkeypatched socket.getaddrinfo, exotic AI_* families, or a platform quirk.

Common situations: Test suites that mock/monkeypatch socket.getaddrinfo with malformed sockaddr tuples; running under alternative Python implementations or LD_PRELOAD'd resolvers; bugs in custom DNS shims injected into the process.

Understand the failure class

Related errors


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