{"record":{"id":"8c842bf824efa51c","repo":"Graphify-Labs/graphify","slug":"dns-resolution-failed-for-hostname-exc-got","errorCode":null,"errorMessage":"DNS resolution failed for '{hostname}': {exc}. Got: {url!r}","messagePattern":"DNS resolution failed for '(.+?)': (.+?)\\. Got: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"graphify/security.py","lineNumber":139,"sourceCode":"        if hostname.lower() in _BLOCKED_HOSTS:\n            raise ValueError(\n                f\"Blocked cloud metadata endpoint '{hostname}'. \"\n                f\"Got: {url!r}\"\n            )\n\n        # Resolve hostname and block private/reserved IP ranges\n        try:\n            infos = socket.getaddrinfo(hostname, None, socket.AF_UNSPEC, socket.SOCK_STREAM)\n            for info in infos:\n                addr = info[4][0]\n                ip = ipaddress.ip_address(addr)\n                if _ip_is_blocked(ip):\n                    raise ValueError(\n                        f\"Blocked private/internal IP {addr} (resolved from '{hostname}'). \"\n                        f\"Got: {url!r}\"\n                    )\n        except socket.gaierror as exc:\n            raise ValueError(\n                f\"DNS resolution failed for '{hostname}': {exc}. Got: {url!r}\"\n            ) from exc\n\n    return url\n\n\n# ---------------------------------------------------------------------------\n# SSRF-guarded connections\n#\n# Instead of monkey-patching the process-global socket.getaddrinfo (a\n# non-thread-safe TOCTOU hazard when multiple fetches run concurrently),\n# we subclass the HTTP(S) connection so each connection resolves DNS exactly\n# once, validates the resulting IP, and then connects to that exact IP. There\n# is no second resolution, so a DNS-rebind attack cannot swap in a private\n# address (e.g. 169.254.169.254) between validation and connection.\n# ---------------------------------------------------------------------------\n\n","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/Graphify-Labs/graphify/blob/7fe58b0b0f3873be9a21c30106b8b8527c353aa6/graphify/security.py#L121-L157","documentation":"ValueError from validate_url when socket.getaddrinfo raises gaierror - the hostname does not resolve at all. Wrapped with `from exc` so the underlying resolver error (NXDOMAIN, timeout) stays in the chain. This is DNS failure rather than a policy block: the guard tries to check the IP but cannot get one.","triggerScenarios":"validate_url(url) where the hostname fails DNS resolution from the machine running graphify (security.py:137-140): typo'd domains, split-horizon DNS where the name only exists internally, resolver outages, air-gapped CI, or .local mDNS names the resolver refuses.","commonSituations":"CI runners without VPN access fetching internal-only hostnames; typos like 'exmaple.com'; IPv6-only DNS quirks; systemd-resolved stub issues in containers; domains that exist publicly but not from restrictive resolvers.","solutions":["Check the name resolves where graphify runs: `getent hosts <hostname>` or `python -c \"import socket; print(socket.getaddrinfo('<hostname>', None))\"`.","Fix typos / use the canonical hostname.","If it is an internal name, get on the right network/VPN or fix search-domain (resolv.conf search=) configuration.","If DNS is flaky, retry - resolver timeouts masquerade as this error."],"exampleFix":"# before\nurl = 'https://api.exmaple.com/v1'   # typo\nfetch(validate_url(url))             # ValueError: DNS resolution failed for 'api.exmaple.com'\n\n# after\nurl = 'https://api.example.com/v1'\nfetch(validate_url(url))","handlingStrategy":"validation","validationCode":"import socket\nfrom urllib.parse import urlparse\n\nhost = urlparse(url).hostname or \"\"\ntry:\n    socket.getaddrinfo(host, None)\nexcept socket.gaierror:\n    raise SystemExit(f\"{host} does not resolve from this machine - fix DNS/VPN/name\")","typeGuard":"def is_safe_url(url: str) -> bool:\n    try:\n        validate_url(url)\n        return True\n    except ValueError:\n        return False","tryCatchPattern":"try:\n    safe = validate_url(url)\nexcept ValueError as exc:\n    if \"DNS resolution failed\" in str(exc):\n        return bad_request(\"hostname does not resolve\")  # user-fixable, 4xx\n    if \"Blocked\" in str(exc):\n        return forbidden(str(exc))                        # policy, audit it\n    raise","preventionTips":["Preflight-resolve hostnames in config at deploy time so DNS drift is caught before runtime.","In CI, assert VPN/peering with a getaddrinfo smoke test before URL-dependent jobs.","Distinguish DNS failure from SSRF blocks in handlers - one is a 4xx user error, the other deserves an audit log."],"tags":["dns","network","url","validation","environment"],"backgroundTag":null,"analyzedSha":"7fe58b0b0f3873be9a21c30106b8b8527c353aa6","analyzedAt":"2026-08-14T19:23:21.323Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}