{"record":{"id":"1a5002899fc48f0a","repo":"BerriAI/litellm","slug":"getaddrinfo-returned-non-string-host-host-r","errorCode":null,"errorMessage":"getaddrinfo returned non-string host: {host!r}","messagePattern":"getaddrinfo returned non-string host: (.+?)","errorType":"exception","errorClass":"SSRFError","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/url_utils.py","lineNumber":217,"sourceCode":"    bracketed: Final = f\"[{hostname}]\" if \":\" in hostname else hostname\n    if port == default_port:\n        return bracketed\n    return f\"{bracketed}:{port}\"\n\n\ndef _sockaddr_host(sockaddr: Any) -> str:\n    \"\"\"Return the host element of a ``getaddrinfo`` sockaddr as ``str``.\n\n    ``getaddrinfo`` with ``IPPROTO_TCP`` returns AF_INET / AF_INET6 sockaddrs\n    whose first element is always a host string. mypy types it as\n    ``str | int`` (since sockaddrs for other families can hold ints), so we\n    narrow at the boundary. Fail closed if the stdlib ever returns something\n    unexpected — a non-string here would mean we have no IP to check against\n    the SSRF blocklist.\n    \"\"\"\n    host: Final = sockaddr[0]\n    if not isinstance(host, str):\n        raise SSRFError(f\"getaddrinfo returned non-string host: {host!r}\")\n    return host\n\n\ndef _is_host_allowlisted(hostname: str, effective_port: int) -> bool:\n    \"\"\"Check whether a host is in the admin-configured allowlist.\n\n    Admin entries may be ``hostname`` (any port) or ``hostname:port``. IPv6\n    literals are written bracketed (``[::1]`` / ``[::1]:8080``). Matching\n    is case-insensitive on the hostname.\n    \"\"\"\n    configured: Final[list[str]] = getattr(litellm, \"user_url_allowed_hosts\", []) or []\n    if not configured:\n        return False\n    normalized_host: Final = _normalize_host(hostname)\n    host_repr: Final = f\"[{normalized_host}]\" if \":\" in normalized_host else normalized_host\n    candidates: Final[set[str]] = {host_repr, f\"{host_repr}:{effective_port}\"}\n    allowlist: Final[set[str]] = {_normalize_host(entry) for entry in configured if entry}\n    return bool(candidates & allowlist)","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/url_utils.py#L199-L235","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","If it occurs in production, inspect the resolver environment (custom resolvers, LD_PRELOAD, patched socket) — stock CPython cannot produce this.","Report it upstream to litellm with the platform and Python version, since it indicates stdlib behavior outside the documented contract."],"exampleFix":"# before (broken test mock)\nsock.getaddrinfo = lambda *a, **k: [(2, 1, 6, '', (80, 1))]  # port first, int host\n\n# after\nsock.getaddrinfo = lambda *a, **k: [(2, 1, 6, '', ('93.184.216.34', 80))]","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def is_inet_sockaddr(sockaddr) -> bool:\n    return isinstance(sockaddr, tuple) and len(sockaddr) >= 1 and isinstance(sockaddr[0], str)","tryCatchPattern":"from litellm.litellm_core_utils.url_utils import SSRFError\n\ntry:\n    validate_url(url)\nexcept SSRFError:\n    raise  # surface to caller","preventionTips":["In tests, mock getaddrinfo with well-formed AF_INET/AF_INET6 sockaddr tuples (host string first).","Avoid monkeypatching socket.getaddrinfo in production code paths.","Treat this error as an environment/mock bug signal, not a user-input problem."],"tags":["ssrf","dns","socket","type-narrowing","internal-guard"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}