{"record":{"id":"430b8c572890aaad","repo":"langchain-ai/langchain","slug":"dns-resolution-failed","errorCode":null,"errorMessage":"DNS resolution failed","messagePattern":"DNS resolution failed","errorType":"exception","errorClass":"SSRFBlockedError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/_security/_policy.py","lineNumber":272,"sourceCode":"    \"\"\"\n    parsed = urllib.parse.urlparse(url)\n    hostname = parsed.hostname or \"\"\n\n    validate_url_sync(url, policy)\n\n    allowed = {h.lower() for h in _effective_allowed_hosts(policy)}\n    if hostname.lower() in allowed:\n        return\n\n    scheme = (parsed.scheme or \"\").lower()\n    port = parsed.port or (443 if scheme == \"https\" else 80)\n    try:\n        addrinfo = await asyncio.to_thread(\n            socket.getaddrinfo, hostname, port, type=socket.SOCK_STREAM\n        )\n    except socket.gaierror as exc:\n        msg = \"DNS resolution failed\"\n        raise SSRFBlockedError(msg) from exc\n\n    for _family, _type, _proto, _canonname, sockaddr in addrinfo:\n        validate_resolved_ip(str(sockaddr[0]), policy)\n\n\ndef validate_url_sync(url: str, policy: SSRFPolicy = DEFAULT_SSRF_POLICY) -> None:\n    \"\"\"Synchronous URL validation (no DNS resolution).\n\n    Suitable for Pydantic validators and other sync contexts. Checks scheme\n    and hostname patterns only - use `validate_url` for full DNS-aware checking.\n\n    Raises:\n        SSRFBlockedError: If the URL violates the policy.\n    \"\"\"\n    parsed = urllib.parse.urlparse(url)\n\n    scheme = (parsed.scheme or \"\").lower()\n    if scheme not in policy.allowed_schemes:","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/_security/_policy.py#L254-L290","documentation":"Raised by the async `validate_url` when `socket.getaddrinfo` (run via `asyncio.to_thread`) fails with `socket.gaierror` while resolving the URL's hostname. The SSRF validator must resolve DNS to check the IP, so an unresolvable name is treated as a blocked URL (SSRFBlockedError, chained from the gaierror) rather than passed through — a fail-closed design.","triggerScenarios":"`await validate_url('https://nonexistent-host-xyz.example.com/path')` where DNS returns NXDOMAIN or times out (gaierror). Also transiently when the resolver is flaky, a VPN/captive portal blocks DNS, or the hostname only resolves via internal DNS unavailable to the process. The allowed-hosts short-circuit at the top means explicit `allowed_hosts` entries skip DNS entirely.","commonSituations":"CI runners without access to internal DNS; air-gapped or proxied environments; typos in base URLs in env vars; ephemeral hostnames from cloud previews that expired. Because the original gaierror is chained (`from exc`), the real cause is visible in the traceback's 'The above exception was the direct cause' section — always read it before assuming a policy block.","solutions":["Verify DNS from the same environment: `python -c \"import socket; print(socket.getaddrinfo('host', 443, type=socket.SOCK_STREAM))\"` — if this fails, fix resolver/VPN/network, not the policy.","Fix typos or stale hostnames in the URL/env var.","For internal-only names, add them to `allowed_hosts` (they then skip DNS validation by design).","Retry once on transient resolver failures if your fetch layer already handles retryable network errors."],"exampleFix":"# before\nawait validate_url('https://api.exmaple.com/v1', policy)  # typo -> DNS resolution failed\n\n# after\nawait validate_url('https://api.example.com/v1', policy)","handlingStrategy":"retry","validationCode":"import socket\n\ndef host_resolves(host: str) -> bool:\n    try:\n        socket.getaddrinfo(host, 443, type=socket.SOCK_STREAM)\n        return True\n    except socket.gaierror:\n        return False","typeGuard":null,"tryCatchPattern":"from langchain_core._security._policy import SSRFBlockedError\n\nfor attempt in range(2):\n    try:\n        await validate_url(url, policy)\n        break\n    except SSRFBlockedError as e:\n        if str(e) == \"DNS resolution failed\" and attempt == 0:\n            await asyncio.sleep(0.5)  # transient resolver failure: retry once\n            continue\n        raise  # policy blocks and persistent DNS failure are not retryable","preventionTips":["Pre-resolve hostnames from the runtime that will fetch (container/VPN DNS differs from your laptop).","Add known internal hostnames to allowed_hosts — they skip DNS validation by design.","Read the chained gaierror to tell NXDOMAIN (fix the name) from timeout (fix the network)."],"tags":["ssrf","dns","network","asyncio"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}