{"record":{"id":"dcf7572c566f8b4a","repo":"BerriAI/litellm","slug":"url-has-no-hostname","errorCode":null,"errorMessage":"URL has no hostname","messagePattern":"URL has no hostname","errorType":"exception","errorClass":"SSRFError","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/url_utils.py","lineNumber":269,"sourceCode":"        url: The user-supplied URL to validate.\n\n    Returns:\n        Tuple of (rewritten_url, host_header).\n        The rewritten URL has the hostname replaced with the validated IP.\n        The host_header value should be sent as the Host header.\n\n    Raises:\n        SSRFError: If the URL scheme is invalid or the hostname resolves\n            to a private/internal IP address.\n    \"\"\"\n    parsed: Final = urlparse(url)\n\n    if parsed.scheme not in _ALLOWED_SCHEMES:\n        raise SSRFError(f\"URL scheme '{parsed.scheme}' is not allowed\")\n\n    hostname: Final = parsed.hostname\n    if not hostname:\n        raise SSRFError(\"URL has no hostname\")\n\n    port: Final = parsed.port\n    default_port: Final = _default_port_for_scheme(parsed.scheme)\n    effective_port: Final = port if port is not None else default_port\n    host_header: Final = _format_host_header(hostname, effective_port, default_port)\n\n    is_allowlisted: Final = _is_host_allowlisted(hostname, effective_port)\n\n    # Resolve hostname and validate ALL addresses\n    try:\n        addrinfo: Final = socket.getaddrinfo(hostname, effective_port, proto=socket.IPPROTO_TCP)\n    except socket.gaierror as e:\n        raise SSRFError(f\"DNS resolution failed for '{hostname}': {e}\")\n\n    if not addrinfo:\n        raise SSRFError(f\"No addresses found for '{hostname}'\")\n\n    if not is_allowlisted:","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/url_utils.py#L251-L287","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Print/log the exact URL being validated (at your boundary) and fix the construction so the host is always present.","Default the host from config when the variable is empty, and fail request validation early if it cannot be resolved.","Add a pre-check: if not urlparse(url).hostname: reject before calling litellm."],"exampleFix":"# before\nurl = f\"{os.getenv('API_SCHEME')}://{os.getenv('API_HOST')}/v1\"\n# API_HOST unset -> \"https:///v1\"\n\n# after\nhost = os.environ[\"API_HOST\"]  # fail fast if missing\nurl = f\"https://{host}/v1\"","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\nif not urlparse(url).hostname:\n    raise ValueError(f\"URL is missing a hostname: {url!r}\")","typeGuard":"def url_has_host(url) -> bool:\n    try:\n        return bool(urlparse(url).hostname)\n    except Exception:\n        return False","tryCatchPattern":"try:\n    resp = safe_get(client, url)\nexcept SSRFError as e:\n    log.warning(\"URL rejected: %s\", e)\n    return bad_request(\"invalid target URL\")","preventionTips":["Unit-test your URL templates with empty host variables to catch 'https:///path' early.","Fail fast on unset host environment variables at startup.","Never build URLs by concatenating possibly-empty parts; validate the result with urlparse."],"tags":["ssrf","url","config","hostname","validation"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}