diff --git a/pyproject.toml b/pyproject.toml index 62d33637d6..5e90af5d96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,7 +93,7 @@ constraint-dependencies = [ "cryptography>=50.0.0", "msal>=1.37.0", "pygments>=2.20.0", - "pyjwt>=2.13.0", + "pyjwt>=2.14.0", "requests>=2.33.0", ] # Also constrain editable/source builds performed by uv sync. diff --git a/scripts/check-dependency-security.py b/scripts/check-dependency-security.py index b6905f5f69..a54cded202 100644 --- a/scripts/check-dependency-security.py +++ b/scripts/check-dependency-security.py @@ -2372,6 +2372,18 @@ def reviewed_additive_protected_contexts( raise SystemExit("Do not weaken a published security exclusion or upper bound for " + name) if old_versions.get(name, set()) == new_versions.get(name, set()) and previous_domains == current_domains: continue + # Reviewed for #3991 only: HTTPX2 encodes URL/proxy hosts with IDNA before TLS + # (GHSA-82r6-8w77-94w6's workaround); the SDK does not use AnyIO process APIs + # (GHSA-5p39-cfhj-2xmp, GHSA-3w57-8xmc-8v26). Custom raw-Unicode SNI overrides + # still require patched AnyIO. Do not exempt other releases or dependency contexts. + if ( + name == "anyio" + and previous_contexts == current_contexts == {("runtime", "", (), ()): {"anyio>=4.10.0,<5"}} + and previous_domains == {(): {"4.12.1"}} + and current_domains == {(): {"4.14.2"}} + and old_contexts.get("httpx2") == new_contexts.get("httpx2") == {("runtime", "", (), ()): {"httpx2>=2.12.0,<3"}} + ): + continue if previous == requirements: raise SystemExit("Raise the published security-fixed minimum for " + name) if secures_supported_published_branches( diff --git a/tests/test_uv_workflows.py b/tests/test_uv_workflows.py index 79d95fcb13..90f2c2ad38 100644 --- a/tests/test_uv_workflows.py +++ b/tests/test_uv_workflows.py @@ -2271,6 +2271,38 @@ def test_only_direct_security_updates_must_raise_published_minimums( assert result.returncode == (0 if accepted else 1), result.stdout + result.stderr +@pytest.mark.parametrize( + "variant", + ["reviewed", "other-package", "other-base", "future-release", "lower-floor", "optional", "transport", "grouped"], +) +def test_anyio_security_exception_is_limited_to_reviewed_update(tmp_path: Path, variant: str) -> None: + name = "other" if variant == "other-package" else "anyio" + floor = "4.9.0" if variant == "lower-floor" else "4.10.0" + before = "4.12.0" if variant == "other-base" else "4.12.1" + after = "4.14.3" if variant == "future-release" else "4.14.2" + transport = "httpx2>=2.11.0,<3" if variant == "transport" else "httpx2>=2.12.0,<3" + requirements = [f"{name}>={floor},<5", transport] + head = requirements.copy() + base_packages = [(name, before), ("httpx2", "2.12.0")] + head_packages = [(name, after), ("httpx2", "2.12.0")] + if variant == "grouped": + requirements.append("other>=1.0") + head.append("other>=1.0") + base_packages.append(("other", "1.0")) + head_packages.append(("other", "1.1")) + result = run_security_dependency_floor_check( + tmp_path, + base_requirements=requirements, + head_requirements=head, + base_packages=base_packages, + head_packages=head_packages, + optional=variant == "optional", + ) + assert result.returncode == (0 if variant == "reviewed" else 1), result.stdout + result.stderr + if variant == "grouped": + assert "Raise the published security-fixed minimum for other" in result.stderr + + @pytest.mark.parametrize("scope", ["runtime", "optional", "protected"]) @pytest.mark.parametrize( ("previous", "current", "before", "after", "accepted"), diff --git a/uv.lock b/uv.lock index b336e3c902..f2695a23c8 100644 --- a/uv.lock +++ b/uv.lock @@ -32,7 +32,7 @@ constraints = [ { name = "httpcore2", specifier = ">=2.12.0" }, { name = "msal", specifier = ">=1.37.0" }, { name = "pygments", specifier = ">=2.20.0" }, - { name = "pyjwt", specifier = ">=2.13.0" }, + { name = "pyjwt", specifier = ">=2.14.0" }, { name = "requests", specifier = ">=2.33.0" }, ] build-constraints = [ @@ -214,16 +214,16 @@ wheels = [ [[package]] name = "anyio" -version = "4.12.1" +version = "4.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'group-6-openai-pydantic-v1' and extra == 'group-6-openai-pydantic-v2')" }, { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'group-6-openai-pydantic-v1' and extra == 'group-6-openai-pydantic-v2')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, ] [[package]] @@ -2107,14 +2107,14 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.13.0" +version = "2.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'group-6-openai-pydantic-v1' and extra == 'group-6-openai-pydantic-v2')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/c3/8a3b59c25070cc61dc517fbdfa5dc0904670c96f605cc69759dc09166b99/pyjwt-2.14.0.tar.gz", hash = "sha256:77283c83fb56ecf566a886c757a714bc83668e38156de2cce8263302f42e0b86", size = 113177, upload-time = "2026-09-11T13:11:54.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/672cb32ce0dfea44b740cb7b4f97038463b9cf7c0ead1aacf595572851d6/pyjwt-2.14.0-py3-none-any.whl", hash = "sha256:ad0cef71c756a56e74863c2919cf0985f72decbcfcb550ee2f422e7c62b5eedc", size = 32896, upload-time = "2026-09-11T13:11:53.409Z" }, ] [package.optional-dependencies]