{"record":{"id":"158f99583dd00529","repo":"github/spec-kit","slug":"refusing-to-download-label-over-non-https-url","errorCode":null,"errorMessage":"Refusing to download {label} over non-HTTPS URL: {url}","messagePattern":"Refusing to download (.+?) over non-HTTPS URL: (.+?)","errorType":"exception","errorClass":"BundlerError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/commands/bundle/__init__.py","lineNumber":923,"sourceCode":"\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.\"\"\"\n    import io\n    import tempfile\n    from pathlib import PurePosixPath\n    from urllib.parse import urlparse as _urlparse\n","sourceCodeStart":905,"sourceCodeEnd":941,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/commands/bundle/__init__.py#L905-L941","documentation":"Spec Kit enforces HTTPS for every bundle download URL. Plain HTTP is accepted only for the exact hosts localhost, 127.0.0.1, and ::1; every other scheme and host combination is rejected as BundlerError before network access. Redirect and final response URLs must satisfy the same policy.","triggerScenarios":"A catalog entry has `ftp://`, `http://`, `git://`, or another non-HTTPS download_url for a non-localhost host, or an HTTPS download redirects to HTTP. The check also runs under --offline before the offline gate.","commonSituations":"Using an internal Artifactory/Nexus/GitLab server that is configured for HTTP only, testing against `http://0.0.0.0:8000` or an alternate localhost name, or a redirect chain that downgrades to HTTP.","solutions":["Serve the artifact over HTTPS and update the catalog download_url to the HTTPS URL.","For a local test server, use exactly `http://localhost`, `http://127.0.0.1`, or `http://[::1]`; `0.0.0.0` and other host names are intentionally rejected.","If a redirect is involved, configure the redirect target to HTTPS so every hop passes the guard.","If you do not control the catalog, install from a local bundle path or choose a catalog source with an HTTPS URL."],"exampleFix":"# before\n\"download_url\": \"http://artifacts.internal/my-bundle-1.0.0.zip\"\n\n# after\n\"download_url\": \"https://artifacts.internal/my-bundle-1.0.0.zip\"","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\nLOCAL_HOSTS = {\"localhost\", \"127.0.0.1\", \"::1\"}\n\ndef is_allowed_download_url(url: str) -> bool:\n    try:\n        p = urlparse(url)\n        host = p.hostname\n        _ = p.port\n    except ValueError:\n        return False\n    if not host:\n        return False\n    return p.scheme == \"https\" or (p.scheme == \"http\" and host in LOCAL_HOSTS)","typeGuard":null,"tryCatchPattern":"except BundlerError as exc:\n    if \"non-HTTPS URL\" in str(exc):\n        report_that_https_or_exact_localhost_is_required()\n    else:\n        raise","preventionTips":["Publish all custom catalogs and artifacts behind HTTPS by default.","Do not rely on alternate local hostnames; use localhost, 127.0.0.1, or ::1.","Check redirect chains with curl -IL and reject any HTTP hop."],"tags":["security","https","url","bundle","catalog"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}