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
- 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.
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
- 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.
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
- {field_name} cannot be a dot path segment
- URL scheme '{parsed.scheme}' is not allowed
- Event hook {hook} is not in the supported event hooks {suppo
- Event hook {event_hook} is not in the supported event hooks
- Invalid environment: {environment}. Please use one of the fo
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/dcf7572c566f8b4a.
Report an issue: GitHub.