github/spec-kit · error · BundlerError

Catalog url must use HTTPS (got {parsed.scheme}://). HTTP is

Error message

Catalog url must use HTTPS (got {parsed.scheme}://). HTTP is only allowed for localhost.

What it means

Raised for http/https catalog URLs when the scheme is plain `http` and the hostname is not a loopback address (`localhost`, `127.0.0.1`, `::1`). This mirrors the fetch-time rule from `specify_cli.catalogs._validate_catalog_url` (#3209/#3210) so plaintext HTTP catalogs are caught at config time, not at fetch time.

Source

Thrown at src/specify_cli/bundler/commands_impl/catalog_config.py:177

    if not (parsed.scheme or parsed.path):
        raise BundlerError(f"Invalid catalog url: '{url}'.")
    # Reject unsupported URL schemes (e.g. ssh://, ftp://) up front so they are
    # never silently canonicalized as local filesystem paths. Local paths that
    # merely contain a ':' but no '://' (e.g. Windows drives) are still allowed.
    if "://" in url and parsed.scheme.lower() not in _REMOTE_SCHEMES:
        raise BundlerError(
            f"Unsupported catalog url scheme '{parsed.scheme}://' in '{url}'. "
            "Use http(s)://, file://, builtin://, or a local path."
        )
    if parsed.scheme.lower() in {"http", "https"}:
        # Mirror specify_cli.catalogs._validate_catalog_url (#3209/#3210):
        # HTTPS only (HTTP just for localhost), and check hostname, not
        # netloc — netloc is truthy for host-less URLs like "https://:8080"
        # or "https://user@". Validating here keeps junk out of
        # bundle-catalogs.yml instead of failing later at fetch time.
        is_localhost = hostname in ("localhost", "127.0.0.1", "::1")
        if parsed.scheme.lower() != "https" and not is_localhost:
            raise BundlerError(
                f"Catalog url must use HTTPS (got {parsed.scheme}://). "
                "HTTP is only allowed for localhost."
            )
        if not hostname:
            raise BundlerError(f"Catalog url must be a valid URL with a host: {url}")

    url = _canonicalize_url(url)
    install_policy = InstallPolicy.parse(policy)
    resolved_id = (source_id or _derive_id(url)).strip()

    catalogs = _read(project_root)
    for existing in catalogs:
        if existing.get("id") == resolved_id or existing.get("url") == url:
            raise BundlerError(
                f"Catalog source '{resolved_id}' (or url) already exists in this project."
            )

    entry = {

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Switch to the https:// version of the URL
  2. For local development, use `http://localhost...`, `http://127.0.0.1...`, or `http://[::1]...`
  3. Put an TLS-terminating proxy in front of an internal HTTP mirror

Example fix

# before
http://catalog.internal.example.com/catalog.json

# after
https://catalog.internal.example.com/catalog.json
# or for local dev:
http://localhost:8080/catalog.json
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

p = urlparse(url)
if p.scheme.lower() in {"http", "https"}:
    host = p.hostname
    if p.scheme.lower() != "https" and host not in ("localhost", "127.0.0.1", "::1"):
        raise SystemExit("non-localhost HTTP catalogs are rejected — use https or localhost")

Type guard

def is_https_or_localhost(url: str) -> bool:
    p = urlparse(url)
    return p.scheme.lower() != "http" or p.hostname in ("localhost", "127.0.0.1", "::1")

Try / catch

try:
    add_source(project_root, url, policy=policy, priority=50)
except BundlerError as exc:
    if "must use HTTPS" in str(exc):
        # rewrite http://host -> https://host, or use http://localhost for dev
        raise
    raise

Prevention

When it happens

Trigger: `http://catalog.internal.example.com/c.json` — any non-localhost http URL. Note `https://…` never triggers this; only scheme!=https with a non-loopback host does.

Common situations: Internal company mirrors served over plain HTTP; local dev against a non-loopback IP (e.g. docker host `http://172.17.0.1`); stale bookmark of a site that later added TLS.

Related errors


AI-assisted analysis of github/spec-kit@bf88c9f9a8 (2026-08-14). Data as JSON: /api/errors/537b9b06965ea9c1. Report an issue: GitHub.