Skip to content

harden: bound HTTP reads and enforce strict redirects#3140

Open
PascalThuet wants to merge 2 commits into
github:mainfrom
PascalThuet:split/bounded-http-reads
Open

harden: bound HTTP reads and enforce strict redirects#3140
PascalThuet wants to merge 2 commits into
github:mainfrom
PascalThuet:split/bounded-http-reads

Conversation

@PascalThuet

@PascalThuet PascalThuet commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Part of splitting #2442 into smaller, dedicated PRs. Re-derived against current main.

What

  • New src/specify_cli/_download_security.py: read_response_limited (bounded, chunk-loop read) + is_https_or_localhost_http (shared scheme predicate) + size constants.
  • authentication/http.py: open_url(strict_redirects=...) plus an always-on _validate_strict_redirect inside the redirect handler — rejects redirects whose target isn't HTTPS (except HTTP→localhost), composing with the existing redirect_validator and auth-stripping rather than replacing them.
  • _version.py / _github_http.py: bounded JSON reads (MAX_JSON_METADATA_BYTES) + strict_redirects=True on the release-tag fetch.
  • authentication/azure_devops.py: route the OAuth token POST through the redirect handler (empty host list → scheme check only), so a 307/308 cannot forward the client_secret request body to a non-HTTPS host; bound the token read.
  • Test fakes that exercise GitHub release-resolution downloads now model HTTPResponse.read(size) cursor semantics, so the broader extension/preset/workflow suites cover the bounded-read path.

Why

Unbounded response.read() on remote JSON is a memory-DoS surface; a redirect to plain HTTP can downgrade transport or leak credentials. Both are closed without changing the happy path.

Validation

  • tests/test_download_security.py, test_github_http.py, test_authentication.py, test_upgrade.py, test_self_upgrade_*271 passed, 1 skipped. (The 19 warnings are pre-existing auth-fixture file-permission notes.)
  • tests/test_extensions.py tests/test_presets.py tests/test_workflows.py927 passed.
  • uvx ruff check src tests — clean.
  • git diff --check — clean.
  • HOME=/private/tmp/hermes-home .venv/bin/python -m pytest tests/integrations/test_integration_hermes.py -q34 passed. The full local pytest tests -q run only fails when Hermes writes to /Users/pascalthuet/.hermes, which is outside this sandbox's writable roots.

Foundation note: this PR adds only the read/URL core of _download_security. The ZIP-extraction + checksum helpers (and the tree-wide unbounded-read gate) land in the follow-up extension/preset and catalog PRs, which extend this module. Independent of PR2/PR3.

Disclosure: Updated on behalf of @PascalThuet by Codex (model: GPT-5, autonomous).

Add a shared _download_security module (read_response_limited,
is_https_or_localhost_http, size constants) and route the GitHub release
and Azure DevOps token network reads through bounded reads so an oversized
response can't exhaust memory.

Add a strict_redirects mode to authentication.open_url: the redirect handler
now rejects any redirect whose target isn't HTTPS (or HTTP to localhost),
composing with the existing per-hop redirect_validator and auth-stripping.
The Azure DevOps token POST is routed through that handler so a 307/308
cannot forward the client_secret body to a non-HTTPS host.
@PascalThuet PascalThuet requested a review from mnriem as a code owner June 23, 2026 21:57
Assisted-by: Codex (model: GPT-5, autonomous)
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.

1 participant