Graphify-Labs/graphify · error · OSError
SSRF blocked: IP {addr} resolved from '{host}' is private/re
Error message
SSRF blocked: IP {addr} resolved from '{host}' is private/reserved What it means
Raised by graphify's SSRF guard (_resolve_and_validate in graphify/security.py) when a hostname passed to safe_fetch/safe_fetch_text resolves to an IP that is private, loopback, link-local, or otherwise reserved. The library enforces this before opening any connection so that fetched URLs cannot reach internal network targets. It surfaces as an OSError to mimic a failed connect(), which urllib/http.client callers already expect.
Source
Thrown at graphify/security.py:173
# ---------------------------------------------------------------------------
def _resolve_and_validate(host: str, port: int) -> tuple[int, str]:
"""Resolve *host* once and return (family, validated_ip) for the first
address that is not in a blocked range.
Raises OSError if every resolved address is private/reserved/internal,
matching the failure mode urllib/http.client expect from connect().
"""
infos = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
for family, _type, _proto, _canon, sockaddr in infos:
addr = sockaddr[0]
try:
ip = ipaddress.ip_address(addr)
except ValueError:
continue
if _ip_is_blocked(ip):
raise OSError(
f"SSRF blocked: IP {addr} resolved from '{host}' is private/reserved"
)
return family, addr
raise OSError(f"SSRF blocked: no usable address resolved from '{host}'")
class _SSRFGuardedHTTPConnection(http.client.HTTPConnection):
"""HTTPConnection that resolves + validates DNS once, then connects to the
exact validated IP (no second resolution = no DNS-rebind TOCTOU)."""
def connect(self) -> None:
family, ip = _resolve_and_validate(self.host, self.port)
self.sock = socket.create_connection(
(ip, self.port),
self.timeout,
self.source_address,
)
if self._tunnel_host:View on GitHub (pinned to 7fe58b0b0f)
Solutions
- Point the URL at a genuinely public host (or the external IP your deployment exposes) instead of a private/loopback address.
- If the target is an internal service you own and the fetch path is trusted, use plain urllib/requests outside graphify's safe_fetch rather than weakening the guard.
- Check DNS for the failing host (dig +short <host>) and confirm which record is triggering the block before changing anything.
- Never disable the SSRF guard in shared/server deployments; the block exists to stop internal-network probing.
Example fix
# before
html = safe_fetch_text("http://localhost:8080/docs/page.html")
# after: serve the content from a public host, or read local files directly
html = Path("docs/page.html").read_text(encoding="utf-8") Defensive patterns
Strategy: validation
Validate before calling
import ipaddress, socket
def resolves_to_public(host: str) -> bool:
for *_rest, sockaddr in socket.getaddrinfo(host, 443, socket.AF_UNSPEC, socket.SOCK_STREAM):
try:
ip = ipaddress.ip_address(sockaddr[0])
except ValueError:
continue
if not (ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved or ip.is_multicast):
return True
return False
assert resolves_to_public("example.com"), "host resolves only to blocked ranges" Type guard
def is_safe_fetch_host(host: str) -> bool:
try:
return resolves_to_public(host)
except socket.gaierror:
return False Try / catch
try:
body = safe_fetch(url)
except OSError as e:
if "SSRF blocked" in str(e):
# policy decision: pick a different, public URL; do not disable the guard
raise ValueError(f"Refusing internal target: {url}") from e
raise Prevention
- Only pass public HTTPS URLs to safe_fetch/safe_fetch_text.
- Never configure localhost/internal hostnames into fetch pipelines that run in shared or server contexts.
- Pre-resolve and audit hosts in config at deploy time with a public-IP check.
When it happens
Trigger: Calling safe_fetch or safe_fetch_text with a URL whose host resolves to 127.0.0.1, 10.x.x.x, 192.168.x.x, 172.16-31.x, 169.254.169.254 (cloud metadata), ::1, or IPv6 unique-local fc00::/7. Also triggered when a public hostname is DNS-rebound to an internal address at resolution time.
Common situations: Fetching documentation pages that reference localhost aliases; test environments where a domain in the config points at an internal box; environments with split-horizon DNS where the same name resolves internally to a private IP; accidentally passing http://localhost:8000/... style URLs to a fetch helper.
Related errors
- Blocked private/internal IP {addr} (resolved from '{hostname
- SSRF blocked: no usable address resolved from '{host}'
- OLLAMA_BASE_URL points at a link-local/metadata address ({ho
- Blocked URL scheme '{parsed.scheme}' - only http and https a
- Blocked cloud metadata endpoint '{hostname}'. Got: {url!r}
AI-assisted analysis of Graphify-Labs/graphify@7fe58b0b0f (2026-08-14).
Data as JSON: /api/errors/5a953f04b2619924.
Report an issue: GitHub.