From fb76c963d9a61bc3b9cf7217ca83be6f4d117a7d Mon Sep 17 00:00:00 2001 From: Hetvi Thakar Date: Thu, 15 Oct 2026 19:33:16 -0700 Subject: [PATCH] python3-pyjwt: fix CVE-2026-102269 This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. [1] https://github.com/jpadilla/pyjwt/commit/e6f48401001609a8f99e71fcaf355fb895d508a8 [2] https://github.com/jpadilla/pyjwt/security/advisories/GHSA-hxm8-2xgr-2p9m Signed-off-by: Hetvi Thakar Signed-off-by: Anuj Mittal --- .../python/files/CVE-2026-102269.patch | 133 ++++++++++++++++++ .../python/python3-pyjwt_2.12.1.bb | 1 + 2 files changed, 134 insertions(+) create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-102269.patch diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-102269.patch b/meta-python/recipes-devtools/python/files/CVE-2026-102269.patch new file mode 100644 index 0000000000..ebf2d280cf --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-102269.patch @@ -0,0 +1,133 @@ +From e6f48401001609a8f99e71fcaf355fb895d508a8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Wed, 9 Sep 2026 09:46:01 -0400 +Subject: [PATCH] Reject non-canonical compact JWS segments + +CVE: CVE-2026-102269 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/e6f48401001609a8f99e71fcaf355fb895d508a8] + +(cherry picked from commit e6f48401001609a8f99e71fcaf355fb895d508a8) +Signed-off-by: Hetvi Thakar +--- + jwt/api_jws.py | 35 +++++++++++++++++++++++------------ + tests/test_api_jws.py | 11 +++++++++++ + tests/test_compressed_jwt.py | 8 ++++---- + 3 files changed, 38 insertions(+), 16 deletions(-) + +diff --git a/jwt/api_jws.py b/jwt/api_jws.py +index 5a91bce..2ee2f22 100644 +--- a/jwt/api_jws.py ++++ b/jwt/api_jws.py +@@ -28,6 +28,9 @@ if TYPE_CHECKING: + from .types import SigOptions + + _ALGORITHM_UNSET = object() ++_BASE64URL_ALPHABET = ( ++ b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" ++) + + + class PyJWS: +@@ -291,6 +294,23 @@ class PyJWS: + + return headers + ++ @staticmethod ++ def _decode_base64url_segment(segment: bytes, name: str) -> bytes: ++ if len(segment) % 4 == 1 or any( ++ character not in _BASE64URL_ALPHABET for character in segment ++ ): ++ raise DecodeError(f"Invalid {name} padding") ++ ++ try: ++ decoded = base64url_decode(segment) ++ except (TypeError, binascii.Error) as err: ++ raise DecodeError(f"Invalid {name} padding") from err ++ ++ if base64url_encode(decoded) != segment: ++ raise DecodeError(f"Invalid {name} padding") ++ ++ return decoded ++ + def _load(self, jwt: str | bytes) -> tuple[bytes, bytes, dict[str, Any], bytes]: + if isinstance(jwt, str): + jwt = jwt.encode("utf-8") +@@ -304,10 +324,7 @@ class PyJWS: + except ValueError as err: + raise DecodeError("Not enough segments") from err + +- try: +- header_data = base64url_decode(header_segment) +- except (TypeError, binascii.Error) as err: +- raise DecodeError("Invalid header padding") from err ++ header_data = self._decode_base64url_segment(header_segment, "header") + + try: + header: dict[str, Any] = json.loads(header_data) +@@ -331,15 +348,9 @@ class PyJWS: + ) + payload = b"" + else: +- try: +- payload = base64url_decode(payload_segment) +- except (TypeError, binascii.Error) as err: +- raise DecodeError("Invalid payload padding") from err ++ payload = self._decode_base64url_segment(payload_segment, "payload") + +- try: +- signature = base64url_decode(crypto_segment) +- except (TypeError, binascii.Error) as err: +- raise DecodeError("Invalid crypto padding") from err ++ signature = self._decode_base64url_segment(crypto_segment, "crypto") + + return (payload, signing_input, header, signature) + +diff --git a/tests/test_api_jws.py b/tests/test_api_jws.py +index 01de813..7e0a97b 100644 +--- a/tests/test_api_jws.py ++++ b/tests/test_api_jws.py +@@ -658,6 +658,17 @@ class TestJWS: + + assert "Invalid crypto padding" in str(exc.value) + ++ def test_decode_rejects_non_canonical_crypto_segment( ++ self, jws: PyJWS, payload: bytes ++ ) -> None: ++ secret = "a" * 32 ++ token = jws.encode(payload, secret, algorithm="HS256") ++ header, encoded_payload, signature = token.split(".") ++ mutated_token = ".".join((header, encoded_payload, f"{signature}!!!!")) ++ ++ with pytest.raises(DecodeError, match="Invalid crypto padding"): ++ jws.decode(mutated_token, secret, algorithms=["HS256"]) ++ + def test_decode_with_algo_none_should_fail( + self, jws: PyJWS, payload: bytes + ) -> None: +diff --git a/tests/test_compressed_jwt.py b/tests/test_compressed_jwt.py +index 1968cb4..51c61a8 100644 +--- a/tests/test_compressed_jwt.py ++++ b/tests/test_compressed_jwt.py +@@ -22,8 +22,8 @@ def test_decodes_complete_valid_jwt_with_compressed_payload() -> None: + # Buffer.from(pako.deflateRaw('{"hello": "world"}')).toString('base64') + example_jwt = ( + b"eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9" +- b".q1bKSM3JyVeyUlAqzy/KSVGqBQA=" +- b".08wHYeuh1rJXmcBcMrz6NxmbxAnCQp2rGTKfRNIkxiw=" ++ b".q1bKSM3JyVeyUlAqzy_KSVGqBQA" ++ b".AAn1elCJC5MQCYFwTwa2tjjtyLgqLUVU-Y1vFBsU8jo" + ) + decoded = CompressedPyJWT().decode_complete( + example_jwt, example_secret, algorithms=["HS256"] +@@ -33,7 +33,7 @@ def test_decodes_complete_valid_jwt_with_compressed_payload() -> None: + "header": {"alg": "HS256", "typ": "JWT"}, + "payload": example_payload, + "signature": ( +- b"\xd3\xcc\x07a\xeb\xa1\xd6\xb2W\x99\xc0\\2\xbc\xfa7" +- b"\x19\x9b\xc4\t\xc2B\x9d\xab\x192\x9fD\xd2$\xc6," ++ b"\x00\t\xf5zP\x89\x0b\x93\x10\t\x81pO\x06\xb6\xb68" ++ b"\xed\xc8\xb8*-ET\xf9\x8do\x14\x1b\x14\xf2:" + ), + } +-- +2.35.6 diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb index f7542cab2e..a159f2fbbb 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb @@ -12,6 +12,7 @@ SRC_URI += "file://CVE-2026-48522.patch \ file://CVE-2026-48526.patch \ file://CVE-2026-103001.patch \ file://CVE-2026-102274.patch \ + file://CVE-2026-102269.patch \ " SRC_URI[sha256sum] = "c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b"