Skip to content

feat(extensions): authenticate GitHub-hosted catalog and download requests with GITHUB_TOKEN/GH_TOKEN#2087

Open
anasseth wants to merge 1 commit intogithub:mainfrom
anasseth:feat/github-token-auth-private-catalogs
Open

feat(extensions): authenticate GitHub-hosted catalog and download requests with GITHUB_TOKEN/GH_TOKEN#2087
anasseth wants to merge 1 commit intogithub:mainfrom
anasseth:feat/github-token-auth-private-catalogs

Conversation

@anasseth
Copy link
Copy Markdown

@anasseth anasseth commented Apr 4, 2026

Description

Fixes #2037. Closes the authentication gap introduced when multi-catalog support landed in #1707.

Before this change, all network requests in ExtensionCatalog used bare urllib.request.urlopen(url) with no headers. Any catalog or extension ZIP hosted in a private GitHub repository would silently fail with HTTP 404, regardless of whether GITHUB_TOKEN or GH_TOKEN was set in the environment.

This PR adds a _make_request(url) helper on ExtensionCatalog that attaches an Authorization: token <value> header when:

  • GITHUB_TOKEN or GH_TOKEN is present in the environment, and
  • the target URL is a GitHub-hosted domain (raw.githubusercontent.com,
    github.com, or api.github.com)

Non-GitHub URLs are always fetched without credentials to prevent token leakage to third-party hosts.

The three affected call sites are:

  • _fetch_single_catalog — fetches catalog JSON from a configured catalog URL
  • fetch_catalog — legacy single-catalog path used when SPECKIT_CATALOG_URL is set
  • download_extension — downloads extension ZIP from a release asset URL

No behavior change for users without a token set — the code path is identical to before.

Documentation in EXTENSION-USER-GUIDE.md has been updated: the existing GH_TOKEN/GITHUB_TOKEN table entry (which described the token as "for downloads" only) now accurately reflects that it covers catalog fetches as well, and a private-catalog usage example has been added.

Testing

  • Ran uv run specify --help — CLI loads correctly, all commands present
  • Ran full test suite: 1131 passed, 5 skipped, 2 failed
    • Both failures are pre-existing on main before this change:
    • TestManifestPathTraversal::test_record_file_rejects_absolute_path
    • TestCommandRegistrar::test_codex_skill_registration_uses_fallback_script_variant_without_init_options
  • 8 new tests added to TestExtensionCatalog in tests/test_extensions.py:
    • 6 unit tests for _make_request: no-token path, GITHUB_TOKEN, GH_TOKEN fallback, precedence when both are set, non-GitHub URL never gets header (security), api.github.com domain
    • 2 integration tests that mock urlopen and assert the captured Request object carries the auth header — one for _fetch_single_catalog, one for download_extension

AI Disclosure

  • I did use AI assistance (describe below)
    This PR was implemented with AI assistance via Claude Code.

@anasseth anasseth requested a review from mnriem as a code owner April 4, 2026 04:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support GITHUB_TOKEN authentication for private catalog and extension download URLs

1 participant