* 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>