* feat(auth): add github_provider_hosts() to enumerate GHES hosts from auth.json
Assisted-by: Claude Code (model: claude-sonnet-4-6, autonomous)
* fix(extensions): resolve GHES release assets via /api/v3
Generalizes resolve_github_release_asset_api_url to GitHub Enterprise
Server hosts (gated by auth.json github hosts), fixing private GHES
extension/preset downloads. github/spec-kit#3147
Assisted-by: Claude Code (model: claude-sonnet-4-6, autonomous)
* fix(extensions,presets): pass auth.json github hosts into release resolver
Assisted-by: Claude Code (model: claude-sonnet-4-6, autonomous)
* docs(auth): document GHES private catalog + release-asset auth
Assisted-by: Claude Code (model: claude-sonnet-4-6, autonomous)
* fix(presets,workflows): pass auth.json github hosts into remaining release resolvers
Wires preset add --from and workflow add through github_provider_hosts()
so private GHES release assets resolve via /api/v3 there too. github/spec-kit#3147
Assisted-by: Claude Code (model: claude-sonnet-4-6, autonomous)
* test(presets): use module-level io.BytesIO in GHES preset test
Addresses Copilot review on PR #3157: drop unnecessary __import__("io")
in test_preset_add_from_ghes_release_url_resolves_via_api_v3 since io is
already imported at module level.
* fix(github-http): pass through GHES asset API URLs by path shape
Addresses Copilot review on PR #3157. A direct GHES /api/v3 release asset
URL was only returned as already-resolved when its host was in the
allowlist; otherwise the resolver returned None and the caller downloaded
the same URL without 'Accept: application/octet-stream', fetching JSON
metadata instead of the binary.
Gate the passthrough on path shape alone, mirroring the github.com case.
This is safe: passthrough returns the input URL unchanged and the caller
fetches it either way, so no new request to an arbitrary host is induced;
the token stays independently gated by auth.json in open_url. The
allowlist remains the anti-SSRF gate on the tag-lookup resolving path.
Add test_passthrough_for_unlisted_ghes_api_asset_url.
* Harden preset URL installs against unsafe redirects
Preset URL installs already rejected non-HTTPS source URLs, but the authenticated opener follows redirects. Validate the final response URL before writing the ZIP, preserve GitHub release asset URL resolution after the preset command module split, stream the response to disk, and keep catalog config serialization on safe YAML output.
Constraint: open_url follows redirects, so source URL validation alone does not constrain the downloaded target
Rejected: Keep response.read() for simplicity | large preset downloads should not be buffered entirely in memory
Confidence: high
Scope-risk: narrow
Directive: Keep preset URL policy aligned with workflow installer redirect validation
Tested: uvx ruff check src/specify_cli/__init__.py src/specify_cli/presets/__init__.py src/specify_cli/presets/_commands.py tests/test_presets.py
Tested: uv run pytest tests/test_presets.py -q
Not-tested: Real network redirect integration against a live HTTP server
Co-authored-by: OmX <omx@oh-my-codex.dev>
* Reject malformed preset download URLs
Preset downloads should fail early when a URL lacks a hostname, even if the scheme is HTTPS. The redirect error now describes any disallowed target instead of implying that only non-HTTPS redirects are blocked.
* Prevent credentialed preset redirects from downgrading transport
Preset URL downloads already checked the final URL after urllib followed redirects, but that was too late for authenticated requests because same-host redirects could preserve Authorization during the redirect itself. The authenticated HTTP helper now supports an opt-in redirect validator, and preset downloads use it to reject disallowed redirect targets before following them. The redirect auth handlers also stop preserving credentials across HTTPS to non-HTTPS downgrades as defense in depth.
* test(presets): 修复 URL 解析测试 mock 缺少 redirect_validator 参数
重定向安全加固为 open_url 新增 redirect_validator 参数,
两处 fake_open_url mock 签名未同步导致 TypeError。
补齐参数后全部 3717 个测试通过。
---------
Co-authored-by: OmX <omx@oh-my-codex.dev>
* fix: resolve GitHub release asset API URL for private repo preset and workflow downloads
- Add shared `resolve_github_release_asset_api_url` utility to `_github_http.py` for
reuse across preset and workflow download paths
- Apply the same private-repo fix from PR #2792 (extensions) to:
- `PresetCatalog.download_pack` — ZIP downloads via catalog `download_url`
- `preset add --from <url>` — ZIP downloads from a direct URL
- `workflow add <url>` — workflow YAML downloads from a direct URL
- `workflow add <id>` (catalog) — workflow YAML downloads via catalog `url`
- For browser release URLs (`github.com/…/releases/download/…`), the asset is
resolved via the GitHub REST API and downloaded with `Accept: application/octet-stream`
- Direct REST API asset URLs (`api.github.com/…/releases/assets/<id>`) are
downloaded directly with `Accept: application/octet-stream`
- Auth is preserved end-to-end through the existing `open_url` infrastructure
- Update `test_download_pack_sends_auth_header` and add
`test_download_pack_accepts_direct_github_rest_asset_url` to cover both paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: URL-encode tag in release API URL to handle special characters
Encode the tag as a path segment (using quote with safe='') when
building the releases/tags/<tag> API URL. This prevents malformed
URLs when tags contain reserved characters like '/' or '#'.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test: add CLI-level tests for preset add --from GitHub release URL resolution
Adds regression tests covering:
- resolve_github_release_asset_api_url unit tests (passthrough, resolution,
network error, URL encoding of special chars in tags)
- CLI-level 'preset add --from <github-release-url>' end-to-end flow
- CLI-level 'preset add --from <api-asset-url>' direct passthrough
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor: deduplicate release URL resolution; fix test issues
- ExtensionCatalog._resolve_github_release_asset_api_url now delegates
to the shared helper in _github_http.py (also gains URL-encoding fix)
- Remove unused 'io' import from test_github_http.py
- Remove duplicate 'provides' dict keys accidentally added to test_presets.py
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: align resolver timeout with download timeout; add workflow CLI tests
- Pass timeout=30 to resolve_github_release_asset_api_url in both
workflow add paths so worst-case latency matches the download timeout
- Add CLI-level regression tests for 'workflow add <url>' covering
browser URL resolution and direct API asset URL passthrough
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: remove unused urllib.request import; add catalog workflow test
- Remove unused 'import urllib.request' in preset add --from path
- Add CLI test for catalog-based 'workflow add <id>' with GitHub
release URL resolution
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* style: remove unused MagicMock imports from tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Manfred Riem <mnriem@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: validate URL scheme in build_github_request
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* test: add missing hostname validation test for build_github_request
* fix: update docstring and fix import grouping per Copilot feedback
* fix: sort imports and simplify url validation in build_github_request
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>