{"record":{"id":"641d0319132ba7ce","repo":"oobabooga/textgen","slug":"access-to-non-public-address-ip-is-blocked","errorCode":null,"errorMessage":"Access to non-public address {ip} is blocked","messagePattern":"Access to non-public address (.+?) is blocked","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"modules/web_search.py","lineNumber":38,"sourceCode":"        raise ValueError(\"Invalid URL: backslashes are not allowed\")\n\n    parsed = urlparse(url)\n    if parsed.scheme not in ('http', 'https'):\n        raise ValueError(f\"Unsupported URL scheme: {parsed.scheme}\")\n\n    if '@' in parsed.netloc:\n        raise ValueError(\"Invalid URL: userinfo (credentials) in URLs is not allowed\")\n\n    hostname = parsed.hostname\n    if not hostname:\n        raise ValueError(\"No hostname in URL\")\n\n    # Resolve hostname and check all returned addresses\n    try:\n        for family, _, _, _, sockaddr in socket.getaddrinfo(hostname, None):\n            ip = ipaddress.ip_address(sockaddr[0])\n            if not ip.is_global:\n                raise ValueError(f\"Access to non-public address {ip} is blocked\")\n    except socket.gaierror:\n        raise ValueError(f\"Could not resolve hostname: {hostname}\")\n\n\ndef safe_get(url, headers=None, timeout=10, max_redirects=5):\n    \"\"\"Fetch a URL with SSRF-safe redirect handling. Validates every hop.\"\"\"\n    _validate_url(url)\n    for _ in range(max_redirects):\n        response = requests.get(url, headers=headers, timeout=timeout, allow_redirects=False)\n        if response.is_redirect and 'Location' in response.headers:\n            url = urljoin(url, response.headers['Location'])\n            _validate_url(url)\n        else:\n            return response\n\n    raise ValueError(f\"Too many redirects (max {max_redirects})\")\n\n","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/oobabooga/textgen/blob/ed888c71f221df552750e1834b3654abab8ae345/modules/web_search.py#L20-L56","documentation":"Raised by _validate_url() during DNS resolution: the hostname resolves to at least one address that is not globally routable (not ip.is_global) — loopback, link-local, private ranges (10/8, 172.16/12, 192.168/16), CGNAT, multicast, etc. This is the core SSRF defense: it prevents the fetch layer from being used to reach internal services (cloud metadata at 169.254.169.254, internal admin panels, databases). Every address returned by getaddrinfo is checked, so a DNS name with mixed public/private A records still fails.","triggerScenarios":"Fetching a URL whose host resolves to a private IP: 'http://localhost/api', 'http://192.168.1.1/admin', 'http://169.254.169.254/latest/meta-data', or a public-looking domain whose DNS returns an internal address (DNS rebinding or split-horizon DNS). Also triggered on redirect hops whose Location points at an internal host.","commonSituations":"Attempting to fetch internal/intranet pages through the web-fetch feature; testing SSRF protections; a corporate split-horizon DNS where an internal name also resolves internally when queried from inside the network; redirect chains that bounce to an internal host.","solutions":["If you legitimately need an internal page, fetch it with a direct HTTP client outside this guarded API — the block is intentional.","Check which IP the hostname resolves to (dig/getent) and use the public address or a public DNS name if split-horizon DNS is returning internal records.","For maintainers: if internal fetching must be supported, add an explicit opt-in allowlist rather than weakening the is_global check.","Treat hits on redirect hops as attacks or misconfiguration of the remote server; surface the error to the user instead of retrying."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import ipaddress, socket\n\ndef resolves_to_public_host(url: str) -> bool:\n    host = urlparse(url).hostname\n    if not host:\n        return False\n    try:\n        return all(ipaddress.ip_address(sa[0]).is_global for *_r, sa in socket.getaddrinfo(host, None))\n    except socket.gaierror:\n        return False","typeGuard":null,"tryCatchPattern":"try:\n    resp = safe_get(url)\nexcept ValueError as e:\n    if 'non-public address' in str(e):\n        log.warning('Blocked internal fetch attempt: %s', url)  # intentional guard; do not bypass\n        resp = None\n    else:\n        raise","preventionTips":["Only submit public internet URLs to the web-fetch API; fetch internal resources with a direct client outside it.","Do not attempt to work around the is_global check — it exists to block metadata-service and LAN attacks.","In crawlers, catch and blacklist URLs that redirect to internal hosts."],"tags":["ssrf","security","dns","network","web-fetch"],"backgroundTag":null,"analyzedSha":"ed888c71f221df552750e1834b3654abab8ae345","analyzedAt":"2026-08-15T05:24:21.000Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}