{"record":{"id":"41d263a34fed0aca","repo":"Graphify-Labs/graphify","slug":"ssrf-blocked-no-usable-address-resolved-from-ho","errorCode":null,"errorMessage":"SSRF blocked: no usable address resolved from '{host}'","messagePattern":"SSRF blocked: no usable address resolved from '(.+?)'","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"error","filePath":"graphify/security.py","lineNumber":177,"sourceCode":"    \"\"\"Resolve *host* once and return (family, validated_ip) for the first\n    address that is not in a blocked range.\n\n    Raises OSError if every resolved address is private/reserved/internal,\n    matching the failure mode urllib/http.client expect from connect().\n    \"\"\"\n    infos = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)\n    for family, _type, _proto, _canon, sockaddr in infos:\n        addr = sockaddr[0]\n        try:\n            ip = ipaddress.ip_address(addr)\n        except ValueError:\n            continue\n        if _ip_is_blocked(ip):\n            raise OSError(\n                f\"SSRF blocked: IP {addr} resolved from '{host}' is private/reserved\"\n            )\n        return family, addr\n    raise OSError(f\"SSRF blocked: no usable address resolved from '{host}'\")\n\n\nclass _SSRFGuardedHTTPConnection(http.client.HTTPConnection):\n    \"\"\"HTTPConnection that resolves + validates DNS once, then connects to the\n    exact validated IP (no second resolution = no DNS-rebind TOCTOU).\"\"\"\n\n    def connect(self) -> None:\n        family, ip = _resolve_and_validate(self.host, self.port)\n        self.sock = socket.create_connection(\n            (ip, self.port),\n            self.timeout,\n            self.source_address,\n        )\n        if self._tunnel_host:\n            self._tunnel()\n\n\nclass _SSRFGuardedHTTPSConnection(http.client.HTTPSConnection):","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/Graphify-Labs/graphify/blob/7fe58b0b0f3873be9a21c30106b8b8527c353aa6/graphify/security.py#L159-L195","documentation":"Raised by _resolve_and_validate in graphify/security.py when getaddrinfo returned addresses but none of them were usable: every candidate was either blocked as private/reserved or was not a parseable IP (ValueError in ip_address, which is skipped). It is the 'no acceptable address left' counterpart to the single-IP block error, and like it is an OSError matching connect() failure semantics.","triggerScenarios":"Calling safe_fetch/safe_fetch_text where a host resolves only to blocked ranges (e.g. a name with only 127.0.0.1 and ::1 A/AAAA records), or where getaddrinfo returns sockaddrs whose address strings ip_address cannot parse, so every entry is skipped and the loop falls through.","commonSituations":"Hostnames that are internal-only aliases resolving exclusively to RFC1918 space; hostnames resolving to non-IP sockaddr entries on unusual platform resolvers; typo'd host that a search-domain wildcard resolves to an internal catch-all address.","solutions":["Verify the host actually resolves to a public address: run socket.getaddrinfo(host, 443) and inspect the returned IPs.","If the host is intentionally internal, move the fetch outside safe_fetch to a transport you explicitly control.","Fix or bypass the DNS record / search domain that is mapping the name onto private addresses.","If you need graphify to fetch from an allow-listed internal mirror, request an explicit allowlist feature upstream instead of monkey-patching _ip_is_blocked."],"exampleFix":"# before\ntry:\n    data = safe_fetch(\"http://mirror.internal.example/pkg.json\")\nexcept OSError as e:\n    raise\n\n# after: diagnose what the name resolves to, use a public mirror or local file\nimport socket\nprint(socket.getaddrinfo(\"mirror.internal.example\", 80))\ndata = Path(\"pkg.json\").read_bytes()","handlingStrategy":"validation","validationCode":"def has_usable_address(host: str) -> bool:\n    \"\"\"True if at least one resolved address is a public unicast IP.\"\"\"\n    try:\n        infos = socket.getaddrinfo(host, 443, socket.AF_UNSPEC, socket.SOCK_STREAM)\n    except socket.gaierror:\n        return False\n    for *_rest, sockaddr in infos:\n        try:\n            ip = ipaddress.ip_address(sockaddr[0])\n        except ValueError:\n            continue\n        if not (ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved or ip.is_multicast):\n            return True\n    return False","typeGuard":"def is_fetchable_host(host: str) -> bool:\n    return has_usable_address(host)","tryCatchPattern":"try:\n    body = safe_fetch(url)\nexcept OSError as e:\n    if str(e).startswith(\"SSRF blocked: no usable address\"):\n        logger.warning(\"host %s has no public address; skipping\", host)\n        return None\n    raise","preventionTips":["Check DNS from the same environment the fetch runs in (containers may resolve differently).","Treat a name with only private records as a config bug, not a transient failure.","Log the failing host once at config-load time to catch bad hostnames early."],"tags":["network","ssrf","dns","security"],"backgroundTag":null,"analyzedSha":"7fe58b0b0f3873be9a21c30106b8b8527c353aa6","analyzedAt":"2026-08-14T19:23:21.323Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}