{"record":{"id":"1fde6560a4ca9dc3","repo":"github/spec-kit","slug":"catalog-url-must-use-https-got-parsed-scheme-1fde65","errorCode":null,"errorMessage":"Catalog URL must use HTTPS (got {parsed.scheme}://). HTTP is only allowed for localhost.","messagePattern":"Catalog URL must use HTTPS \\(got (.+?)://\\)\\. HTTP is only allowed for localhost\\.","errorType":"validation","errorClass":"PresetValidationError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/presets/__init__.py","lineNumber":4156,"sourceCode":"\n        Args:\n            url: URL to validate\n\n        Raises:\n            PresetValidationError: If URL is invalid or uses non-HTTPS scheme\n        \"\"\"\n        from urllib.parse import urlparse\n\n        try:\n            parsed = urlparse(url)\n            hostname = parsed.hostname\n        except ValueError:\n            raise PresetValidationError(f\"Catalog URL is malformed: {url}\") from None\n        is_localhost = hostname in (\"localhost\", \"127.0.0.1\", \"::1\")\n        if parsed.scheme != \"https\" and not (\n            parsed.scheme == \"http\" and is_localhost\n        ):\n            raise PresetValidationError(\n                f\"Catalog URL must use HTTPS (got {parsed.scheme}://). \"\n                \"HTTP is only allowed for localhost.\"\n            )\n        # Check hostname, not netloc: netloc is truthy for host-less URLs like\n        # \"https://:8080\" or \"https://user@\", so the host guarantee this error\n        # promises would not actually hold. hostname is None in those cases (#3209).\n        if not hostname:\n            raise PresetValidationError(\n                \"Catalog URL must be a valid URL with a host.\"\n            )\n\n    def _make_request(self, url: str):\n        \"\"\"Build a urllib Request, adding auth headers when a provider matches.\n\n        Delegates to :func:`specify_cli.authentication.http.build_request`.\n        \"\"\"\n        from specify_cli.authentication.http import build_request\n        return build_request(url)","sourceCodeStart":4138,"sourceCodeEnd":4174,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/presets/__init__.py#L4138-L4174","documentation":"Catalog URLs must use HTTPS, with a single exception: plain HTTP is allowed for localhost (hostname exactly localhost, 127.0.0.1, or ::1). Any other scheme — http against a remote host, ftp, file — fails this PresetValidationError. This exists because catalog fetches send auth headers and must not leak over plaintext connections.","triggerScenarios":"Configuring a catalog entry with `http://internal.example.com/catalog.json` (remote HTTP), `ftp://...`, or an http:// URL whose host is a machine name or 0.0.0.0 (not in the localhost allowlist). Note: only the literal hostname counts — `http://127.0.0.1.nip.io` is rejected.","commonSituations":"Pointing at an internal/staging catalog served over plain HTTP inside a VPN, or a local dev server bound to a LAN hostname instead of localhost.","solutions":["Serve the catalog over HTTPS (put it behind TLS or a proxy) and use the https:// URL.","For local development, keep the host literal: http://localhost:8000, http://127.0.0.1:8000, or http://[::1]:8000.","If you use a hostname alias for localhost, switch to the literal 127.0.0.1 to stay within the exception."],"exampleFix":"# before\ncatalogs:\n  - url: \"http://catalog.internal/catalog.json\"\n\n# after\ncatalogs:\n  - url: \"https://catalog.internal/catalog.json\"","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\np = urlparse(url)\nhost = p.hostname\nassert p.scheme == \"https\" or (p.scheme == \"http\" and host in (\"localhost\", \"127.0.0.1\", \"::1\")), \\\n    \"catalog URL must be https (http only for localhost)\"","typeGuard":"from urllib.parse import urlparse\ndef is_allowed_catalog_url(url: str) -> bool:\n    try:\n        p = urlparse(url)\n    except ValueError:\n        return False\n    return p.scheme == \"https\" or (p.scheme == \"http\" and p.hostname in (\"localhost\", \"127.0.0.1\", \"::1\"))","tryCatchPattern":"try:\n    manager.add_catalog(url)\nexcept PresetValidationError as e:\n    if \"must use HTTPS\" in str(e):\n        # switch the entry to https, or use a literal localhost host for dev\n        ...","preventionTips":["Default every catalog entry to https://.","Local dev: keep http://localhost, http://127.0.0.1, or http://[::1] literally — hostname aliases are not exempt.","Auth headers are attached to catalog requests; never point them at plain-HTTP remote hosts."],"tags":["preset","catalog","https","security"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}