langchain-ai/langchain · error · SSRFBlockedError

DNS resolution failed

Error message

DNS resolution failed

What it means

The async SSRF-guard httpx transport (`libs/core/langchain_core/_security/_transport.py`, `handle_async_request`) resolves the request hostname via `socket.getaddrinfo` before connecting; a `socket.gaierror` becomes `SSRFBlockedError('DNS resolution failed')`. DNS failure is treated as a block so the request never proceeds with an unvalidated destination.

Source

Thrown at libs/core/langchain_core/_security/_transport.py:84

        validate_url_sync(str(request.url), self._policy)

        # Allowed-hosts bypass - skip DNS/IP validation entirely.
        allowed = {h.lower() for h in _effective_allowed_hosts(self._policy)}
        if hostname.lower() in allowed:
            return await self._inner.handle_async_request(request)

        # 4. DNS resolution
        port = request.url.port or (443 if scheme == "https" else 80)
        try:
            addrinfo = await asyncio.to_thread(
                socket.getaddrinfo,
                hostname,
                port,
                type=socket.SOCK_STREAM,
            )
        except socket.gaierror as exc:
            msg = "DNS resolution failed"
            raise SSRFBlockedError(msg) from exc

        if not addrinfo:
            msg = "DNS resolution returned no results"
            raise SSRFBlockedError(msg)

        # 5. Validate ALL resolved IPs - any blocked means reject.
        for _family, _type, _proto, _canonname, sockaddr in addrinfo:
            ip_str: str = sockaddr[0]  # type: ignore[assignment]
            validate_resolved_ip(ip_str, self._policy)

        # 6. Pin to first resolved IP.
        pinned_ip = addrinfo[0][4][0]

        # 7. Rewrite URL to use pinned IP, preserving Host header and SNI.
        pinned_url = request.url.copy_with(host=pinned_ip)

        # Build extensions dict, adding sni_hostname for HTTPS so TLS
        # certificate validation uses the original hostname.

View on GitHub (pinned to e32fa9a52e)

Solutions

  1. Confirm the hostname resolves from the same runtime: `python -c "import socket; socket.getaddrinfo('host', 443)"`
  2. Fix or fully qualify the target hostname / use an IP literal if policy allows
  3. Repair container DNS configuration (resolv.conf, k8s CoreDNS, docker --dns)
  4. If the host is a trusted internal allow-listed name, add it to the transport's allowed-hostnames set so it bypasses resolution-based checks
Defensive patterns

Strategy: try-catch

Validate before calling

import socket

def resolves(host: str, port: int) -> bool:
    try:
        return bool(socket.getaddrinfo(host, port, type=socket.SOCK_STREAM))
    except socket.gaierror:
        return False

Try / catch

try:
    resp = await client.get(url)
except SSRFBlockedError as e:
    if 'DNS resolution failed' in str(e):
        logger.warning('unresolvable target %s', url)
        return None
    raise

Prevention

When it happens

Trigger: Issuing a request through an httpx client configured with the SSRF-guard async transport where the URL hostname does not resolve (NXDOMAIN, SERVFAIL, no resolver). Affects GETs to mistyped domains or hostnames only resolvable inside another network.

Common situations: Offline CI; containers with broken DNS; internal hostnames in one environment used in another; testing SSRF protection itself with dead hostnames.

Understand the failure class

Related errors


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