BerriAI/litellm · critical · SSRFError

URL targets a blocked address ({resolved_ip}). If this is a

Error message

URL targets a blocked address ({resolved_ip}). If this is a legitimate internal service, add the host to `user_url_allowed_hosts` in general_settings.

What it means

The core SSRF block from litellm's validate_url: after DNS resolution, every resolved IP is checked with _is_blocked_ip, which blocks any non-global IP (RFC 6890: private, loopback, link-local, CGNAT), multicast, unparseable addresses, IPv4-mapped IPv6 forms, and cloud metadata ranges (e.g. Azure Wire Server 168.63.129.16). If any resolved address is blocked, fetching is refused unless the host:port was admin-allowlisted via user_url_allowed_hosts in general_settings. The message deliberately includes the remediation path.

Source

Thrown at litellm/litellm_core_utils/url_utils.py:291

    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:
        for family, type_, proto, canonname, sockaddr in addrinfo:
            resolved_ip = _sockaddr_host(sockaddr)
            if _is_blocked_ip(resolved_ip):
                raise SSRFError(
                    f"URL targets a blocked address ({resolved_ip}). "
                    "If this is a legitimate internal service, add the host "
                    "to `user_url_allowed_hosts` in general_settings."
                )

    # For HTTPS with SSL verification enabled, TLS certificate validation
    # binds the connection to the hostname — DNS rebinding can't redirect
    # to a different server because the cert wouldn't match.
    # When SSL verification is disabled, this defense doesn't apply, so
    # we rewrite to the validated IP like HTTP.
    ssl_verify: Final = getattr(litellm, "ssl_verify", True)
    if parsed.scheme == "https" and ssl_verify is not False:
        return url, host_header

    # For HTTP, rewrite URL to connect to the validated IP directly
    # to prevent DNS rebinding (no TLS to bind the connection).
    validated_ip: Final = _sockaddr_host(addrinfo[0][4])
    is_ipv6: Final = addrinfo[0][0] == socket.AF_INET6

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. If the target is a legitimate internal service, ask the proxy admin to add the exact host (optionally host:port, e.g. 'internal-api.corp:8080') to user_url_allowed_hosts under general_settings in the litellm config, then restart the proxy.
  2. If you did not expect an internal IP, investigate the DNS name — it may be a DNS rebinding attempt or a misconfigured record.
  3. Point the URL at a globally routable endpoint instead (public hostname or correct external IP).
  4. Admins: remember allowlisting skips IP checks for that host, so only allowlist hosts you control and trust.

Example fix

# before
response = safe_get(client, "http://internal-llm.corp.local:8080/health")
# SSRFError: URL targets a blocked address (10.1.2.3)...

# after: litellm_config.yaml
# general_settings:
#   user_url_allowed_hosts:
#     - "internal-llm.corp.local:8080"
response = safe_get(client, "http://internal-llm.corp.local:8080/health")
Defensive patterns

Strategy: try-catch

Validate before calling

import socket
from ipaddress import ip_address

def all_ips_global(hostname: str) -> bool:
    try:
        infos = socket.getaddrinfo(hostname, 443, proto=socket.IPPROTO_TCP)
    except socket.gaierror:
        return False
    ips = [i[4][0] for i in infos]
    return all(_is_public(ip) for ip in ips)

def _is_public(addr: str) -> bool:
    try:
        ip = ip_address(addr)
    except ValueError:
        return False
    return ip.is_global and not ip.is_multicast

Try / catch

from litellm.litellm_core_utils.url_utils import SSRFError

try:
    resp = safe_get(client, url)
except SSRFError as e:
    if "blocked address" in str(e):
        return bad_request("target is not reachable under SSRF policy; ask the admin to allowlist it")
    raise

Prevention

When it happens

Trigger: validate_url/safe_get on a URL whose hostname resolves to a private/internal IP — e.g. http://localhost:8080/fetch, http://169.254.169.254/latest/meta-data (cloud metadata), http://10.0.0.5:9000, or a public-looking DNS name that resolves (round-robin or rebinding) to an internal address. Also triggered when a hostname resolves to multiple addresses and ANY one of them is non-global.

Common situations: Legitimate internal services (self-hosted models, internal file/media hosts) referenced by user-supplied URL fields in the proxy; developers testing locally against 127.0.0.1; multi-A records where one record is private; deployments where the 'user URL' feature is exposed to end users who request internal targets (the actual attack this guards against).

Related errors


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