Graphify-Labs/graphify · error · ValueError

Blocked private/internal IP {addr} (resolved from '{hostname

Error message

Blocked private/internal IP {addr} (resolved from '{hostname}'). Got: {url!r}

What it means

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).

Source

Thrown at graphify/security.py:134

        )

    hostname = parsed.hostname
    if hostname:
        # Block known cloud metadata hostnames
        if hostname.lower() in _BLOCKED_HOSTS:
            raise ValueError(
                f"Blocked cloud metadata endpoint '{hostname}'. "
                f"Got: {url!r}"
            )

        # Resolve hostname and block private/reserved IP ranges
        try:
            infos = socket.getaddrinfo(hostname, None, socket.AF_UNSPEC, socket.SOCK_STREAM)
            for info in infos:
                addr = info[4][0]
                ip = ipaddress.ip_address(addr)
                if _ip_is_blocked(ip):
                    raise ValueError(
                        f"Blocked private/internal IP {addr} (resolved from '{hostname}'). "
                        f"Got: {url!r}"
                    )
        except socket.gaierror as exc:
            raise ValueError(
                f"DNS resolution failed for '{hostname}': {exc}. Got: {url!r}"
            ) from exc

    return url


# ---------------------------------------------------------------------------
# SSRF-guarded connections
#
# Instead of monkey-patching the process-global socket.getaddrinfo (a
# non-thread-safe TOCTOU hazard when multiple fetches run concurrently),
# we subclass the HTTP(S) connection so each connection resolves DNS exactly
# once, validates the resulting IP, and then connects to that exact IP. There

View on GitHub (pinned to 7fe58b0b0f)

Solutions

  1. If you truly need internal fetches, use a dedicated HTTP client outside the guarded path - do not weaken validate_url.
  2. For local development, exempt via an explicit, narrow configuration only if your threat model allows; otherwise test against a public staging URL.
  3. If unexpected: treat as a possible DNS-rebinding probe - check what the hostname resolves to from the same box (getent hosts <name>).

Example fix

# before
url = 'http://intranet.corp.local/report'   # resolves to 10.1.2.3
fetch(validate_url(url))                   # ValueError: Blocked private/internal IP

# after - fetch internal resources with a client outside the SSRF-guarded path
resp = requests_internal.get('http://intranet.corp.local/report', timeout=10)
Defensive patterns

Strategy: validation

Validate before calling

import ipaddress, socket
from urllib.parse import urlparse

def resolves_to_private(host: str) -> bool:
    try:
        infos = socket.getaddrinfo(host, None, socket.AF_UNSPEC, socket.SOCK_STREAM)
        return any(
            ipaddress.ip_address(i[4][0]).is_private or ipaddress.ip_address(i[4][0]).is_loopback
            for i in infos
        )
    except socket.gaierror:
        return False  # let validate_url produce the DNS error instead

Type guard

def is_safe_url(url: str) -> bool:
    try:
        validate_url(url)
        return True
    except ValueError:
        return False

Try / catch

try:
    safe = validate_url(url)
except ValueError as exc:
    if "private/internal IP" in str(exc):
        return bad_request("internal addresses are not fetchable")
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Graphify-Labs/graphify@7fe58b0b0f (2026-08-14). Data as JSON: /api/errors/af47e1d53fe454cc. Report an issue: GitHub.