Version in base suite: 2.10.1-2 Base version: pyjwt_2.10.1-2 Target version: pyjwt_2.10.1-2+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/pyjwt/pyjwt_2.10.1-2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/pyjwt/pyjwt_2.10.1-2+deb13u1.dsc changelog | 11 + patches/Merge-commit-from-fork.patch | 210 +++++++++++++++++++++++++++++++++++ patches/series | 1 3 files changed, 222 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpiib1l_rl/pyjwt_2.10.1-2.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpiib1l_rl/pyjwt_2.10.1-2+deb13u1.dsc: no acceptable signature found diff -Nru pyjwt-2.10.1/debian/changelog pyjwt-2.10.1/debian/changelog --- pyjwt-2.10.1/debian/changelog 2025-01-07 14:53:38.000000000 +0000 +++ pyjwt-2.10.1/debian/changelog 2026-04-28 14:43:00.000000000 +0000 @@ -1,3 +1,14 @@ +pyjwt (2.10.1-2+deb13u1) trixie-security; urgency=medium + + * Team upload + * Fix CVE-2026-32597: PyJWT did not validate the crit (Critical) Header + Parameter defined in RFC 7515 §4.1.11. When a JWS token contains a crit + array listing extensions that PyJWT does not understand, the library + accepts the token instead of rejecting it. This violates the MUST + requirement in the RFC. (Closes: #1130662) + + -- Jochen Sprickerhof Tue, 28 Apr 2026 16:43:00 +0200 + pyjwt (2.10.1-2) unstable; urgency=medium * Team upload diff -Nru pyjwt-2.10.1/debian/patches/Merge-commit-from-fork.patch pyjwt-2.10.1/debian/patches/Merge-commit-from-fork.patch --- pyjwt-2.10.1/debian/patches/Merge-commit-from-fork.patch 1970-01-01 00:00:00.000000000 +0000 +++ pyjwt-2.10.1/debian/patches/Merge-commit-from-fork.patch 2026-04-28 14:43:00.000000000 +0000 @@ -0,0 +1,210 @@ +From: =?utf-8?q?Jos=C3=A9_Padilla?= +Date: Thu, 12 Mar 2026 12:46:08 -0400 +Subject: Merge commit from fork +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit + +Co-authored-by: José Padilla + + +Origin: backport, https://github.com/jpadilla/pyjwt/commit/051ea341b5573fe3edcd53042f347929b92c2b92 +--- + jwt/api_jws.py | 27 +++++++++++++- + tests/test_api_jws.py | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++ + tests/test_api_jwt.py | 18 +++++++++ + 3 files changed, 143 insertions(+), 2 deletions(-) + +diff --git a/jwt/api_jws.py b/jwt/api_jws.py +index 654ee0b..db2c80f 100644 +--- a/jwt/api_jws.py ++++ b/jwt/api_jws.py +@@ -137,7 +137,7 @@ class PyJWS: + header: dict[str, Any] = {"typ": self.header_typ, "alg": algorithm_} + + if headers: +- self._validate_headers(headers) ++ self._validate_headers(headers, encoding=True) + header.update(headers) + + if not header["typ"]: +@@ -208,6 +208,8 @@ class PyJWS: + + payload, signing_input, header, signature = self._load(jwt) + ++ self._validate_headers(header) ++ + if header.get("b64", True) is False: + if detached_payload is None: + raise DecodeError( +@@ -327,14 +329,35 @@ class PyJWS: + if not alg_obj.verify(signing_input, prepared_key, signature): + raise InvalidSignatureError("Signature verification failed") + +- def _validate_headers(self, headers: dict[str, Any]) -> None: ++ # Extensions that PyJWT actually understands and supports ++ _supported_crit: set[str] = {"b64"} ++ ++ def _validate_headers( ++ self, headers: dict[str, Any], *, encoding: bool = False ++ ) -> None: + if "kid" in headers: + self._validate_kid(headers["kid"]) ++ if not encoding and "crit" in headers: ++ self._validate_crit(headers) + + def _validate_kid(self, kid: Any) -> None: + if not isinstance(kid, str): + raise InvalidTokenError("Key ID header parameter must be a string") + ++ def _validate_crit(self, headers: dict[str, Any]) -> None: ++ crit = headers["crit"] ++ if not isinstance(crit, list) or len(crit) == 0: ++ raise InvalidTokenError("Invalid 'crit' header: must be a non-empty list") ++ for ext in crit: ++ if not isinstance(ext, str): ++ raise InvalidTokenError("Invalid 'crit' header: values must be strings") ++ if ext not in self._supported_crit: ++ raise InvalidTokenError(f"Unsupported critical extension: {ext}") ++ if ext not in headers: ++ raise InvalidTokenError( ++ f"Critical extension '{ext}' is missing from headers" ++ ) ++ + + _jws_global_obj = PyJWS() + encode = _jws_global_obj.encode +diff --git a/tests/test_api_jws.py b/tests/test_api_jws.py +index 3efdc0d..f1c421f 100644 +--- a/tests/test_api_jws.py ++++ b/tests/test_api_jws.py +@@ -900,3 +900,103 @@ class TestJWS: + ) + assert len(record) == 1 + assert "foo" in str(record[0].message) ++ ++ def test_decode_rejects_unknown_crit_extension( ++ self, jws: PyJWS, payload: bytes ++ ) -> None: ++ secret = "secret" ++ token = jws.encode( ++ payload, ++ secret, ++ algorithm="HS256", ++ headers={"crit": ["x-custom-policy"], "x-custom-policy": "require-mfa"}, ++ ) ++ ++ with pytest.raises(InvalidTokenError, match="Unsupported critical extension"): ++ jws.decode(token, secret, algorithms=["HS256"]) ++ ++ def test_decode_rejects_empty_crit(self, jws: PyJWS, payload: bytes) -> None: ++ secret = "secret" ++ token = jws.encode( ++ payload, ++ secret, ++ algorithm="HS256", ++ headers={"crit": []}, ++ ) ++ ++ with pytest.raises(InvalidTokenError, match="must be a non-empty list"): ++ jws.decode(token, secret, algorithms=["HS256"]) ++ ++ def test_decode_rejects_non_list_crit(self, jws: PyJWS, payload: bytes) -> None: ++ secret = "secret" ++ token = jws.encode( ++ payload, ++ secret, ++ algorithm="HS256", ++ headers={"crit": "b64"}, ++ ) ++ ++ with pytest.raises(InvalidTokenError, match="must be a non-empty list"): ++ jws.decode(token, secret, algorithms=["HS256"]) ++ ++ def test_decode_rejects_crit_with_non_string_values( ++ self, jws: PyJWS, payload: bytes ++ ) -> None: ++ secret = "secret" ++ token = jws.encode( ++ payload, ++ secret, ++ algorithm="HS256", ++ headers={"crit": [123]}, ++ ) ++ ++ with pytest.raises(InvalidTokenError, match="values must be strings"): ++ jws.decode(token, secret, algorithms=["HS256"]) ++ ++ def test_decode_rejects_crit_extension_missing_from_header( ++ self, jws: PyJWS, payload: bytes ++ ) -> None: ++ secret = "secret" ++ token = jws.encode( ++ payload, ++ secret, ++ algorithm="HS256", ++ headers={"crit": ["b64"]}, ++ ) ++ ++ with pytest.raises(InvalidTokenError, match="missing from headers"): ++ jws.decode(token, secret, algorithms=["HS256"]) ++ ++ def test_decode_accepts_supported_crit_extension( ++ self, jws: PyJWS, payload: bytes ++ ) -> None: ++ secret = "secret" ++ token = jws.encode( ++ payload, ++ secret, ++ algorithm="HS256", ++ headers={"crit": ["b64"], "b64": False}, ++ is_payload_detached=True, ++ ) ++ ++ decoded = jws.decode( ++ token, ++ secret, ++ algorithms=["HS256"], ++ detached_payload=payload, ++ ) ++ assert decoded == payload ++ ++ def test_get_unverified_header_rejects_unknown_crit( ++ self, jws: PyJWS, payload: bytes ++ ) -> None: ++ secret = "secret" ++ token = jws.encode( ++ payload, ++ secret, ++ algorithm="HS256", ++ headers={"crit": ["x-unknown"], "x-unknown": "value"}, ++ ) ++ ++ with pytest.raises(InvalidTokenError, match="Unsupported critical extension"): ++ jws.get_unverified_header(token) +diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py +index 4b4bdcd..8dcb218 100644 +--- a/tests/test_api_jwt.py ++++ b/tests/test_api_jwt.py +@@ -943,3 +943,21 @@ class TestJWT: + + with pytest.raises(InvalidJTIError): + jwt.decode(token, secret, algorithms=["HS256"]) ++ ++ # -------------------- Crit Header Tests -------------------- ++ ++ def test_decode_rejects_token_with_unknown_crit_extension(self, jwt: PyJWT) -> None: ++ """RFC 7515 §4.1.11: tokens with unsupported critical extensions MUST be rejected.""" ++ from jwt.exceptions import InvalidTokenError ++ ++ secret = "secret" ++ payload = {"sub": "attacker", "role": "admin"} ++ token = jwt.encode( ++ payload, ++ secret, ++ algorithm="HS256", ++ headers={"crit": ["x-custom-policy"], "x-custom-policy": "require-mfa"}, ++ ) ++ ++ with pytest.raises(InvalidTokenError, match="Unsupported critical extension"): ++ jwt.decode(token, secret, algorithms=["HS256"]) diff -Nru pyjwt-2.10.1/debian/patches/series pyjwt-2.10.1/debian/patches/series --- pyjwt-2.10.1/debian/patches/series 2025-01-04 08:03:06.000000000 +0000 +++ pyjwt-2.10.1/debian/patches/series 2026-04-28 14:43:00.000000000 +0000 @@ -1 +1,2 @@ docs-Use-packaged-intersphinx-resources.patch +Merge-commit-from-fork.patch