{"record":{"id":"f2dced06f0f8d3a1","repo":"invoke-ai/InvokeAI","slug":"download-url-url-has-no-host","errorCode":null,"errorMessage":"Download URL '{url}' has no host.","messagePattern":"Download URL '(.+?)' has no host\\.","errorType":"exception","errorClass":"UnsafeDownloadURLException","httpStatus":null,"severity":"error","filePath":"invokeai/app/util/ssrf.py","lineNumber":204,"sourceCode":"\ndef validate_download_url(url: str, allow_private_urls: bool = False) -> None:\n    \"\"\"Reject `url` up front if it obviously points somewhere only the server can reach.\n\n    Every address the host resolves to must be public — a hostname with both a public and a\n    loopback record is rejected, because we cannot control which one the HTTP client picks.\n\n    An unresolvable host is allowed through to the HTTP client, so that offline test\n    environments and mocked sessions keep working. That is only safe because the session\n    from `build_guarded_session()` re-checks the address it actually connects to.\n    \"\"\"\n    parts = urlsplit(str(url))\n\n    if parts.scheme.lower() not in ALLOWED_SCHEMES:\n        raise UnsafeDownloadURLException(f\"Unsupported URL scheme '{parts.scheme}'. Only http and https are allowed.\")\n\n    host = parts.hostname\n    if not host:\n        raise UnsafeDownloadURLException(f\"Download URL '{url}' has no host.\")\n\n    try:\n        port = parts.port\n    except ValueError as e:\n        raise UnsafeDownloadURLException(f\"Download URL '{url}' has an invalid port.\") from e\n\n    if allow_private_urls:\n        return\n\n    for spelling in _host_spellings(host):\n        literal = _parse_ipv4_literal(spelling)\n        if literal is not None:\n            candidates = [literal]\n        else:\n            try:\n                candidates = _resolve(spelling, port)\n            except (OSError, UnicodeError, ValueError):\n                continue","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/util/ssrf.py#L186-L222","documentation":"After scheme validation, the URL must contain a host. urlsplit().hostname returns None for malformed or host-less URLs, and validate_download_url raises UnsafeDownloadURLException because a download target without a host cannot be resolved or connected to.","triggerScenarios":"Passing 'https:///path/model.safetensors' (empty authority), a bare path like '/models/foo.safetensors', or a malformed URL whose netloc is stripped; also spaces or invalid characters that break netloc parsing.","commonSituations":"String concatenation bugs building the URL (missing host segment); relative paths passed where an absolute download URL is required; URLs mangled by shell escaping.","solutions":["Provide a full absolute URL including scheme and host, e.g. https://host/path/file","Print/inspect the URL string before calling the download API to catch mangling or truncation","Use a local import path instead of the remote-download API for local files","Quote URLs passed through shells to avoid stripping the host"],"exampleFix":"// before\nurl = 'https:///models/sd15.safetensors'  # empty host\n// after\nurl = 'https://huggingface.co/models/sd15.safetensors'","handlingStrategy":"validation","validationCode":"from urllib.parse import urlsplit\n\ndef validate_host(url):\n    if not urlsplit(str(url)).hostname:\n        raise ValueError(f\"URL has no host: {url}\")","typeGuard":null,"tryCatchPattern":"from invokeai.app.util.ssrf import UnsafeDownloadURLException\ntry:\n    download(url)\nexcept UnsafeDownloadURLException as e:\n    if 'has no host' in str(e):\n        raise ValueError(f\"Fix the download URL, it lacks a host: {url}\") from e\n    raise","preventionTips":["Build download URLs with f-strings including host explicitly","Never pass relative filesystem paths to the remote download API","Log the final URL string before calling the download API"],"tags":["security","url","validation","download"],"backgroundTag":"url-missing-host","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}