github/spec-kit · error · ValueError

providers[{i}]: invalid host pattern {h!r}. Only exact hostn

Error message

providers[{i}]: invalid host pattern {h!r}. Only exact hostnames or '*.suffix' forms are allowed (e.g. 'github.com' or '*.visualstudio.com').

What it means

Raised while parsing the `providers` array in the auth config when a `hosts` entry fails the `_is_valid_host_pattern` check. Only exact hostnames (e.g. `github.com`) or single-label `*.suffix` wildcards (e.g. `*.visualstudio.com`) are accepted; broader wildcards like `*github.com`, `*.`, or hosts containing scheme/path characters are rejected because they can match attacker-controlled domains (e.g. `*github.com` also matches `github.com.evil.com`).

Source

Thrown at src/specify_cli/authentication/config.py:139

    if not isinstance(providers_raw, list):
        raise ValueError("auth.json must contain a 'providers' array")

    entries: list[AuthConfigEntry] = []
    for i, entry_raw in enumerate(providers_raw):
        if not isinstance(entry_raw, dict):
            raise ValueError(f"providers[{i}]: must be a JSON object")

        hosts = entry_raw.get("hosts")
        if not isinstance(hosts, list) or not hosts:
            raise ValueError(f"providers[{i}]: 'hosts' must be a non-empty array")
        if not all(isinstance(h, str) and h.strip() for h in hosts):
            raise ValueError(f"providers[{i}]: each host must be a non-empty string")
        # Normalize hosts: strip whitespace and lowercase
        hosts = [h.strip().lower() for h in hosts]
        # Reject dangerous wildcard forms (e.g. *github.com matches github.com.evil.com)
        for h in hosts:
            if not _is_valid_host_pattern(h):
                raise ValueError(
                    f"providers[{i}]: invalid host pattern {h!r}. "
                    "Only exact hostnames or '*.suffix' forms are allowed "
                    "(e.g. 'github.com' or '*.visualstudio.com')."
                )

        provider = entry_raw.get("provider", "")
        if not isinstance(provider, str) or not provider:
            raise ValueError(f"providers[{i}]: 'provider' must be a non-empty string")

        auth = entry_raw.get("auth", "")
        if not isinstance(auth, str) or not auth:
            raise ValueError(f"providers[{i}]: 'auth' must be a non-empty string")

        token = entry_raw.get("token")
        token_env = entry_raw.get("token_env")

        # Validate token/token_env types
        if token is not None and (not isinstance(token, str) or not token.strip()):

View on GitHub (pinned to bf88c9f9a8)

Solutions

  1. Use an exact hostname (e.g. `github.com`) or a single `*.suffix` wildcard (e.g. `*.visualstudio.com`)
  2. Remove scheme/path portions from the host string — hosts are bare hostnames only
  3. List each deeper subdomain explicitly instead of multi-level wildcards

Example fix

// before
"hosts": ["*github.com", "https://git.example.com"]

// after
"hosts": ["github.com", "git.example.com"]
Defensive patterns

Strategy: validation

Validate before calling

import re
_HOST_EXACT = re.compile(r"^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$")

def is_valid_host_pattern(h: str) -> bool:
    h = h.strip().lower()
    if h.startswith("*."):
        return bool(_HOST_EXACT.match(h[2:]))
    return bool(_HOST_EXACT.match(h))

Type guard

def is_host_pattern_list(value: object) -> bool:
    return (
        isinstance(value, list)
        and bool(value)
        and all(isinstance(h, str) and is_valid_host_pattern(h) for h in value)
    )

Try / catch

try:
    load_auth_config(raw)
except ValueError as exc:
    if "invalid host pattern" in str(exc):
        # surface the offending host from the message and fix the config source
        raise SystemExit(f"Fix auth config: {exc}") from exc
    raise

Prevention

When it happens

Trigger: An entry in the `providers` JSON/YAML list contains a host string that is neither a plain hostname nor a `*.suffix` pattern — e.g. `"*github.com"`, `"https://github.com"`, `"*"`, `"github.com/*"`, or `"*.*.com"`. Hosts are stripped and lowercased before the check, so whitespace/case never trigger it.

Common situations: Copying a CORS-style or cookies-style wildcard from another tool's config; pasting a full URL instead of a hostname; trying to match all subdomains in depth with `**.` or nested wildcards.

Related errors


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