Repository navigation
build(deps-dev): close httpx2 2.9.1 HIGH advisories (CVE-2026-84381, CVE-2026-84382) #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| # October 2026 HTTPX2 Transport Security Floor — Doctoring Record | ||
|
|
||
| ## Scope | ||
|
|
||
| The `trivy-fs` security gate reported three HIGH findings in | ||
| `services/account_unification/uv.lock`. All of them were in the development | ||
| HTTP transport. That transport consists of the direct development requirement | ||
| `httpx2` and its exact-coupled dependency `httpcore2`, both at 2.9.1: | ||
|
|
||
| | Advisory | Affected | Fixed | Effect | | ||
| |---|---|---|---| | ||
| | CVE-2026-84381 / GHSA-7mj9-2mp8-4m2p | `httpcore2` < 2.10.0; `httpx2` 2.6.0–2.9.1 | 2.10.0 | A `wss` origin through a SOCKS5 proxy does not start TLS. The handshake, headers, cookies, and frames cross the proxy in cleartext without certificate verification. | | ||
| | CVE-2026-84382 / GHSA-8xx6-hgc6-gc2m | `httpx2` < 2.12.0 | 2.12.0 | Each compressed network chunk is fully inflated before bounded streaming, so a hostile server can force very large intermediate allocations. | | ||
|
|
||
| This change moves both packages to 2.12.0 on the three canonical dependency | ||
| surfaces in one resolver execution. `httpx2` 2.12.0 requires | ||
| `httpcore2==2.12.0`. The resolver also adds `httpx2-jsfetch` 1.0 | ||
| (BSD-3-Clause), but only behind an `emscripten` platform marker. It is not | ||
| installed on Linux or macOS. | ||
|
|
||
| No application code, database schema, authentication policy, review | ||
| credential, or LLM integration changes. The account-unification runtime | ||
| requirement set (`requirements.lock`) does not contain `httpx2`. The packages | ||
| are test and development transport only. This lowers runtime exposure, but it | ||
| does not remove the need for a patched development graph, because the tests | ||
| and CI runners execute this transport. | ||
|
|
||
| ## Separate Dependabot proposals | ||
|
|
||
| Dependabot opened separate drafts for each package (#122, #124, #149, | ||
| #150). `httpx2` pins `httpcore2` exactly, so a fragment that updates one | ||
| package without the other cannot resolve. A `requirements-dev.txt` edit | ||
| without the matching `uv.lock` change is also not reproducible. This change | ||
| replaces those fragments with one coupled update. The behavior is consistent | ||
| with the grouped-update rationale in | ||
| `2026-08-dependency-and-packaging-refresh.md`. | ||
|
|
||
| ## Verification contract | ||
|
|
||
| `tests/test_dependency_security_floor.py` reads `pyproject.toml`, `uv.lock`, | ||
| and `requirements-dev.txt` directly. It preserves every matching package | ||
| entry and fails when any resolved or generated pin for `httpx2` or | ||
| `httpcore2` is below 2.12.0, or when the two lock representations disagree. | ||
| A baseline-negative control temporarily restored the dependency surfaces from | ||
| `main` after the test was written and produced the intended failures; the | ||
| original implementation order was dependency update, test creation, RED | ||
| control, then restored patched surfaces and GREEN. This was not an original | ||
| production-code tests-first sequence. | ||
|
|
||
| The patched graph was then verified with `uv lock --check`, a locked Python | ||
| 3.12.13 install, Ruff, 100% docstring coverage, compilation, documentation | ||
| contracts, 765 passing tests, 100% production statement/branch coverage, and | ||
| a local HIGH/CRITICAL Trivy scan with zero findings. An online `uv build` | ||
| attempt failed because the PyPI connection timed out while resolving the | ||
| already pinned build requirement `setuptools==84.0.0`; this is an environment | ||
| network failure, not a source or lock failure. The same build completed | ||
| successfully with `uv build --offline` from the retained local package cache. | ||
| These checks establish graph compatibility and package-surface consistency; | ||
| they do not claim that this repository executes HTTPX2 request or WebSocket | ||
| traffic. | ||
|
|
||
| ## Residual risk | ||
|
|
||
| A security floor proves that a known vulnerable release is not selected. It | ||
| does not prove that 2.12.0 has no other defects. Hosted `trivy-fs` and | ||
| dependency-review results on the exact pull request head remain | ||
| authoritative. | ||
|
|
||
| ## References — APA 7th | ||
|
|
||
| Aqua Security. (2026a). *CVE-2026-84381*. Aqua Vulnerability Database. | ||
| Retrieved October 6, 2026, from https://avd.aquasec.com/nvd/cve-2026-84381 | ||
|
|
||
| Aqua Security. (2026b). *CVE-2026-84382*. Aqua Vulnerability Database. | ||
| Retrieved October 6, 2026, from https://avd.aquasec.com/nvd/cve-2026-84382 | ||
|
|
||
| Astral Software, Inc. (2026). *Locking and syncing*. Retrieved August 6, | ||
| 2026, from https://docs.astral.sh/uv/concepts/projects/sync/ | ||
|
|
||
| Pydantic. (2026a). *HTTPX2 2.10.0* [Software release]. GitHub. | ||
| https://github.com/pydantic/httpx2/releases/tag/v2.10.0 | ||
|
|
||
| Pydantic. (2026b). *HTTPX2 2.12.0* [Software release]. GitHub. | ||
| https://github.com/pydantic/httpx2/releases/tag/v2.12.0 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
118 changes: 118 additions & 0 deletions
118
services/account_unification/tests/test_dependency_security_floor.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| """Locked dependency surfaces stay above published security fix versions. | ||
|
|
||
| The development dependency graph contains ``httpx2`` and its exact-coupled | ||
| ``httpcore2``. The graph had two HIGH advisories: CVE-2026-84381 (``wss`` | ||
| through SOCKS5 sent in cleartext, fixed in 2.10.0) and CVE-2026-84382 | ||
| (streaming decompression memory amplification, fixed in 2.12.0). These tests | ||
| read every matching entry from the reviewed declaration, the universal | ||
| ``uv.lock`` resolution, and the generated hash-locked development | ||
| requirements. They verify the dependency graph, not execution of HTTPX2 | ||
| request or WebSocket consumers. | ||
| """ | ||
| from __future__ import annotations | ||
|
|
||
| import re | ||
| import tomllib | ||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
|
|
||
| SERVICE_ROOT = Path(__file__).resolve().parents[1] | ||
|
|
||
| SECURITY_FLOORS = { | ||
| "httpx2": (2, 12, 0), | ||
| "httpcore2": (2, 12, 0), | ||
| } | ||
|
|
||
|
|
||
| def _version_tuple(version: str) -> tuple[int, ...]: | ||
| """Convert a plain release version such as ``2.12.0`` into comparable integers.""" | ||
| return tuple(int(part) for part in version.split(".")) | ||
|
|
||
|
|
||
| def _versions_from_lock( | ||
| lock: dict[str, list[dict[str, str]]], | ||
| ) -> dict[str, list[str]]: | ||
| """Collect every resolved version, including platform-specific lock rows.""" | ||
| versions: dict[str, list[str]] = {} | ||
| for package in lock["package"]: | ||
| versions.setdefault(package["name"], []).append(package["version"]) | ||
| return versions | ||
|
|
||
|
|
||
| def _versions_from_requirements(text: str) -> dict[str, list[str]]: | ||
| """Collect every exact requirement pin, including conditional pins.""" | ||
| versions: dict[str, list[str]] = {} | ||
| for match in re.finditer( | ||
| r"^([A-Za-z0-9_.-]+)==([0-9.]+)", text, re.MULTILINE | ||
| ): | ||
| versions.setdefault(match.group(1).lower(), []).append(match.group(2)) | ||
| return versions | ||
|
|
||
|
|
||
| def _locked_versions() -> dict[str, list[str]]: | ||
| """Return every resolved version for each package name in ``uv.lock``.""" | ||
| lock = tomllib.loads((SERVICE_ROOT / "uv.lock").read_text(encoding="utf-8")) | ||
| return _versions_from_lock(lock) | ||
|
|
||
|
|
||
| def _requirements_dev_versions() -> dict[str, list[str]]: | ||
| """Return every exact pin for each package in generated dev requirements.""" | ||
| text = (SERVICE_ROOT / "requirements-dev.txt").read_text(encoding="utf-8") | ||
| return _versions_from_requirements(text) | ||
|
|
||
|
|
||
| def _assert_security_floor( | ||
| package_name: str, versions: dict[str, list[str]] | ||
| ) -> None: | ||
| """Reject every package version below its published security floor.""" | ||
| assert package_name in versions | ||
| assert all( | ||
| _version_tuple(version) >= SECURITY_FLOORS[package_name] | ||
| for version in versions[package_name] | ||
| ) | ||
|
|
||
|
|
||
| def test_security_floor_checks_every_conditional_requirement_pin() -> None: | ||
| """A safe final pin must not hide an earlier vulnerable conditional pin.""" | ||
| versions = _versions_from_requirements( | ||
| """ | ||
| httpx2==2.9.1 ; sys_platform == 'win32' | ||
| httpx2==2.12.0 ; sys_platform != 'win32' | ||
| httpcore2==2.9.1 ; sys_platform == 'win32' | ||
| httpcore2==2.12.0 ; sys_platform != 'win32' | ||
| """ | ||
| ) | ||
| assert versions["httpx2"] == ["2.9.1", "2.12.0"] | ||
| assert versions["httpcore2"] == ["2.9.1", "2.12.0"] | ||
| with pytest.raises(AssertionError): | ||
| _assert_security_floor("httpx2", versions) | ||
| with pytest.raises(AssertionError): | ||
| _assert_security_floor("httpcore2", versions) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("package_name", sorted(SECURITY_FLOORS)) | ||
| def test_universal_lock_resolves_patched_transport(package_name: str) -> None: | ||
| """``uv.lock`` must not resolve a release affected by the HTTPX2 advisories.""" | ||
| _assert_security_floor(package_name, _locked_versions()) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("package_name", sorted(SECURITY_FLOORS)) | ||
| def test_generated_dev_requirements_pin_patched_transport(package_name: str) -> None: | ||
| """The hash-locked pip surface must agree with the patched universal lock.""" | ||
| pinned = _requirements_dev_versions() | ||
| locked = _locked_versions() | ||
| assert pinned[package_name] == locked[package_name] | ||
| _assert_security_floor(package_name, pinned) | ||
|
|
||
|
|
||
| def test_direct_declaration_pins_patched_httpx2() -> None: | ||
| """The reviewed ``pyproject.toml`` dev pin is the patched release, not the lock alone.""" | ||
| project = tomllib.loads((SERVICE_ROOT / "pyproject.toml").read_text(encoding="utf-8")) | ||
| pins = [ | ||
| requirement | ||
| for requirement in project["project"]["optional-dependencies"]["dev"] | ||
| if requirement.startswith("httpx2==") | ||
| ] | ||
| assert len(pins) == 1 | ||
| assert _version_tuple(pins[0].split("==", 1)[1]) >= SECURITY_FLOORS["httpx2"] |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔍 Exact-head coverage remains unverified
The cited full-suite coverage predates the final commit. The repository requires 100% production statement and branch coverage on the exact head.
Was this helpful? React with 👍 or 👎 to provide feedback.