langchain-ai/langchain · error · SSRFBlockedError

localhost address

Error message

localhost address

What it means

`"localhost address"` is the reason returned when the resolved IP equals or falls in the loopback range (127.0.0.0/8, ::1) and `block_localhost` is enabled — reported through the shared `raise SSRFBlockedError(reason)` in `validate_resolved_ip`. This blocks the classic SSRF vector of pointing a fetched URL at the machine's own loopback interface, where local-only services (metadata agents, admin ports) listen.

Source

Thrown at libs/core/langchain_core/_security/_policy.py:212

def validate_resolved_ip(ip_str: str, policy: SSRFPolicy) -> None:
    """Validate a resolved IP address against the SSRF policy.

    Raises SSRFBlockedError if the IP is blocked.
    """
    try:
        addr = ipaddress.ip_address(ip_str)
    except ValueError as exc:
        msg = "invalid IP address"
        raise SSRFBlockedError(msg) from exc

    if isinstance(addr, ipaddress.IPv6Address):
        inner = _extract_embedded_ipv4(addr)
        if inner is not None:
            addr = inner

    reason = _ip_in_blocked_networks(addr, policy)
    if reason is not None:
        raise SSRFBlockedError(reason)


def validate_hostname(hostname: str, policy: SSRFPolicy) -> None:
    """Validate a hostname against the SSRF policy.

    Raises SSRFBlockedError if the hostname is blocked.
    """
    lower = hostname.lower()

    if policy.block_localhost and lower in _LOCALHOST_NAMES:
        msg = "localhost address"
        raise SSRFBlockedError(msg)

    if policy.block_cloud_metadata and lower in _CLOUD_METADATA_HOSTNAMES:
        msg = "cloud metadata endpoint"
        raise SSRFBlockedError(msg)

    if policy.block_k8s_internal and lower.endswith(_K8S_SUFFIX):

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Add the dev hostname to `allowed_hosts`, or run under `LANGCHAIN_ENV=local...` so `_effective_allowed_hosts` automatically admits 'localhost' and 'testserver'.
  2. Address the service via a non-loopback interface or container hostname that is explicitly allowed.
  3. Keep the block on for any user-supplied URL — allowlisting loopback globally in production is a real SSRF exposure.

Example fix

# before
await validate_url('http://localhost:3000/data', DEFAULT_SSRF_POLICY)
# SSRFBlockedError: localhost address

# after (local dev)
import os
os.environ['LANGCHAIN_ENV'] = 'local'
await validate_url('http://localhost:3000/data', DEFAULT_SSRF_POLICY)
Defensive patterns

Strategy: try-catch

Validate before calling

import socket, ipaddress

def resolves_to_loopback(host: str) -> bool:
    try:
        ip = ipaddress.ip_address(host)
        return ip.is_loopback
    except ValueError:
        infos = socket.getaddrinfo(host, 443, type=socket.SOCK_STREAM)
        return all(ipaddress.ip_address(i[4][0]).is_loopback for i in infos)

Try / catch

from langchain_core._security._policy import SSRFBlockedError

try:
    await validate_url(url, policy)
except SSRFBlockedError as e:
    if "localhost address" in str(e) and env == "dev":
        return await validate_url(url, dev_policy)  # LANGCHAIN_ENV=local allowlist
    raise

Prevention

When it happens

Trigger: `await validate_url('http://127.0.0.1:8000/health')` or a hostname resolving to 127.0.0.1 under a policy with `block_localhost=True`. Note the hostname-level twin at line ~224: `validate_hostname` raises the same message for literal names in `_LOCALHOST_NAMES` (e.g. 'localhost') before DNS even runs.

Common situations: Local development pointing a web loader or custom tool at a dev server (`http://localhost:3000`), or in CI where services are addressed on loopback. Also triggered by attacker-supplied URLs in agent workflows that try `http://localhost/` to reach the host's internal services — which is exactly what the guard is for.

Related errors


AI-assisted analysis of langchain-ai/langchain@e32fa9a52e (2026-08-14). Data as JSON: /api/errors/3b67674d7909dd48. Report an issue: GitHub.