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
- Add the dev hostname to `allowed_hosts`, or run under `LANGCHAIN_ENV=local...` so `_effective_allowed_hosts` automatically admits 'localhost' and 'testserver'.
- Address the service via a non-loopback interface or container hostname that is explicitly allowed.
- 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
- Use LANGCHAIN_ENV=local (or add localhost to allowed_hosts) in dev; keep defaults strict in prod.
- Address dev services via explicitly allowed hostnames, not raw loopback IPs, so policy intent stays auditable.
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.