{"record":{"id":"af47e1d53fe454cc","repo":"Graphify-Labs/graphify","slug":"blocked-private-internal-ip-addr-resolved-from","errorCode":null,"errorMessage":"Blocked private/internal IP {addr} (resolved from '{hostname}'). Got: {url!r}","messagePattern":"Blocked private/internal IP (.+?) \\(resolved from '(.+?)'\\)\\. Got: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"graphify/security.py","lineNumber":134,"sourceCode":"        )\n\n    hostname = parsed.hostname\n    if hostname:\n        # Block known cloud metadata hostnames\n        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","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/Graphify-Labs/graphify/blob/7fe58b0b0f3873be9a21c30106b8b8527c353aa6/graphify/security.py#L116-L152","documentation":"ValueError from validate_url when the hostname resolves (via getaddrinfo, AF_UNSPEC) to an IP that _ip_is_blocked considers private/reserved - 127.x, 10.x, 192.168.x, 169.254.x, ::1, fc00::/7, etc. Tier three of the SSRF guard: the DNS answer is checked, so numeric-IP and DNS-rebinding tricks against private ranges are caught (single-resolution window notwithstanding).","triggerScenarios":"validate_url(url) where any resolved address fails _ip_is_blocked (security.py:127-136): e.g. http://10.0.0.5/x, a public name whose DNS points at 127.0.0.1, IPv6 ULA hosts, or hostnames whose A/AAAA records include a private IP among others.","commonSituations":"Legit internal tooling accidentally pointed at the SSRF-guarded fetcher (users expect to fetch intranet URLs); DNS rebinding attacks where a public name flips to a private IP; containers resolving service names to 10.x/172.x overlay networks; local development against http://127.0.0.1:8000.","solutions":["If you truly need internal fetches, use a dedicated HTTP client outside the guarded path - do not weaken validate_url.","For local development, exempt via an explicit, narrow configuration only if your threat model allows; otherwise test against a public staging URL.","If unexpected: treat as a possible DNS-rebinding probe - check what the hostname resolves to from the same box (getent hosts <name>)."],"exampleFix":"# before\nurl = 'http://intranet.corp.local/report'   # resolves to 10.1.2.3\nfetch(validate_url(url))                   # ValueError: Blocked private/internal IP\n\n# after - fetch internal resources with a client outside the SSRF-guarded path\nresp = requests_internal.get('http://intranet.corp.local/report', timeout=10)","handlingStrategy":"validation","validationCode":"import ipaddress, socket\nfrom urllib.parse import urlparse\n\ndef resolves_to_private(host: str) -> bool:\n    try:\n        infos = socket.getaddrinfo(host, None, socket.AF_UNSPEC, socket.SOCK_STREAM)\n        return any(\n            ipaddress.ip_address(i[4][0]).is_private or ipaddress.ip_address(i[4][0]).is_loopback\n            for i in infos\n        )\n    except socket.gaierror:\n        return False  # let validate_url produce the DNS error instead","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 \"private/internal IP\" in str(exc):\n        return bad_request(\"internal addresses are not fetchable\")\n    raise","preventionTips":["Never route intranet fetches through the public-facing URL fetcher; separate the trust boundaries.","Remember the check resolves DNS once - rebinding races remain possible; keep the SSRF-guarded connection classes (see the code below validate_url) in play.","Log blocked-IP events with the hostname for incident review."],"tags":["security","ssrf","dns","network","validation"],"backgroundTag":null,"analyzedSha":"7fe58b0b0f3873be9a21c30106b8b8527c353aa6","analyzedAt":"2026-08-14T19:23:21.323Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}