{"record":{"id":"724cc5c54aaf2319","repo":"oobabooga/textgen","slug":"no-hostname-in-url","errorCode":null,"errorMessage":"No hostname in URL","messagePattern":"No hostname in URL","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"modules/web_search.py","lineNumber":31,"sourceCode":"\n\ndef _validate_url(url):\n    \"\"\"Validate that a URL is safe to fetch (not targeting private/internal networks).\"\"\"\n    # Reject characters that cause parsing discrepancies between urlparse and requests,\n    # which can be exploited to bypass SSRF protections (GHSA-27xf-58m5-vxmc).\n    if '\\\\' in url:\n        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'])","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/oobabooga/textgen/blob/ed888c71f221df552750e1834b3654abab8ae345/modules/web_search.py#L13-L49","documentation":"Raised by _validate_url() when urlparse() returns an empty hostname. This happens for URLs that have a scheme but no authority component, such as 'file:///path', 'http:///foo', or URLs where the netloc was consumed by a preceding component the parser treats differently (e.g. after other validation failures leave a malformed URL). Without a hostname there is nothing to resolve or safety-check, so the request is refused.","triggerScenarios":"Calling safe_get()/download_web_page() with URLs like 'file:///etc/hosts', 'https:///resource', 'http://:8080/x' (empty host, port only), or scheme-only strings like 'https://'.","commonSituations":"User pastes a local file path with file:// expecting it to be fetched; malformed URLs from string concatenation where the host segment was dropped; scraping pipelines that build URLs from templated parts with an empty host variable.","solutions":["Fix the URL so it includes a real host: 'https://example.com/resource'.","If a templated URL builder produced it, assert the host component is non-empty before calling the fetch API.","Do not attempt to fetch local files through this API — read them with open()/Path directly instead."],"exampleFix":"# before\nresp = safe_get(f'https://{host}/page')  # host == '' -> 'https:///page' raises\n\n# after\nassert host, 'host must not be empty'\nresp = safe_get(f'https://{host}/page')","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef url_has_host(url: str) -> bool:\n    return bool(urlparse(url).hostname)","typeGuard":null,"tryCatchPattern":"try:\n    resp = safe_get(url)\nexcept ValueError as e:\n    if 'No hostname' in str(e):\n        raise ValueError(f'Malformed URL (missing host): {url!r}') from e\n    raise","preventionTips":["Build URLs from explicit scheme + host variables and assert host is non-empty before concatenation.","Run urlparse(url).hostname checks on templated URLs in tests.","Never route local-file access through the web-fetch API."],"tags":["ssrf","url-validation","security","web-fetch"],"backgroundTag":null,"analyzedSha":"ed888c71f221df552750e1834b3654abab8ae345","analyzedAt":"2026-08-15T05:24:21.000Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}