{"record":{"id":"31aafe6fb655c118","repo":"github/spec-kit","slug":"preset-download-url-must-use-https-download-url","errorCode":null,"errorMessage":"Preset download URL must use HTTPS: {download_url}","messagePattern":"Preset download URL must use HTTPS: (.+?)","errorType":"exception","errorClass":"PresetError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/presets/__init__.py","lineNumber":4875,"sourceCode":"        # \"https://[::1\") makes urlparse / hostname access raise ValueError.\n        # The download_url comes from catalog payload data, so surface a clean\n        # PresetError rather than leaking a raw ValueError past the command\n        # handler (which only catches PresetError). Mirrors catalogs (#3435)\n        # and workflows/catalog.py (#3484).\n        try:\n            parsed = urlparse(download_url)\n            hostname = parsed.hostname\n            parsed.port\n        except ValueError:\n            raise PresetError(\n                f\"Preset download URL is malformed: {download_url}\"\n            ) from None\n        if not hostname:\n            raise PresetError(\n                f\"Preset download URL is malformed: {download_url}\"\n            )\n        if not is_https_or_localhost_http(download_url):\n            raise PresetError(\n                f\"Preset download URL must use HTTPS: {download_url}\"\n            )\n\n        if target_dir is None:\n            target_dir = self.cache_dir / \"downloads\"\n        target_dir = Path(target_dir)\n        version = pack_info.get(\"version\", \"unknown\")\n        declared_format = archive_format_from_name(download_url)\n        build_safe_download_path(\n            target_dir,\n            pack_id,\n            version,\n            error_type=PresetError,\n            label=\"preset\",\n            suffix=archive_suffix(declared_format or \"tar.gz\"),\n        )\n        target_dir.mkdir(parents=True, exist_ok=True)\n","sourceCodeStart":4857,"sourceCodeEnd":4893,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/presets/__init__.py#L4857-L4893","documentation":"The download_url does not satisfy the HTTPS-or-localhost-HTTP policy enforced by is_https_or_localhost_http (from _download_security). Plain HTTP URLs to remote hosts are rejected to prevent unencrypted, tamperable downloads of preset content.","triggerScenarios":"download_url starting with http:// (not https://) and not pointing at localhost — e.g. 'http://example.com/pack.tar.gz' in a third-party catalog entry.","commonSituations":"Internal mirror served over plain HTTP; legacy catalog entries predating the HTTPS requirement; test fixtures using http URLs for non-localhost hosts.","solutions":["Serve the artifact over HTTPS and update download_url to the https:// form","For local testing, use http://localhost or http://127.0.0.1 which are explicitly allowed","Ask the catalog maintainer to upgrade their links to HTTPS"],"exampleFix":"# before\n\"download_url\": \"http://mirror.example.com/mytheme.tar.gz\"\n\n# after\n\"download_url\": \"https://mirror.example.com/mytheme.tar.gz\"","handlingStrategy":"validation","validationCode":"from specify_cli._download_security import is_https_or_localhost_http\nif not is_https_or_localhost_http(download_url):\n    raise ValueError(f\"download_url must be HTTPS (or localhost HTTP): {download_url!r}\")","typeGuard":"def is_allowed_scheme_url(url: str) -> bool:\n    return url.startswith(\"https://\") or url.startswith(\"http://localhost\") or url.startswith(\"http://127.0.0.1\")","tryCatchPattern":"except PresetError as e:\n    if \"must use HTTPS\" in str(e):\n        switch_to_https_mirror(download_url)  # or use localhost for tests\n    raise","preventionTips":["Host artifacts on HTTPS endpoints only; plain-HTTP mirrors are rejected by design","Use http://localhost fixtures for offline tests instead of remote http URLs"],"tags":["presets","security","https","download"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}