{"record":{"id":"c323807e2c0e9359","repo":"github/spec-kit","slug":"refusing-to-download-label-url-is-malformed-u","errorCode":null,"errorMessage":"Refusing to download {label}: URL is malformed: {url}","messagePattern":"Refusing to download (.+?): URL is malformed: (.+?)","errorType":"exception","errorClass":"BundlerError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/commands/bundle/__init__.py","lineNumber":918,"sourceCode":"        expected_sha256=getattr(resolved.entry, \"sha256\", None),\n    )\n    _validate_catalog_manifest(resolved.entry, manifest)\n    return manifest\n\n\ndef _require_https(label: str, url: str) -> None:\n    from urllib.parse import urlparse\n\n    # urlparse / hostname access raise ValueError on a malformed authority;\n    # keep the documented BundlerError contract (older Pythons surface this via\n    # the .hostname access below rather than at the urlparse call).\n    try:\n        parsed = urlparse(url)\n        hostname = parsed.hostname\n        # Accessing ``port`` performs urllib's syntax/range validation.\n        _ = parsed.port\n    except ValueError:\n        raise BundlerError(\n            f\"Refusing to download {label}: URL is malformed: {url}\"\n        ) from None\n    is_localhost = hostname in (\"localhost\", \"127.0.0.1\", \"::1\")\n    if parsed.scheme != \"https\" and not (parsed.scheme == \"http\" and is_localhost):\n        raise BundlerError(\n            f\"Refusing to download {label} over non-HTTPS URL: {url}\"\n        )\n    if not parsed.hostname:\n        raise BundlerError(f\"Refusing to download {label} from URL with no host: {url}\")\n\n\ndef _download_remote_manifest(\n    entry_id: str,\n    url: str,\n    *,\n    expected_sha256: str | None = None,\n):\n    \"\"\"Fetch a remote bundle artifact over HTTPS and extract its manifest.\"\"\"","sourceCodeStart":900,"sourceCodeEnd":936,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/commands/bundle/__init__.py#L900-L936","documentation":"Spec Kit refuses to download a bundle artifact when urllib cannot parse the URL. _require_https() parses the URL and accesses .hostname and .port; a malformed authority such as an unclosed IPv6 bracket or an invalid port raises ValueError, which is converted to BundlerError. The same guard is applied to the catalog URL, a resolved GitHub API URL, every redirect hop, and the final response URL.","triggerScenarios":"Running `specify bundle info|install|update <id>` when the catalog entry's download_url is malformed, or when the server redirects to a malformed Location URL. Validation runs even with --offline because _download_manifest checks the URL before the offline gate.","commonSituations":"A hand-edited custom bundle catalog contains `https://[::1/bundle.zip`, `https://host:not-a-port/bundle.zip`, or an out-of-range port. A proxy or release server emits a malformed redirect target.","solutions":["Copy the URL shown in the error and inspect the active bundle catalog entry that supplied it.","Correct the URL syntax: close IPv6 brackets as `https://[::1]/bundle.zip`, use a numeric port in 0-65535, and include a host.","If the bad URL is a redirect target rather than the catalog URL, fix the release server or redirect configuration and retry.","Re-run `specify bundle info <id>` to confirm the catalog entry now resolves."],"exampleFix":"# catalog.json (before)\n\"download_url\": \"https://[::1:8443/bundle.zip\"\n\n# catalog.json (after)\n\"download_url\": \"https://[::1]:8443/bundle.zip\"","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef is_parseable_download_url(url: str) -> bool:\n    try:\n        parsed = urlparse(url)\n        host = parsed.hostname\n        _ = parsed.port\n    except ValueError:\n        return False\n    return bool(host)","typeGuard":null,"tryCatchPattern":"from specify_cli.bundler import BundlerError\n\ntry:\n    specify_bundle_install(bundle_id)\nexcept BundlerError as exc:\n    if \"URL is malformed\" in str(exc):\n        show_catalog_url_error(exc)\n    else:\n        raise","preventionTips":["Run automated catalog validation that parses every download_url with urlparse and checks .hostname/.port.","Generate URLs with urljoin/urlencode rather than string concatenation.","Test custom catalogs with `specify bundle search --json` before making them the install source."],"tags":["security","url","bundle","catalog","validation"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}