From patchwork Thu Nov 5 04:56:07 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 102952 Received: from rcdn-iport-8.cisco.com (rcdn-iport-8.cisco.com [173.37.86.79]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.27342.1793854576520776416 for ; Wed, 04 Nov 2026 20:56:16 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=U8upZV75; spf=pass (domain: cisco.com, ip: 173.37.86.79, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=13453; q=dns/txt; s=iport01; t=1793854576; x=1795064176; h=from:to:subject:date:message-id:mime-version: content-transfer-encoding; bh=l4jbizkZc5D0f07bmC0rFVNWlnKK6Af2TkqDt+AaWJs=; b=U8upZV75PDnZMfhOdZgw94dMxthrHFK6hdm20GGoyQd5v6n0dtTXfJ2a rgUlJhqkfpjvxVykQ5SAoBMneqjQGqV9Z6P1Ry/C4mlguZPOaDfqm1aaZ f5zr5pnZKaqURMqEjGIBe005R5UzynuauxbSsU91bdIcwVJgT2cDJN9im f30twXuhOPMUu81WEVviDIzlTdF+wfcqAKpfrYodSHZieVaEqing9VIW4 8w1tJfr4R/RhpiJGP9eun0ChwZPWh5ckiu8fuOyOphgUArEhD2mbb51RD VRrAFO1/AhhUbWIFMtw+iirFAPCvFqoHnysKGFF6LVvBTxdHt6gTDRNqt w==; X-CSE-ConnectionGUID: of3gqFuASsqtvkuZW8KYVA== X-CSE-MsgGUID: aoHsUgstRfO+AILOfME5gw== X-IPAS-Result: A0APAACOCuxq/5H/Ja1aGgEBAQEBAQEBAQEDAQEBARIBAQEBAgIBAQEBgX8CAQEBAQsBglZ1YUJJA4RUj1OCIYtolDoPAQEBD0QNBAEBkyMCJjcGDgECBAMCAwEBAQEBAQEBAQEBAQoBAQUBAQECAQcFgQ4Thk8NkBIBKQQLARgBWQMBAgMCJgIiCyMYCYMCAYI6AzcCARG8R3p/M4EBgykBgVTYTA2CWgELFAGBCi4BhT+CfyABhQNdGAFEgxqBHycbG4FygRWDaYEFSwFOQgKFPIJqBIIiehKBWh6MegKBUoZwSIECHANZLAFVEw0KCwcFgWgDNRIqFW4yHYEjPhcvWBsGBYEdgR6BEoEHXYErKF4hgQiBdgKCUYIAAgFJQQ4HRVMJJUMSRyQiCBIJARMaLAqBGjU8CSY/FQxkCxoFYQgCDQxyORAwHiQvBRwmQBsKGBgNSBEsNxUZBD0BbgeSL4JkLWQXEwEHJBdoEg+BDB4ckwUyj2SCIYE1nmtxCiiDeIwkjz6FfBozhASBV5JCklQLmH2CWYsxhAqRVwgbTYRpgX4mgVlwFYMiCUoZD44rDguDRBzOUyM1AgkDLwEBBwIHAQwBAwuBaJACLYFPAQE IronPort-Data: A9a23:G5wt/ql0Y469cug7AIk1PmXo5gzQJ0RdPkR7XQ2eYbSJt1+Wr1Gzt xJLUDiAa/vfNDb9fNkiaoS+/UgO6MWBm9FiGgRo+C88RltH+JHPbTi7wugcHM8zwunrFh8PA xA2M4GYRCwMZiaC4Errav6+/SEUOZigHtLUEPTDNj16WThqQSIgjQMLs+Mii+aEu/Dha++2k Y20+ZK31GONgWYubDtNs/7b8nuDgdyr0N8mlg1mDRx0lAe2e0k9VPo3Oay3Jn3kdYhYdsbSb /rD1ryw4lTC9B4rDN6/+p6jGqHdauePVeQmoiM+t5mK2nCulARrukoIHKZ0hXNsttm8t4sZJ OOhGnCHYVxB0qXkwIzxWvTDes10FfUuFLTveRBTvSEPpqHLWyOE/hlgMK05FYoWoeluL3xnz ucBNC4OcyK818GH/pvuH4GAhux7RCXqFJkUtnclyXTSCuwrBMiTBa7L/tRfmjw3g6iiH96HO JFfMmUpNkmdJUQTZz/7C7pm9AusrnnjczRboUi9rqss6G+Vxwt0uFToGIeOJIbUFJgLwC50o ErP+ETiPx4BKOaR7nms01aLiMriwCzkDdd6+LqQs6QCbEeo7msLBRsbUFG2rfW0hguyVsxSL 2QQ+zEytu417EGtQ9z3UhG0rXLCuQQTM+e8CMUg4w2Lj66R6AGDCy1cHnhKaccts4k9QjlCO kK1ou4FzAdH6NW9IU9xPJ/Nxd9uEUD59VM/WBI= IronPort-HdrOrdr: A9a23:tDrzcaM7yBBNycBcTvejsMiBIKoaSvp037BN7TEVdfU1SL37qy nAppomPHPP5Qr5O0tQ+uxoRpPgfZq0z/cciuMs1NyZMzUO1lHFEGgb1+vf6gylPTHi/ehA0q olWa1/BNrsSWVet6/BkW2F+xJK+qjgzEhu7t2uqEtQcQ== X-Talos-CUID: 9a23:CwX6BmmBt/78rfpNT47IJjNMcD/XOXrTl3jJDVKRMn1wVKOWVG2vqIBVreM7zg== X-Talos-MUID: 9a23:JB8A8Atvua5fDTVF2s2njTZCL/pn+LiUBmdOuLQinMehGHF/JGLI X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,196,1787011200"; d="scan'208";a="536269566" Received: from rcdn-l-core-08.cisco.com ([173.37.255.145]) by rcdn-iport-8.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 05 Nov 2026 04:56:15 +0000 Received: from sjc-ads-2020.cisco.com (sjc-ads-2020.cisco.com [171.70.61.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "ciscoit-managed-infra-smtp-auth.cisco.com", Issuer "Internal Private TLS SubCA" (verified OK)) by rcdn-l-core-08.cisco.com (Postfix) with ESMTPS id 58877180001C2 for ; Thu, 5 Nov 2026 04:56:15 +0000 (GMT) Received: by sjc-ads-2020.cisco.com (Postfix, from userid 1887505) id F0128CC124B; Wed, 4 Nov 2026 20:56:14 -0800 (PST) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Subject: [meta-python][scarthgap][PATCH 1/8] python3-pyjwt: fix CVE-2026-102269 Date: Wed, 4 Nov 2026 20:56:07 -0800 Message-Id: <20261105045614.2677776-1-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 MIME-Version: 1.0 X-Outbound-Client-TLS: VERIFIED;sjc-ads-2020.cisco.com [171.70.61.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256;ciscoit-managed-infra-smtp-auth.cisco.com X-Outbound-SMTP-Client: 171.70.61.208, sjc-ads-2020.cisco.com X-Outbound-Node: rcdn-l-core-08.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Thu, 05 Nov 2026 04:56:24 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/131854 From: Hetvi Thakar This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. It also applies the upstream follow-up [3] to accept canonical Base64URL padding while continuing to reject malformed JWS segments. [1] https://github.com/jpadilla/pyjwt/commit/e6f48401001609a8f99e71fcaf355fb895d508a8 [2] https://github.com/jpadilla/pyjwt/security/advisories/GHSA-hxm8-2xgr-2p9m [3] https://github.com/jpadilla/pyjwt/commit/7bf32526738fe837387bdedd4849a4a525c33a79 Signed-off-by: Hetvi Thakar --- .../python3-pyjwt/CVE-2026-102269_p1.patch | 138 ++++++++++++++ .../python3-pyjwt/CVE-2026-102269_p2.patch | 169 ++++++++++++++++++ .../python/python3-pyjwt_2.8.0.bb | 2 + 3 files changed, 309 insertions(+) create mode 100644 meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102269_p1.patch create mode 100644 meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102269_p2.patch diff --git a/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102269_p1.patch b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102269_p1.patch new file mode 100644 index 0000000000..68fe3f4edc --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102269_p1.patch @@ -0,0 +1,138 @@ +From cfcc99413c41c6b59a6de6fc222291367d8922b2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Wed, 14 Oct 2026 02:38:35 -0700 +Subject: [PATCH] Reject non-canonical compact JWS segments + +CVE: CVE-2026-102269 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/e6f48401001609a8f99e71fcaf355fb895d508a8] + +Backport Changes: +- Added a separating blank line after _BASE64URL_ALPHABET to retain two + blank lines before PyJWS; PyJWT 2.8.0 lacks the adjacent upstream + _ALGORITHM_UNSET constant. + +Signed-off-by: Hetvi Thakar +--- + jwt/api_jws.py | 36 ++++++++++++++++++++++++------------ + tests/test_api_jws.py | 11 +++++++++++ + tests/test_compressed_jwt.py | 8 ++++---- + 3 files changed, 39 insertions(+), 16 deletions(-) + +diff --git a/jwt/api_jws.py b/jwt/api_jws.py +index 0d5ab2b..10618b7 100644 +--- a/jwt/api_jws.py ++++ b/jwt/api_jws.py +@@ -23,6 +23,10 @@ from .warnings import RemovedInPyjwt3Warning + if TYPE_CHECKING: + from .algorithms import AllowedPrivateKeys, AllowedPublicKeys + ++_BASE64URL_ALPHABET = ( ++ b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" ++) ++ + + class PyJWS: + header_typ = "JWT" +@@ -248,6 +252,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") +@@ -261,10 +282,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 = json.loads(header_data) +@@ -283,15 +301,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 29f84a7..3b05845 100644 +--- a/tests/test_api_jws.py ++++ b/tests/test_api_jws.py +@@ -478,6 +478,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, payload): + jws_message = jws.encode(payload, key=None, algorithm=None) + +diff --git a/tests/test_compressed_jwt.py b/tests/test_compressed_jwt.py +index 21fac3f..7603633 100644 +--- a/tests/test_compressed_jwt.py ++++ b/tests/test_compressed_jwt.py +@@ -20,8 +20,8 @@ def test_decodes_complete_valid_jwt_with_compressed_payload(): + # 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"] +@@ -31,7 +31,7 @@ def test_decodes_complete_valid_jwt_with_compressed_payload(): + "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/CVE-2026-102269_p2.patch b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102269_p2.patch new file mode 100644 index 0000000000..dcd0daebd2 --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102269_p2.patch @@ -0,0 +1,169 @@ +From 7bf32526738fe837387bdedd4849a4a525c33a79 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Mon, 28 Sep 2026 12:22:34 -0400 +Subject: [PATCH] Accept canonical Base64URL padding in JWT segments (#1216) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +* Accept trailing Base64URL padding when decoding JWS segments + +The canonical encode/decode round-trip rejected AWS ALB tokens that +include standard '=' padding. Keep rejecting non-alphabet junk such +as '!!!!', but allow trailing padding so those tokens verify. + +Fixes #1209 + +* [pre-commit.ci] auto fixes from pre-commit.com hooks + +for more information, see https://pre-commit.ci + +* test: cover padded Base64URL segment validation + +* test: verify padded ALB-style ES256 JWT decoding + +--------- + +Co-authored-by: jaideeppyne +Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> +Co-authored-by: José Padilla + +CVE: CVE-2026-102269 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/7bf32526738fe837387bdedd4849a4a525c33a79] + +Backport Changes: +- Omitted the upstream release changelog update, which documents a later version. + +Signed-off-by: Hetvi Thakar +--- + jwt/api_jws.py | 21 +++++++++++++++++---- + tests/test_api_jws.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ + tests/test_api_jwt.py | 12 ++++++++++-- + 3 files changed, 71 insertions(+), 6 deletions(-) + +diff --git a/jwt/api_jws.py b/jwt/api_jws.py +index 10618b7..acf04e2 100644 +--- a/jwt/api_jws.py ++++ b/jwt/api_jws.py +@@ -254,17 +254,30 @@ class PyJWS: + + @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 ++ # Accept trailing '=' used by some issuers (e.g. AWS ALB). Still ++ # reject non-alphabet junk such as '!!!!' (GHSA-hxm8-2xgr-2p9m). ++ padding = 0 ++ stripped = segment ++ while stripped.endswith(b"="): ++ stripped = stripped[:-1] ++ padding += 1 ++ if padding > 2: ++ raise DecodeError(f"Invalid {name} padding") ++ ++ if padding and len(segment) % 4 != 0: ++ raise DecodeError(f"Invalid {name} padding") ++ ++ if len(stripped) % 4 == 1 or any( ++ character not in _BASE64URL_ALPHABET for character in stripped + ): + raise DecodeError(f"Invalid {name} padding") + + try: +- decoded = base64url_decode(segment) ++ decoded = base64url_decode(stripped) + except (TypeError, binascii.Error) as err: + raise DecodeError(f"Invalid {name} padding") from err + +- if base64url_encode(decoded) != segment: ++ if base64url_encode(decoded) != stripped: + raise DecodeError(f"Invalid {name} padding") + + return decoded +diff --git a/tests/test_api_jws.py b/tests/test_api_jws.py +index 1ceb8df..c903c64 100644 +--- a/tests/test_api_jws.py ++++ b/tests/test_api_jws.py +@@ -489,6 +489,50 @@ class TestJWS: + with pytest.raises(DecodeError, match="Invalid crypto padding"): + jws.decode(mutated_token, secret, algorithms=["HS256"]) + ++ def test_decode_accepts_padded_crypto_segment( ++ self, jws: PyJWS, payload: bytes ++ ) -> None: ++ secret = "a" * 32 ++ token = jws.encode(payload, secret, algorithm="HS256") ++ header, encoded_payload, signature = token.split(".") ++ pad = "=" * ((4 - len(signature) % 4) % 4) ++ assert pad, ( ++ "HS256 signatures should need padding when restored to 4-char groups" ++ ) ++ padded_token = ".".join((header, encoded_payload, signature + pad)) ++ ++ assert jws.decode(padded_token, secret, algorithms=["HS256"]) == payload ++ ++ def test_decode_rejects_embedded_padding_in_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, signature[:2] + "=" + signature[2:]) ++ ) ++ ++ with pytest.raises(DecodeError, match="Invalid crypto padding"): ++ jws.decode(mutated_token, secret, algorithms=["HS256"]) ++ ++ @pytest.mark.parametrize( ++ ("segment", "decoded"), ++ [(b"Zg", b"f"), (b"Zg==", b"f"), (b"Zm8=", b"fo")], ++ ) ++ def test_decode_accepts_canonical_base64url_padding( ++ self, segment: bytes, decoded: bytes ++ ) -> None: ++ assert PyJWS._decode_base64url_segment(segment, "crypto") == decoded ++ ++ @pytest.mark.parametrize( ++ "segment", ++ [b"Zg===", b"Zg=", b"a", b"Z?==", b"Zh", b"Zh=="], ++ ) ++ def test_decode_rejects_invalid_base64url_padding(self, segment: bytes) -> None: ++ with pytest.raises(DecodeError, match="Invalid crypto padding"): ++ PyJWS._decode_base64url_segment(segment, "crypto") ++ + def test_decode_with_algo_none_should_fail(self, jws, payload): + jws_message = jws.encode(payload, key=None, algorithm=None) + +diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py +index 618b60f..dc07b24 100644 +--- a/tests/test_api_jwt.py ++++ b/tests/test_api_jwt.py +@@ -282,7 +282,12 @@ class TestJWT: + # encoding / decoding operations equally (causing tests + # to still pass). + @crypto_required +- def test_decodes_valid_es256_jwt(self, jwt): ++ @pytest.mark.parametrize( ++ "signature_padding", [b"", b"=="], ids=["compact", "alb-padded"] ++ ) ++ def test_decodes_valid_es256_jwt( ++ self, jwt: PyJWT, signature_padding: bytes ++ ) -> None: + example_payload = {"hello": "world"} + with open(key_path("testkey_ec.pub")) as fp: + example_pubkey = fp.read() +@@ -292,7 +297,10 @@ class TestJWT: + b"d2SsX8hhlnWelQFmPFSf_JzC2EbLnar92t-bXsDovzxp25ExazrVHkfPkQ" + ) + +- decoded_payload = jwt.decode(example_jwt, example_pubkey, algorithms=["ES256"]) ++ # AWS ALB can include standard padding on its ES256 signature (#1209). ++ decoded_payload = jwt.decode( ++ example_jwt + signature_padding, example_pubkey, algorithms=["ES256"] ++ ) + assert decoded_payload == example_payload + + # 'Control' RSA JWT created by another library. +-- +2.35.6 diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb index 3804d8ab72..5a1dac4df6 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb @@ -11,6 +11,8 @@ SRC_URI += " \ file://CVE-2026-48524.patch \ file://CVE-2026-48525.patch \ file://CVE-2026-48526.patch \ + file://CVE-2026-102269_p1.patch \ + file://CVE-2026-102269_p2.patch \ " SRC_URI[sha256sum] = "57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de" From patchwork Thu Nov 5 04:56:08 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 102951 Received: from rcdn-iport-5.cisco.com (rcdn-iport-5.cisco.com [173.37.86.76]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.27343.1793854576557325274 for ; Wed, 04 Nov 2026 20:56:16 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=F+XFB1Qh; spf=pass (domain: cisco.com, ip: 173.37.86.76, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=9503; q=dns/txt; s=iport01; t=1793854576; x=1795064176; h=from:to:subject:date:message-id:in-reply-to:references: mime-version:content-transfer-encoding; bh=zesxGz9gfAYqv2ruZEnlEUbFQ5DSrajKFdK2o/PS0Xg=; b=F+XFB1QhUSSQKYNauxMW9Pk/NBZjOcRJ0ypG/c3DC4eWv4B9N7508Kh4 pHXxas6DHN4RQS8IiGwc7q0CMNHNOryjQpFmf0IxqkPZQWltI4YffTG/7 6DlENHXKO8p1cRv8vNOzwaTAcijmvUYUfW+Khs0uf35ciOc3X2bGXjAFu A2YhzUTmUaxq7+fvvlPNdHB2mj6+Vd9qr+08PT9h+zAXW4acGjCnb4U2v ULfi48qtUMPgSbaIB5wHf0+XqvucucmjUzsEddokRZWj9jC2p5UJab7JI ziQGrqjoNfy1p3soGislr5xuFqiSSfgqwIHjloo0zTuCJPf49mLZ/4amx w==; X-CSE-ConnectionGUID: xXx+MucwRT2YseKQiCYLiQ== X-CSE-MsgGUID: SlEMFhuNRCemDMtKLMiHOg== X-IPAS-Result: A0AFAABGC+xq/5L/Ja1aEwEBBQEBAQEBAQEBAQEDAQEBARIBAQEBAgIBAQEBgX0EAQEBAQsBglZ1YUJJA5QngiEDgROCCpsEgX4PAQEBD0QNBAEBhQUCjhwCJjUIDgECBAMCAwEBAQEBAQEBAQEBAQoBAQUBAQECAQcFgQ4Thk8NkBIBAgEDJwsBGT0cAgEBAi8kByMIGYMCAYJxAwIBEQa8RYF5M4EBgyqBVNszAQsUAYE4AYFyg02II10YAYR9JxsbgXKBFYJzdoEFgVwCgUiGXgSCInoSgVqBA4wVAoFShnBIgR4DWSwBVRMNCgsHBYFoAzUSKhVuMh2BIz4XgQcbBgWBHYEegRKBB12BKyheIYEIgXYCglGCAAIBSUEOB0VTCSVDEkckIggSCQETGiwKgRo1PAkmPxUMZAsaBWEIAg0McjkQMB4kLwUcJkAbChgYDUgRLDcVGQQ+bgeSL4MPAnsJCgEqAX8SMgpfHhySdJJIgTWfXAoog3iMJJU6GjOEBJQZklQLmH2OCoFNhzeMfVCEaYFpATqBWXAVO4JnCUoZD445g2vOUyM1AgkDLwEBBwIHDgMLgWiRfgEB IronPort-Data: A9a23:lWO3BK83eLHRNbTp1qjBDrUD0X+TJUtcMsCJ2f8bNWPcYEJGY0x3z TEcUTqEM/iLY2v2eIh2PIiy9UpTvsCAmt8xGQRupCFEQiMRo6IpJzg2wmQcns+2BpeeJK6yx 5xGMrEsFOhtEDmB4E/rbei5xZVF/fngbqLmD+LZMTxGSwZhSSMw4TpugOdRbrRA2bBVOCvT/ 4mtyyHjEAX9gWAtazpIs/jrRC5H5ZwehhtJ5jTSWtgT1LPuvyF9JI4SI6i3M0z5TuF8dsamR /zOxa2O5WjQ+REgELuNyt4XpWVTH9Y+lSDX4pZnc/DKbipq/0Te4Y5nXBYoUnq7vh3S9zxHJ HqhgrTrIeshFvWkdO3wyHC0GQkmVUFN0OevzXRSLaV/wmWeG0YAzcmCA2kmDZ1b3/t8Hltyr 80DJRM0bzuyo+OPlefTpulE3qzPLeHxN48Z/3UlxjbDALN+ENbIQr7B4plT2zJYasJmRKmFI ZFGL2AyMVKZP0Mn1lQ/UPrSmM+omnn2cDRCgFmUvqEwpWPUyWSd1ZCwbIaKJoLSHpk9ckCwn Wbr2kr5HzcgH9Ga5R+Z0VaJh8/oknauMG4VPPjinhJwu3WU3mEVBRgcWFe3rPX8hkOgVtZ3L 00P5jFoqrA/8kGuRNTxUxC05nmesXYht8F4CeY27kSJj6HT+QvcXjlCRT9aY9tgv8gzLdA36 mK0cxrSLWQHmNWopbi1r994cRva1fApEFI/ IronPort-HdrOrdr: A9a23:CmuORK3RoPpj5Vl1rqONPAqjBJ4kLtp133Aq2lEZdPUzSL39qy nAppomPHPP5Qr5HUtQ+uxoW5PwJE80i6QV3WB5B97LN2PbUSmTXeNfBODZrAEIdReTygck78 ddWpk7LsHsBl5nisu/ygy5H9E8hOSjysmT9IDjJ7MHd3ASV0mmhD0JbDqmLg== X-Talos-CUID: 9a23:olKf6m5tO73NoiaS7dss5H4EWeU4eWLn8C3hOEKbMGdCeZmUcArF X-Talos-MUID: 9a23:Dm2TLQRyKTbQOFKGRXTXgBxHGsd1xZ6FBUUhtIkln+yFFQt/bmI= X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,196,1787011200"; d="scan'208";a="544746034" Received: from rcdn-l-core-09.cisco.com ([173.37.255.146]) by rcdn-iport-5.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 05 Nov 2026 04:56:15 +0000 Received: from sjc-ads-2020.cisco.com (sjc-ads-2020.cisco.com [171.70.61.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "ciscoit-managed-infra-smtp-auth.cisco.com", Issuer "Internal Private TLS SubCA" (verified OK)) by rcdn-l-core-09.cisco.com (Postfix) with ESMTPS id 5DBA818000200 for ; Thu, 5 Nov 2026 04:56:15 +0000 (GMT) Received: by sjc-ads-2020.cisco.com (Postfix, from userid 1887505) id 00362CC124C; Wed, 4 Nov 2026 20:56:14 -0800 (PST) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Subject: [meta-python][scarthgap][PATCH 2/8] python3-pyjwt: fix CVE-2026-102267 Date: Wed, 4 Nov 2026 20:56:08 -0800 Message-Id: <20261105045614.2677776-2-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20261105045614.2677776-1-hthakar@cisco.com> References: <20261105045614.2677776-1-hthakar@cisco.com> MIME-Version: 1.0 X-Outbound-Client-TLS: VERIFIED;sjc-ads-2020.cisco.com [171.70.61.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256;ciscoit-managed-infra-smtp-auth.cisco.com X-Outbound-SMTP-Client: 171.70.61.208, sjc-ads-2020.cisco.com X-Outbound-Node: rcdn-l-core-09.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Thu, 05 Nov 2026 04:56:24 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/131853 From: Hetvi Thakar This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. Reject redirects when PyJWKClient fetches a JWKS to prevent requests from following redirects to unintended destinations. [1] https://github.com/jpadilla/pyjwt/commit/0a795b8e1f6ef08f634aa7086fc41cc6d5ce3e56 [2] https://github.com/jpadilla/pyjwt/security/advisories/GHSA-9v7f-9g4p-ffgj Signed-off-by: Hetvi Thakar --- .../python3-pyjwt/CVE-2026-102267.patch | 223 ++++++++++++++++++ .../python/python3-pyjwt_2.8.0.bb | 1 + 2 files changed, 224 insertions(+) create mode 100644 meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102267.patch diff --git a/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102267.patch b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102267.patch new file mode 100644 index 0000000000..d83cd8ca59 --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102267.patch @@ -0,0 +1,223 @@ +From 67ef104edd43a60ba87db0740364778eae796fe2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Wed, 14 Oct 2026 02:41:53 -0700 +Subject: [PATCH] Reject redirects in PyJWKClient fetches + +CVE: CVE-2026-102267 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/0a795b8e1f6ef08f634aa7086fc41cc6d5ce3e56] + +Backport Changes: +- Used the existing typing.List import for the handlers annotation. +- Omitted the mocked_http_error_response opener-mock conversion because + that helper is absent from the PyJWT 2.8.0 test suite. +- Adapted the two SSL-context tests from live requests to opener mocks; + the 2.8.0 success test needs a local ssl_ctx and an explicit mocked + get_jwk_set call, and its failure test needs the certificate-error mock. + +Signed-off-by: Hetvi Thakar +--- + jwt/jwks_client.py | 21 +++++++-- + tests/test_jwks_client.py | 96 ++++++++++++++++++++++++++++++++++----- + 2 files changed, 103 insertions(+), 14 deletions(-) + +diff --git a/jwt/jwks_client.py b/jwt/jwks_client.py +index 0e7d226..ab601a4 100644 +--- a/jwt/jwks_client.py ++++ b/jwt/jwks_client.py +@@ -12,6 +12,19 @@ from .exceptions import PyJWKClientConnectionError, PyJWKClientError + from .jwk_set_cache import JWKSetCache + + ++class _NoRedirectHandler(urllib.request.HTTPRedirectHandler): ++ def redirect_request( ++ self, ++ req: urllib.request.Request, ++ fp: Any, ++ code: int, ++ msg: str, ++ headers: Any, ++ newurl: str, ++ ) -> None: ++ return None ++ ++ + class PyJWKClient: + def __init__( + self, +@@ -61,9 +74,11 @@ class PyJWKClient: + def fetch_data(self) -> Any: + try: + r = urllib.request.Request(url=self.uri, headers=self.headers) +- with urllib.request.urlopen( +- r, timeout=self.timeout, context=self.ssl_context +- ) as response: ++ handlers: List[Any] = [_NoRedirectHandler()] ++ if self.ssl_context is not None: ++ handlers.append(urllib.request.HTTPSHandler(context=self.ssl_context)) ++ opener = urllib.request.build_opener(*handlers) ++ with opener.open(r, timeout=self.timeout) as response: + jwk_set = json.load(response) + except (URLError, TimeoutError) as e: + raise PyJWKClientConnectionError( +diff --git a/tests/test_jwks_client.py b/tests/test_jwks_client.py +index 4a836f2..b3f2555 100644 +--- a/tests/test_jwks_client.py ++++ b/tests/test_jwks_client.py +@@ -1,7 +1,9 @@ + import contextlib + import json + import ssl ++import threading + import time ++from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + from unittest import mock + from urllib.error import URLError + +@@ -9,6 +11,7 @@ import pytest + + import jwt + from jwt import PyJWKClient ++from jwt.jwks_client import _NoRedirectHandler + from jwt.api_jwk import PyJWK + from jwt.exceptions import PyJWKClientConnectionError, PyJWKClientError + +@@ -47,7 +50,10 @@ RESPONSE_DATA_NO_MATCHING_KID = { + + @contextlib.contextmanager + def mocked_success_response(data): +- with mock.patch("urllib.request.urlopen") as urlopen_mock: ++ with mock.patch("urllib.request.build_opener") as build_opener_mock: ++ opener = mock.Mock() ++ build_opener_mock.return_value = opener ++ urlopen_mock = opener.open + response = mock.Mock() + response.__enter__ = mock.Mock(return_value=response) + response.__exit__ = mock.Mock() +@@ -58,7 +64,10 @@ def mocked_success_response(data): + + @contextlib.contextmanager + def mocked_failed_response(): +- with mock.patch("urllib.request.urlopen") as urlopen_mock: ++ with mock.patch("urllib.request.build_opener") as build_opener_mock: ++ opener = mock.Mock() ++ build_opener_mock.return_value = opener ++ urlopen_mock = opener.open + urlopen_mock.side_effect = URLError("Fail to process the request.") + yield urlopen_mock + +@@ -67,7 +76,10 @@ def mocked_failed_response(): + def mocked_first_call_wrong_kid_second_call_correct_kid( + response_data_one, response_data_two + ): +- with mock.patch("urllib.request.urlopen") as urlopen_mock: ++ with mock.patch("urllib.request.build_opener") as build_opener_mock: ++ opener = mock.Mock() ++ build_opener_mock.return_value = opener ++ urlopen_mock = opener.open + response = mock.Mock() + response.__enter__ = mock.Mock(return_value=response) + response.__exit__ = mock.Mock() +@@ -81,13 +93,60 @@ def mocked_first_call_wrong_kid_second_call_correct_kid( + + @contextlib.contextmanager + def mocked_timeout(): +- with mock.patch("urllib.request.urlopen") as urlopen_mock: ++ with mock.patch("urllib.request.build_opener") as build_opener_mock: ++ opener = mock.Mock() ++ build_opener_mock.return_value = opener ++ urlopen_mock = opener.open + urlopen_mock.side_effect = TimeoutError("timed out") + yield urlopen_mock + + + @crypto_required + class TestPyJWKClient: ++ def test_fetch_data_rejects_redirect_without_contacting_destination(self) -> None: ++ destination_requests = 0 ++ ++ class RedirectHandler(BaseHTTPRequestHandler): ++ def do_GET(self) -> None: # noqa: N802 - stdlib handler API ++ nonlocal destination_requests ++ if self.path == "/start": ++ self.send_response(302) ++ self.send_header("Location", f"{base_url}/destination") ++ self.end_headers() ++ else: ++ destination_requests += 1 ++ self.send_response(200) ++ self.end_headers() ++ ++ def log_message(self, _format: str, *args: object) -> None: ++ return ++ ++ server = ThreadingHTTPServer(("127.0.0.1", 0), RedirectHandler) ++ base_url = f"http://127.0.0.1:{server.server_port}" ++ thread = threading.Thread(target=server.serve_forever, daemon=True) ++ thread.start() ++ try: ++ client = PyJWKClient( ++ f"{base_url}/start", headers={"Authorization": "secret"} ++ ) ++ with pytest.raises(PyJWKClientConnectionError): ++ client.fetch_data() ++ assert destination_requests == 0 ++ finally: ++ server.shutdown() ++ server.server_close() ++ thread.join() ++ ++ def test_redirect_handler_does_not_follow_redirects(self) -> None: ++ handler = _NoRedirectHandler() ++ ++ assert ( ++ handler.redirect_request( ++ mock.Mock(), mock.Mock(), 302, "Found", {}, "https://other.test" ++ ) ++ is None ++ ) ++ + def test_fetch_data_forwards_headers_to_correct_url(self): + url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" + +@@ -372,9 +431,22 @@ class TestPyJWKClient: + + def test_get_jwt_set_sslcontext_default(self): + url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" +- jwks_client = PyJWKClient(url, ssl_context=ssl.create_default_context()) +- +- jwk_set = jwks_client.get_jwk_set() ++ ssl_ctx = ssl.create_default_context() ++ jwks_client = PyJWKClient(url, ssl_context=ssl_ctx) ++ ++ with mock.patch("urllib.request.build_opener") as build_opener_mock: ++ opener = mock.Mock() ++ build_opener_mock.return_value = opener ++ response = mock.Mock() ++ response.__enter__ = mock.Mock(return_value=response) ++ response.__exit__ = mock.Mock() ++ response.read.return_value = json.dumps(RESPONSE_DATA_WITH_MATCHING_KID) ++ opener.open.return_value = response ++ jwk_set = jwks_client.get_jwk_set() ++ handlers = build_opener_mock.call_args.args ++ assert any( ++ getattr(handler, "_context", None) is ssl_ctx for handler in handlers ++ ) + + assert jwk_set is not None + +@@ -384,7 +456,9 @@ class TestPyJWKClient: + url, ssl_context=ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) + ) + +- with pytest.raises(PyJWKClientError): +- jwks_client.get_jwk_set() +- +- assert "Failed to get an expected error" ++ with mock.patch("urllib.request.build_opener") as build_opener_mock: ++ build_opener_mock.return_value.open.side_effect = URLError( ++ ssl.SSLCertVerificationError("certificate verify failed") ++ ) ++ with pytest.raises(PyJWKClientError): ++ jwks_client.get_jwk_set() +-- +2.35.6 diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb index 5a1dac4df6..fa03d1e616 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb @@ -13,6 +13,7 @@ SRC_URI += " \ file://CVE-2026-48526.patch \ file://CVE-2026-102269_p1.patch \ file://CVE-2026-102269_p2.patch \ + file://CVE-2026-102267.patch \ " SRC_URI[sha256sum] = "57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de" From patchwork Thu Nov 5 04:56:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 102950 Received: from rcdn-iport-3.cisco.com (rcdn-iport-3.cisco.com [173.37.86.74]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.27526.1793854576555231509 for ; Wed, 04 Nov 2026 20:56:16 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=hKEObO9t; spf=pass (domain: cisco.com, ip: 173.37.86.74, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=5750; q=dns/txt; s=iport01; t=1793854576; x=1795064176; h=from:to:subject:date:message-id:in-reply-to:references: mime-version:content-transfer-encoding; bh=ZpBn4BseToTYgzm1d1GedbnRiV3AyMshWwZuv04lthY=; b=hKEObO9tZmDncm+FcUHZt0TqLmyUsKzuvlSKCiyAbiLZWkTPzelNVqFn W2CubtIRwIL/DwFkULHYMLVdTgXURQ6AwKCNURqCcUf71mL43rUi82ncQ B80okhBdCFEOlNWPVZFC0f1CJTkyolFhhC7A8OvjC9Ig+BtDI9hhoruDK auBXS+Ym3ckT8OYtqnb5Z31dGzsHleLjsxfBOU+ngNuxmpvy/MwRZPnqU TFT9thFeAAv5Rh+ex1gCfUI+4WJM0fkPYlWPLbqifkxiGMEnOxdKNZ4MQ g++UfjtXFM8juUwcqwNJjrCG9Y9ohrfy6MD0BoLnXPmMiJLa+EmKAYEw0 A==; X-CSE-ConnectionGUID: NIvUj8QRQwWqsucu2XEuug== X-CSE-MsgGUID: Hyxv+cYiT3y57Ifn800qDg== X-IPAS-Result: A0BIAgDKC+xq/4//Ja1aHgEBCxIMggULgld1YUJJA5QngiEDniGBfg8BAQEPNB0EAQGEP0YCjhwCJjQJDgECBAMCAwEBAQEBAQEBAQEBAQoBAQUBAQECAQcFgQ4Thk8NkBIBAgEDJwsBGAE9HAMBAi8rIwgZgwIBgnQCAREGvEeBeTOBAYMpAYFU2zMBCxQBgTiFQIgjXRgBhH0nGxuBcoEVg2mBBYFcAQECiCQEgg0VehKBWoMliXMCgVKGcEiBHgNZLAFVEw0KCwcFgWgDNRIqFW4yHYEjPheBBxsGBYEdgR6BEoEHXYErKF4hgQiBdgKCUYIAAgFJQQ4HRVMJJUMSRyQiCBIJARMaLAqBGjU8CSY/FQxkCxoFYQgCDQxyORAwHiQvBRwmQBsKGBgNSBEsNxUZBD5uB5IvgmQrAgGBDQErRjkSgRsPD5JzkmWhEQoog3iMJJU6GjOEBIFXkkKSVAuYfY4KlgRNhGmBaDyBWXAVgyIJShkPjisOCxyDRIVkyG8jNQIJAy8BAQcCBw4DC4FokX4BAQ IronPort-Data: A9a23:STWHVaxnAbtxarmDGBV6t+dmxyrEfRIJ4+MujC+fZmUNrF6WrkUFy 2IaXziHP6mNM2CnfooiPo7k8ktXv8XRnYVkHgc4+FhgHilAwSbn6Xt1DatR0we6dJCroJdPt p1GAjX4BJlqCCKa/lHyYuCJQUBUjcmgXqD7BPPPJhd/TAplTDZJoR94kobVuKYw6TSCK13L4 4yaT/H3Ygf/hWctaj5MscpvlTs21BjMkGJA1rABTagjUG/2zxE9EJ8ZLKetGHr0KqE8NvK6X evK0Iai9Wrf+Ro3Yvv9+losWhRXKlJ6FVHmZkt+A8BOsDAbzsAB+vpT2M4nVKtio27hc+adZ zl6ncfYpQ8BZsUgkQmGOvVSO3kW0aZuoNcrLZUj2CCe5xWuTpfi/xlhJGcXI7Aaqt9NOH5x7 OcFIy1XXj2emu3jldpXSsE07igiBNPgMIVavjRryivUSK54B5vCWK7No9Rf2V/chOgXQq2YP JVfM2cyKk2cOHWjOX9PYH46tOe0hnD8eidwo1OOrq1x6G/WpOB0+OWxbIuKIYbTHa25mG64t 3LYvG/IAy1BJYHA6DSDoir0n8PAyHaTtIU6UefQGuRRqFqLy2oeDRcbWVe2rby1h1CzX/pbK lcI4WwptaU0+UmhQ9XxUhH+p2SL1iPwQPJKGOE8rQXIwa3O7kPBWC4PTyVKb5ots8peqSEW6 2JlVujBXVRH2IB5g1rEnltIhVte4RQoEFI= IronPort-HdrOrdr: A9a23:LCLKO6PC+S8fY8BcTu2jsMiBIKoaSvp037BN7TEUdfU7SKKlfq yV8cjzkCWE6wr5O0tQ/OxoRpPgfZq0z/cciuMs1PWZLWvbUQCTQ72Kg7GP/9SZIU3D398Y87 t8eK5jD9C1J117gcHmpDScKb8bsb66GGTCv5am85+rJjsaDZ1d0w== X-Talos-CUID: 9a23:124JmGllJMt12pPdw1wvoOES107XOVv80krSc3aCMHlWUaaIQkLKyI55jsU7zg== X-Talos-MUID: 9a23:CPgJbgrBs3wt1qa6dksezxFzJNkr6JWvNBsczMontc/UbXRpFzjI2Q== X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,196,1787011200"; d="scan'208";a="544973210" Received: from rcdn-l-core-06.cisco.com ([173.37.255.143]) by rcdn-iport-3.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 05 Nov 2026 04:56:15 +0000 Received: from sjc-ads-2020.cisco.com (sjc-ads-2020.cisco.com [171.70.61.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "ciscoit-managed-infra-smtp-auth.cisco.com", Issuer "Internal Private TLS SubCA" (verified OK)) by rcdn-l-core-06.cisco.com (Postfix) with ESMTPS id 6337918000242 for ; Thu, 5 Nov 2026 04:56:15 +0000 (GMT) Received: by sjc-ads-2020.cisco.com (Postfix, from userid 1887505) id 0530ACC124D; Wed, 4 Nov 2026 20:56:15 -0800 (PST) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Subject: [meta-python][scarthgap][PATCH 3/8] python3-pyjwt: fix CVE-2026-102268 and CVE-2026-102270 Date: Wed, 4 Nov 2026 20:56:09 -0800 Message-Id: <20261105045614.2677776-3-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20261105045614.2677776-1-hthakar@cisco.com> References: <20261105045614.2677776-1-hthakar@cisco.com> MIME-Version: 1.0 X-Outbound-Client-TLS: VERIFIED;sjc-ads-2020.cisco.com [171.70.61.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256;ciscoit-managed-infra-smtp-auth.cisco.com X-Outbound-SMTP-Client: 171.70.61.208, sjc-ads-2020.cisco.com X-Outbound-Node: rcdn-l-core-06.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Thu, 05 Nov 2026 04:56:23 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/131852 From: Hetvi Thakar This patch applies the shared upstream fix as referenced in [2] and [3], using the commit shown in [1]. Use a bounded PEM marker scan to reject loader-accepted public-key variants and avoid excessive regular-expression backtracking. [1] https://github.com/jpadilla/pyjwt/commit/8b4e233a22206b34ec1186e912e75c0b2396ac07 [2] https://github.com/jpadilla/pyjwt/security/advisories/GHSA-ffc3-869f-jxw9 [3] https://github.com/advisories/GHSA-jwrc-g2q2-pq5p Signed-off-by: Hetvi Thakar --- .../CVE-2026-102268_CVE-2026-102270.patch | 116 ++++++++++++++++++ .../python/python3-pyjwt_2.8.0.bb | 1 + 2 files changed, 117 insertions(+) create mode 100644 meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102268_CVE-2026-102270.patch diff --git a/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102268_CVE-2026-102270.patch b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102268_CVE-2026-102270.patch new file mode 100644 index 0000000000..ed7fd0949b --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102268_CVE-2026-102270.patch @@ -0,0 +1,116 @@ +From 5b3dab98c3a6c6cd64dca4895451c1dd11f04b08 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Wed, 14 Oct 2026 02:43:17 -0700 +Subject: [PATCH] fix: reject loader-accepted PEM variants + +CVE: CVE-2026-102268 CVE-2026-102270 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/8b4e233a22206b34ec1186e912e75c0b2396ac07] + +Backport Changes: +- Retain the existing Any import when adding Callable to the PyJWT 2.8.0 + regression tests; the upstream test suite imports Union instead. + +Signed-off-by: Hetvi Thakar +--- + jwt/utils.py | 20 ++++++++++-------- + tests/test_algorithms.py | 44 +++++++++++++++++++++++++++++++++++++++- + 2 files changed, 55 insertions(+), 9 deletions(-) + +diff --git a/jwt/utils.py b/jwt/utils.py +index 81c5ee4..4e3ad3c 100644 +--- a/jwt/utils.py ++++ b/jwt/utils.py +@@ -116,18 +116,22 @@ _PEMS = { + b"X509 CRL", + } + +-_PEM_RE = re.compile( +- b"----[- ]BEGIN (" +- + b"|".join(_PEMS) +- + b""")[- ]----\r? +-.+?\r? +-----[- ]END \\1[- ]----\r?\n?""", +- re.DOTALL, ++# Accept PEM formatting variants that the cryptography loader accepts, while ++# retaining the explicit asymmetric-key marker check. ++_PEM_MARKER_RE = re.compile( ++ b"(?=(----[- ](BEGIN|END) (" + b"|".join(_PEMS) + b")[- ]----))" + ) + + + def is_pem_format(key: bytes) -> bool: +- return bool(_PEM_RE.search(key)) ++ begin_markers: dict[bytes, int] = {} ++ for marker in _PEM_MARKER_RE.finditer(key): ++ marker_type, label = marker.group(2), marker.group(3) ++ if marker_type == b"BEGIN": ++ begin_markers[label] = marker.start(1) + len(marker.group(1)) ++ elif label in begin_markers and begin_markers[label] < marker.start(1): ++ return True ++ return False + + + # Based on https://github.com/pyca/cryptography/blob/bcb70852d577b3f490f015378c75cba74986297b/src/cryptography/hazmat/primitives/serialization/ssh.py#L40-L46 +diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py +index e5220c6..0fbffc9 100644 +--- a/tests/test_algorithms.py ++++ b/tests/test_algorithms.py +@@ -1,6 +1,6 @@ + import base64 + import json +-from typing import Any, cast ++from typing import Any, Callable, cast + + import pytest + +@@ -82,6 +82,48 @@ class TestAlgorithms: + with open(key_path(key)) as keyfile: + algo.prepare_key(keyfile.read()) + ++ @pytest.mark.parametrize( ++ "mutation", ++ [ ++ lambda key: key.replace(b"-----END", b"\t-----END"), ++ lambda key: key.replace(b"\n", b"\r"), ++ lambda key: key.replace(b"\n", b""), ++ ], ++ ) ++ def test_hmac_should_reject_loader_accepted_pem_mutations( ++ self, mutation: Callable[[bytes], bytes] ++ ) -> None: ++ algo = HMACAlgorithm(HMACAlgorithm.SHA256) ++ with open(key_path("testkey2_rsa.pub.pem"), "rb") as keyfile: ++ mutated_key = mutation(keyfile.read()) ++ ++ with pytest.raises(InvalidKeyError): ++ algo.prepare_key(mutated_key) ++ ++ def test_hmac_should_accept_incomplete_pem_marker(self) -> None: ++ algo = HMACAlgorithm(HMACAlgorithm.SHA256) ++ key = b"-----BEGIN PUBLIC KEY-----" * 10000 ++ ++ assert algo.prepare_key(key) == key ++ ++ def test_hmac_should_reject_a_later_pem_block(self) -> None: ++ algo = HMACAlgorithm(HMACAlgorithm.SHA256) ++ with open(key_path("testkey2_rsa.pub.pem"), "rb") as keyfile: ++ key = b"-----BEGIN PUBLIC KEY-----" + keyfile.read() ++ ++ with pytest.raises(InvalidKeyError): ++ algo.prepare_key(key) ++ ++ def test_hmac_should_reject_a_pem_block_after_an_overlapping_end_marker( ++ self, ++ ) -> None: ++ algo = HMACAlgorithm(HMACAlgorithm.SHA256) ++ with open(key_path("testkey2_rsa.pub.pem"), "rb") as keyfile: ++ key = b"-----END CERTIFICATE" + keyfile.read() ++ ++ with pytest.raises(InvalidKeyError): ++ algo.prepare_key(key) ++ + def test_hmac_jwk_should_parse_and_verify(self): + algo = HMACAlgorithm(HMACAlgorithm.SHA256) + +-- +2.35.6 diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb index fa03d1e616..f62b8aa7bb 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb @@ -14,6 +14,7 @@ SRC_URI += " \ file://CVE-2026-102269_p1.patch \ file://CVE-2026-102269_p2.patch \ file://CVE-2026-102267.patch \ + file://CVE-2026-102268_CVE-2026-102270.patch \ " SRC_URI[sha256sum] = "57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de" From patchwork Thu Nov 5 04:56:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 102953 Received: from rcdn-iport-2.cisco.com (rcdn-iport-2.cisco.com [173.37.86.73]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.27344.1793854576576944746 for ; Wed, 04 Nov 2026 20:56:16 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=TJMLlHIV; spf=pass (domain: cisco.com, ip: 173.37.86.73, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=6388; q=dns/txt; s=iport01; t=1793854576; x=1795064176; h=from:to:subject:date:message-id:in-reply-to:references: mime-version:content-transfer-encoding; bh=bnPyfC0+S+GTyVtefcAWnPAbJybUGKgHLmj15/2fuTU=; b=TJMLlHIV+ZDVMMFEtNlqUPoka7QVka0v6N3A7kdATQ3VjfDJw5fW9aU/ UrBjkXl4uRS25ksxVda357BKPqfW2hTdGF1byuPPskMNbwnu+zev99NUQ a2wdfMLLTsKItH7xeHE6p1b81que2ZIvknT11nyZXrbrALsh5OX5/XTHQ GmKFdrww+yTb7hZPAVRQr+jNqMZfhslM9CDOhnHGjxHhB20IZ8jkW3MRV EJBS5q/o0pzTC7MigG+ZzYSH9v9rKWOrXcZsjTsbwdxhDQTgKMg8L/+HK WokkRDC8twuq/d9+QdeNb2pFKvsRHatzW9PgT4E2nJQD5/w2XaQxJp6vs w==; X-CSE-ConnectionGUID: BHtQZuZST0SbBIkqAoUeuA== X-CSE-MsgGUID: 3z5q52OHRS6uC8M0ndaGWg== X-IPAS-Result: A0BIAgBGC+xq/5H/Ja1aHgEBCxIMggULgld1YUJJA5QngiEDniGBfg8BAQEPRA0EAQGFBQKOHAImNAkOAQIEAwIDAQEBAQEBAQEBAQEBCgEBBQEBAQIBBwWBDhOGTw2QEgECAQMnCwEYAT0cAwECLysjCBmDAgGCdAIBEbxLgXkzgQGDKQGBVNszAQsUAYE4hUCII10YAUSEOScbG4FygRWDaYEFgVwCiCYEgg0VehKBWo0YAoFShnBIgR4DWSwBVRMNCgsHBYFoAzUSKhVuMh2BIz4XgQcbBgWBHYEegRKBB12BKyheIYEIgXYCglGCAAIBSUEOB0VTCSVDEkckIggSCQETGiwKgRo1PAkmPxUMZAsaBWEIAg0McjkQMB4kLwUcJkAbChgYDUgRLDcVGQQ+bgeSL4MPAoEOASt/Eg8jaR6lWKERCiiDeIwklToaM4QEgVeSQpJUC5h9jgqWB0qEaYFoPIFZcBWDIglKGQ+OKw4Lg2CFZMhvIzUCAQgDLwEBBwIHDgMLgWiRfgEB IronPort-Data: A9a23:RZiM7q9i2D6C9fmCesWaDrUD0X+TJUtcMsCJ2f8bNWPcYEJGY0x3z jMbDGiDMv7camCnfNt2b4mx80pTvp/cytU3GlBk+X9EQiMRo6IpJzg2wmQcns+2BpeeJK6yx 5xGMrEsFOhtEDmB4E/rbei5xZVF/fngbqLmD+LZMTxGSwZhSSMw4TpugOdRbrRA2bBVOCvT/ 4mtyyHjEAX9gWAtazpIs/jrRC5H5ZwehhtJ5jTSWtgT1LPuvyF9JI4SI6i3M0z5TuF8dsamR /zOxa2O5WjQ+REgELuNyt4XpWVTH9Y+lSDX4pZnc/DKbipq/0Te4Y5nXBYoUnq7vh3S9zxHJ HqhgrTrIeshFvWkdO3wyHC0GQkmVUFN0OevzXRSLaV/wmWeG0YAzcmCA2kcHZEhyPtFOl0Wz vYyCTMURxe+iv+flefTpulE3qzPLeHxN48Z/3UlxjbDALN+EdbIQr7B4plT2zJYasJmRKmFI ZFGL2AyMVKZP0An1lQ/UPrSmM+omnn2cDRCgFmUvqEwpWPUyWSd1ZCwa4KPI4XVH5g9ckCw/ XCb5jmjJTEgD4KC4mqgqnSgj8DwknauMG4VPPjinhJwu3WU3mEVBRgcWFe3rPX8hkOgVtZ3L 00P5jFoqrA/8kGuRNTxUxC05nmesXYht8F4CeY27kSJj6HT+QvcXjhCRT9aY9tgv8gzLdA36 mK0cxrSLWQHmNWopbi1r994cRva1fApEFI/ IronPort-HdrOrdr: A9a23:kmuXSaz8mZV9QnQWmURqKrPwAL1zdoMgy1knxilNoHtuA6ilfq +V8sjzuSWYtN9VYgBCpTniAtjkfZqjz/9ICOAqVN/INjUO+lHYTr2KhrGM/9SPIUHDH5ZmtZ tIQuxZFMD6C0R8gILR5Qm1FMtl/fy8mZrY4ts3CxxWPHhXg2YK1XYeNjqm X-Talos-CUID: 9a23:v6BW8mMGYOLsi+5Dcgl7zXwJKPsZKkbX5lbTOma/NGt0YejA X-Talos-MUID: 9a23:ZsskDwsNBfi4pTE8782n3yM/LZoyxISUMx4QusUj48+IPANPNGLI X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,196,1787011200"; d="scan'208";a="530211725" Received: from rcdn-l-core-08.cisco.com ([173.37.255.145]) by rcdn-iport-2.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 05 Nov 2026 04:56:15 +0000 Received: from sjc-ads-2020.cisco.com (sjc-ads-2020.cisco.com [171.70.61.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "ciscoit-managed-infra-smtp-auth.cisco.com", Issuer "Internal Private TLS SubCA" (verified OK)) by rcdn-l-core-08.cisco.com (Postfix) with ESMTPS id 68654180001C5 for ; Thu, 5 Nov 2026 04:56:15 +0000 (GMT) Received: by sjc-ads-2020.cisco.com (Postfix, from userid 1887505) id 0BFE7CC124E; Wed, 4 Nov 2026 20:56:15 -0800 (PST) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Subject: [meta-python][scarthgap][PATCH 4/8] python3-pyjwt: fix CVE-2026-102271 Date: Wed, 4 Nov 2026 20:56:10 -0800 Message-Id: <20261105045614.2677776-4-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20261105045614.2677776-1-hthakar@cisco.com> References: <20261105045614.2677776-1-hthakar@cisco.com> MIME-Version: 1.0 X-Outbound-Client-TLS: VERIFIED;sjc-ads-2020.cisco.com [171.70.61.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256;ciscoit-managed-infra-smtp-auth.cisco.com X-Outbound-SMTP-Client: 171.70.61.208, sjc-ads-2020.cisco.com X-Outbound-Node: rcdn-l-core-08.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Thu, 05 Nov 2026 04:56:24 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/131855 From: Hetvi Thakar This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. Reject DER-encoded public keys and certificates when preparing an HMAC key to prevent asymmetric-key material from being accepted as an HMAC secret. [1] https://github.com/jpadilla/pyjwt/commit/2798504fa2663364573cf2d1043d8d7fef389499 [2] https://github.com/jpadilla/pyjwt/security/advisories/GHSA-p4g4-x82p-q773 Signed-off-by: Hetvi Thakar --- .../python3-pyjwt/CVE-2026-102271.patch | 141 ++++++++++++++++++ .../python/python3-pyjwt_2.8.0.bb | 1 + 2 files changed, 142 insertions(+) create mode 100644 meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102271.patch diff --git a/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102271.patch b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102271.patch new file mode 100644 index 0000000000..82938afb27 --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102271.patch @@ -0,0 +1,141 @@ +From 20f172fe70aad3314ee988d343c5f1fb05b4ff70 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Wed, 14 Oct 2026 05:10:38 -0700 +Subject: [PATCH] fix: reject DER public keys as HMAC secrets + +CVE: CVE-2026-102271 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/2798504fa2663364573cf2d1043d8d7fef389499] + +Backport Changes: +- Import UnsupportedAlgorithm, which the PyJWT 2.8.0 algorithms module + does not already import, for the DER loader exception handlers. + +Signed-off-by: Hetvi Thakar +--- + jwt/algorithms.py | 29 +++++++++++++++++++++++++++-- + tests/test_algorithms.py | 39 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 66 insertions(+), 2 deletions(-) + +diff --git a/jwt/algorithms.py b/jwt/algorithms.py +index b6303ed..fcfca89 100644 +--- a/jwt/algorithms.py ++++ b/jwt/algorithms.py +@@ -28,7 +28,8 @@ else: + + + try: +- from cryptography.exceptions import InvalidSignature ++ from cryptography import x509 ++ from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import padding +@@ -67,6 +68,7 @@ try: + NoEncryption, + PrivateFormat, + PublicFormat, ++ load_der_public_key, + load_pem_private_key, + load_pem_public_key, + load_ssh_public_key, +@@ -261,10 +263,33 @@ class HMACAlgorithm(Algorithm): + def __init__(self, hash_alg: HashlibHash) -> None: + self.hash_alg = hash_alg + ++ @staticmethod ++ def _is_der_key(key_bytes: bytes) -> bool: ++ if not has_crypto: ++ return False ++ ++ try: ++ load_der_public_key(key_bytes) ++ except (TypeError, ValueError, UnsupportedAlgorithm): ++ pass ++ else: ++ return True ++ ++ try: ++ x509.load_der_x509_certificate(key_bytes) ++ except (TypeError, ValueError, UnsupportedAlgorithm): ++ return False ++ else: ++ return True ++ + def prepare_key(self, key: str | bytes) -> bytes: + key_bytes = force_bytes(key) + +- if is_pem_format(key_bytes) or is_ssh_key(key_bytes): ++ if ( ++ is_pem_format(key_bytes) ++ or is_ssh_key(key_bytes) ++ or self._is_der_key(key_bytes) ++ ): + raise InvalidKeyError( + "The specified key is an asymmetric key or x509 certificate and" + " should not be used as an HMAC secret." +diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py +index 0fbffc9..c843903 100644 +--- a/tests/test_algorithms.py ++++ b/tests/test_algorithms.py +@@ -12,6 +12,7 @@ from .keys import load_ec_pub_key_p_521, load_hmac_key, load_rsa_pub_key + from .utils import crypto_required, key_path + + if has_crypto: ++ from cryptography import x509 + from cryptography.hazmat.primitives.asymmetric.ec import ( + EllipticCurvePrivateKey, + EllipticCurvePublicKey, +@@ -28,6 +29,10 @@ if has_crypto: + RSAPrivateKey, + RSAPublicKey, + ) ++ from cryptography.hazmat.primitives.serialization import ( ++ Encoding, ++ PublicFormat, ++ ) + + from jwt.algorithms import ECAlgorithm, OKPAlgorithm, RSAAlgorithm, RSAPSSAlgorithm + +@@ -172,6 +177,40 @@ class TestAlgorithms: + key = algo.prepare_key('{"this": "is just a json-shaped secret"}') + assert key == b'{"this": "is just a json-shaped secret"}' + ++ @crypto_required ++ @pytest.mark.parametrize( ++ "key_format_name", ++ ("SubjectPublicKeyInfo", "PKCS1"), ++ ) ++ def test_hmac_prepare_key_rejects_der_public_key( ++ self, key_format_name: str ++ ) -> None: ++ algo = HMACAlgorithm(HMACAlgorithm.SHA256) ++ public_key = cast(RSAPublicKey, load_rsa_pub_key()) ++ der_key = public_key.public_bytes( ++ Encoding.DER, getattr(PublicFormat, key_format_name) ++ ) ++ ++ with pytest.raises(InvalidKeyError, match="asymmetric key"): ++ algo.prepare_key(der_key) ++ ++ @crypto_required ++ def test_hmac_prepare_key_rejects_der_certificate(self) -> None: ++ algo = HMACAlgorithm(HMACAlgorithm.SHA256) ++ with open(key_path("testkey_rsa.cer"), "rb") as certificate_file: ++ certificate = x509.load_pem_x509_certificate(certificate_file.read()) ++ der_certificate = certificate.public_bytes(Encoding.DER) ++ ++ with pytest.raises(InvalidKeyError, match="x509 certificate"): ++ algo.prepare_key(der_certificate) ++ ++ @crypto_required ++ def test_hmac_prepare_key_accepts_non_key_binary_secret(self) -> None: ++ algo = HMACAlgorithm(HMACAlgorithm.SHA256) ++ secret = b"\x30\x82not-a-der-key" ++ ++ assert algo.prepare_key(secret) == secret ++ + @crypto_required + def test_rsa_should_parse_pem_public_key(self): + algo = RSAAlgorithm(RSAAlgorithm.SHA256) +-- +2.35.6 diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb index f62b8aa7bb..612b1c9cde 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb @@ -15,6 +15,7 @@ SRC_URI += " \ file://CVE-2026-102269_p2.patch \ file://CVE-2026-102267.patch \ file://CVE-2026-102268_CVE-2026-102270.patch \ + file://CVE-2026-102271.patch \ " SRC_URI[sha256sum] = "57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de" From patchwork Thu Nov 5 04:56:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 102954 Received: from rcdn-iport-1.cisco.com (rcdn-iport-1.cisco.com [173.37.86.72]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.27345.1793854576653952957 for ; Wed, 04 Nov 2026 20:56:16 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=PvHq+ecC; spf=pass (domain: cisco.com, ip: 173.37.86.72, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=13000; q=dns/txt; s=iport01; t=1793854576; x=1795064176; h=from:to:subject:date:message-id:in-reply-to:references: mime-version:content-transfer-encoding; bh=VIzXuBLn8ztEl0kD4NSvtR/Clu/ZrfkooN7dPQjSWCA=; b=PvHq+ecC4YfQ5p2H1Ct8JzDVZWwPREtx37z6LxFDEwAqZ23MhJh/U20G Ulp5bNEEeMBuuzRiDT7VtWfDMgu7a1NJi82kbY5j9UzOrk23DAk84DaFG v/5Z5Ip6tlnVdp1ZkvpOteQLVd5T5CaM8FNeoelDfpc6g1hzY1XwOsZXx 8s3EPBvElay5cih9ZaI0pcgiqdeGsACbNjzPcgAN1SaBQOOX7mtEywWhE XSOH2xVk8hynps1BDjvZWPqqz0bQGmMOHeVhxPH5VBi9A2CKwdSbh/r8S pojQYvcJ3mE9tjfSZohx32IvC+qjBhxrZFcb/9iG3IHMuAYTVYdKgS3/e g==; X-CSE-ConnectionGUID: sPQhrhvZSAuMlje9z/szJA== X-CSE-MsgGUID: oie4GMzwQAyddkjNltFD8w== X-IPAS-Result: A0BKAgCOCuxq/5H/Ja1aEwEBCQEBCxIMggULghg/dWFCSQOUJ4IhA4ETnQ6Bfg8BAQEPRA0EAQGFBQKOHAImNAkOAQIEAwIDAQEBAQEBAQEBAQEBCgEBBQEBAQIBBwWBDhOGTw2QEgECAQMnCwEYAT0cAwECLysjCBmCKlgBgnQCAREGvEGBeTOBAYMpAYFU2zMBCxQBgTiFQIgjXRgBRIQ5JxsbgXKBFYJzdoEFgVwCiCYEgiJ6EoFagQOMFQKBUoZwSIEeA1ksAVUTDQoLBwWBaAM1EioVbjIdgSM+F4EHGwYFgR2BHoESgQddgSsoXiGBCIF2AoJRggACAUlBDgdFUwklQxJHJCIIEgkBExosCoEaNTwJJj8VDGQLGgVhCAINDHI5EDAeJC8FHCZAGwoYGA1IESw3FRkEPm4Hki+CcSA9HCITAQkifxIyaR6TNZIjoREKKIN4jCSVOhozhASUGZJUC5h9jgqBTZQ0UIRpgWg8gVlwFTuCZwlKGQ+OLgsLg2DOUyM1AgkDLwEBBwIHDgMLgWiRHmABAQ IronPort-Data: A9a23:S9rpqqBLmY3fqRVW/3jiw5YqxClBgxIJ4kV8jS/XYbTApG8i1zxSn TZLWWCPM/qNajD2KNxzPo6z9B4CvpTWzdExOVdlrnsFo1CmBibm6XV1Cm+qYkt+++WaFBoPA /02M4eGcYZsCCCM/n9BC5C5xVFkz6aEW7HgP+DNPyF1VGdMRTwo4f5Zs7ZRbrVA357jXGthh fuo+5eBYQb9h2YuWo4pw/vrRC1H7ayaVAww5jTSVdgT1HfCmn8cCo4oJK3ZBxPQXolOE+emc P3Ixbe/83mx109F5gSNy+uTnuUiG9Y+DCDW4pZkc/HKbitq+kTe5p0G2M80Mi+7vdkmc+dZk 72hvbToIesg0zaldO41C3G0GAkmVUFKFSOuzXWX6aSuI0P6n3TE2M1XDFs2I78j0/tSBWNDp O5fMyIvR0XW7w626OrTpuhEnM8vKozveYgYoHwllWGfBvc9SpeFSKLPjTNa9G5v3YYVQrCEO pdfMGYyBPjDS0Un1lM/CI4+leShnFH0ciZTrxSeoq9fD237kVAtiuOzb4CLEjCMbcJUkxq3g 3DZxk3oDh0ZNvqb1RSpom3504cjmgu+Aur+DoaQ8eZnhlCWzGEfBBAaEFe2v/S9okq/QM5Eb UsM9ywjqKI/+ECmQp/6RRLQnZKflgQXV9wVF6gx7xuAj/KEpQ2YHWMDCDVGbbTKqfMLeNDj7 XfR9/uBONClmOT9pa61nltMkQ6PBA== IronPort-HdrOrdr: A9a23:2wWhraP/OKTJOcBcTu2jsMiBIKoaSvp037BN7TEUdfU7SKKlfq yV8cjzkCWE6wr5O0tQ/OxoRpPgfZq0z/cciuMs1PWZLWvbUQCTQ72Kg7GP/9SZIU3D398Y87 t8eK5jD9C1J117gcHmpDScKb8bsb66GGTCv5am85+rJjsaDZ1d0w== X-Talos-CUID: 9a23:YkMAHW+MMNyFZ6oJ4qCVv1UsI8A/cmPv917NBku6BDd7D6aHa3bFrQ== X-Talos-MUID: 9a23:uSQiSwlz0//031mR1nWgdnplNco0zIG8F3kkgJkKuMuiMTBeF2eC2WE= X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,196,1787011200"; d="scan'208";a="544236726" Received: from rcdn-l-core-08.cisco.com ([173.37.255.145]) by rcdn-iport-1.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 05 Nov 2026 04:56:15 +0000 Received: from sjc-ads-2020.cisco.com (sjc-ads-2020.cisco.com [171.70.61.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "ciscoit-managed-infra-smtp-auth.cisco.com", Issuer "Internal Private TLS SubCA" (verified OK)) by rcdn-l-core-08.cisco.com (Postfix) with ESMTPS id 6D5681800046B for ; Thu, 5 Nov 2026 04:56:15 +0000 (GMT) Received: by sjc-ads-2020.cisco.com (Postfix, from userid 1887505) id 13180CC124F; Wed, 4 Nov 2026 20:56:15 -0800 (PST) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Subject: [meta-python][scarthgap][PATCH 5/8] python3-pyjwt: fix CVE-2026-101917 Date: Wed, 4 Nov 2026 20:56:11 -0800 Message-Id: <20261105045614.2677776-5-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20261105045614.2677776-1-hthakar@cisco.com> References: <20261105045614.2677776-1-hthakar@cisco.com> MIME-Version: 1.0 X-Outbound-Client-TLS: VERIFIED;sjc-ads-2020.cisco.com [171.70.61.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256;ciscoit-managed-infra-smtp-auth.cisco.com X-Outbound-SMTP-Client: 171.70.61.208, sjc-ads-2020.cisco.com X-Outbound-Node: rcdn-l-core-08.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Thu, 05 Nov 2026 04:56:24 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/131856 From: Hetvi Thakar This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. Throttle repeated JWKS refreshes for unknown key IDs with a client cooldown to prevent repeated endpoint fetches. [1] https://github.com/jpadilla/pyjwt/commit/ba4853a75fb9676362da17f67d0f64bd18afd4e1 [2] https://github.com/jpadilla/pyjwt/security/advisories/GHSA-2gx3-rcp4-g85q Signed-off-by: Hetvi Thakar --- .../python3-pyjwt/CVE-2026-101917.patch | 311 ++++++++++++++++++ .../python/python3-pyjwt_2.8.0.bb | 1 + 2 files changed, 312 insertions(+) create mode 100644 meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101917.patch diff --git a/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101917.patch b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101917.patch new file mode 100644 index 0000000000..eb5d0f3014 --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101917.patch @@ -0,0 +1,311 @@ +From 92c0ba0994dd1cc57321d3c70b17974de0e8ebb5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Wed, 14 Oct 2026 05:12:02 -0700 +Subject: [PATCH] Throttle repeated PyJWKClient refreshes + +CVE: CVE-2026-101917 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/ba4853a75fb9676362da17f67d0f64bd18afd4e1] + +Backport Changes: +- Add minimal constructor and get_signing_key docstrings carrying the + upstream cooldown wording and parameter documentation because PyJWT 2.8.0 + lacks the newer method docstrings. +- Use Optional[float] for _last_successful_fetch and List[PyJWK] for the + extracted signing-key helper to match the 2.8.0 typing imports. +- Also set cooldown_duration=0 in the existing + test_get_jwt_set_no_matching_kid_after_second_attempt test, which assumes + an immediate second fetch; upstream already has that setting. + +Signed-off-by: Hetvi Thakar +--- + jwt/jwks_client.py | 67 +++++++++++++++--- + tests/test_jwks_client.py | 143 +++++++++++++++++++++++++++++++++++++- + 2 files changed, 199 insertions(+), 11 deletions(-) + +diff --git a/jwt/jwks_client.py b/jwt/jwks_client.py +index ab601a4..ab5f40c 100644 +--- a/jwt/jwks_client.py ++++ b/jwt/jwks_client.py +@@ -1,4 +1,7 @@ + import json ++import math ++import threading ++import time + import urllib.request + from functools import lru_cache + from ssl import SSLContext +@@ -36,7 +39,22 @@ class PyJWKClient: + headers: Optional[Dict[str, Any]] = None, + timeout: int = 30, + ssl_context: Optional[SSLContext] = None, ++ cooldown_duration: float = 30, + ): ++ """A client for retrieving signing keys from a JWKS endpoint. ++ ++ Unknown key IDs can trigger one forced refresh after the cooldown ++ period configured by ``cooldown_duration``. Every successful fetch ++ starts this cooldown, including the initial fetch and cache-expiry ++ fetches. A newly rotated key may therefore wait for the cooldown ++ period before it is fetched; set ``cooldown_duration`` to ``0`` to ++ disable this behavior. The cooldown is bypassed when ++ ``cache_jwk_set`` is ``False``. ++ ++ :param cooldown_duration: Minimum time in seconds between forced ++ refreshes after an unknown key ID. Defaults to ``30``. ++ :type cooldown_duration: float ++ """ + if headers is None: + headers = {} + # urllib's default OpenerDirector also handles file://, ftp://, and +@@ -54,6 +72,18 @@ class PyJWKClient: + self.headers = headers + self.timeout = timeout + self.ssl_context = ssl_context ++ if cooldown_duration < 0: ++ raise PyJWKClientError( ++ "Cooldown duration must be greater than or equal to 0, " ++ f'the input is "{cooldown_duration}"' ++ ) ++ if not math.isfinite(cooldown_duration): ++ raise PyJWKClientError( ++ f'Cooldown duration must be finite, the input is "{cooldown_duration}"' ++ ) ++ self.cooldown_duration = cooldown_duration ++ self._last_successful_fetch: Optional[float] = None ++ self._client_lock = threading.RLock() + + if cache_jwk_set: + # Init jwt set cache with default or given lifespan. +@@ -89,6 +119,7 @@ class PyJWKClient: + # finally block on error would discard a previously cached JWKS. + if self.jwk_set_cache is not None: + self.jwk_set_cache.put(jwk_set) ++ self._last_successful_fetch = time.monotonic() + return jwk_set + + def get_jwk_set(self, refresh: bool = False) -> PyJWKSet: +@@ -106,6 +137,10 @@ class PyJWKClient: + + def get_signing_keys(self, refresh: bool = False) -> List[PyJWK]: + jwk_set = self.get_jwk_set(refresh) ++ return self._get_signing_keys_from_jwk_set(jwk_set) ++ ++ @staticmethod ++ def _get_signing_keys_from_jwk_set(jwk_set: PyJWKSet) -> List[PyJWK]: + signing_keys = [ + jwk_set_key + for jwk_set_key in jwk_set.keys +@@ -118,20 +153,34 @@ class PyJWKClient: + return signing_keys + + def get_signing_key(self, kid: str) -> PyJWK: +- signing_keys = self.get_signing_keys() +- signing_key = self.match_kid(signing_keys, kid) +- +- if not signing_key: +- # If no matching signing key from the jwk set, refresh the jwk set and try again. +- signing_keys = self.get_signing_keys(refresh=True) ++ """Return the signing key matching the given ``kid``. ++ ++ If no match is found in the current JWK Set, the set is refreshed ++ from the endpoint and the lookup is retried once when the refresh ++ cooldown permits it. ++ """ ++ with self._client_lock: ++ signing_keys = self.get_signing_keys() + signing_key = self.match_kid(signing_keys, kid) + + if not signing_key: +- raise PyJWKClientError( +- f'Unable to find a signing key that matches: "{kid}"' ++ cooling_down = ( ++ self.jwk_set_cache is not None ++ and self._last_successful_fetch is not None ++ and time.monotonic() - self._last_successful_fetch ++ < self.cooldown_duration + ) ++ if not cooling_down: ++ signing_keys = self.get_signing_keys(refresh=True) ++ self._last_successful_fetch = time.monotonic() ++ signing_key = self.match_kid(signing_keys, kid) + +- return signing_key ++ if not signing_key: ++ raise PyJWKClientError( ++ f'Unable to find a signing key that matches: "{kid}"' ++ ) ++ ++ return signing_key + + def get_signing_key_from_jwt(self, token: str) -> PyJWK: + unverified = decode_token(token, options={"verify_signature": False}) +diff --git a/tests/test_jwks_client.py b/tests/test_jwks_client.py +index b3f2555..ed730d7 100644 +--- a/tests/test_jwks_client.py ++++ b/tests/test_jwks_client.py +@@ -362,7 +362,7 @@ class TestPyJWKClient: + + def test_get_jwt_set_refresh_cache(self): + url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" +- jwks_client = PyJWKClient(url) ++ jwks_client = PyJWKClient(url, cooldown_duration=0) + + kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw" + +@@ -377,7 +377,7 @@ class TestPyJWKClient: + + def test_get_jwt_set_no_matching_kid_after_second_attempt(self): + url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" +- jwks_client = PyJWKClient(url) ++ jwks_client = PyJWKClient(url, cooldown_duration=0) + + kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw" + +@@ -387,6 +387,145 @@ class TestPyJWKClient: + ): + jwks_client.get_signing_key(kid) + ++ def test_unknown_kid_refresh_is_cooled_down(self) -> None: ++ url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" ++ kid = "unknown-kid" ++ jwks_client = PyJWKClient(url, cooldown_duration=30) ++ ++ with mock.patch("urllib.request.build_opener") as build_opener_mock: ++ opener = mock.Mock() ++ build_opener_mock.return_value = opener ++ response = mock.Mock() ++ response.__enter__ = mock.Mock(return_value=response) ++ response.__exit__ = mock.Mock() ++ response.read.return_value = json.dumps(RESPONSE_DATA_NO_MATCHING_KID) ++ opener.open.return_value = response ++ ++ for _ in range(2): ++ with pytest.raises(PyJWKClientError, match="matches"): ++ jwks_client.get_signing_key(kid) ++ ++ assert opener.open.call_count == 1 ++ ++ def test_unknown_kid_refresh_runs_again_after_cooldown(self) -> None: ++ url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" ++ kid = "unknown-kid" ++ jwks_client = PyJWKClient(url, cooldown_duration=0.01) ++ ++ with mock.patch("urllib.request.build_opener") as build_opener_mock: ++ opener = mock.Mock() ++ build_opener_mock.return_value = opener ++ response = mock.Mock() ++ response.__enter__ = mock.Mock(return_value=response) ++ response.__exit__ = mock.Mock() ++ response.read.return_value = json.dumps(RESPONSE_DATA_NO_MATCHING_KID) ++ opener.open.return_value = response ++ ++ clock = [0.0] ++ with mock.patch( ++ "jwt.jwks_client.time.monotonic", side_effect=lambda: clock[0] ++ ): ++ with pytest.raises(PyJWKClientError, match="matches"): ++ jwks_client.get_signing_key(kid) ++ clock[0] = 0.02 ++ with pytest.raises(PyJWKClientError, match="matches"): ++ jwks_client.get_signing_key(kid) ++ ++ assert opener.open.call_count == 2 ++ ++ def test_unknown_kid_refresh_serializes_concurrent_misses(self) -> None: ++ url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" ++ kid = "unknown-kid" ++ jwks_client = PyJWKClient(url, cooldown_duration=30) ++ refresh_started = threading.Event() ++ second_done = threading.Event() ++ release_refresh = threading.Event() ++ ++ with mock.patch("urllib.request.build_opener") as build_opener_mock: ++ opener = mock.Mock() ++ build_opener_mock.return_value = opener ++ response = mock.Mock() ++ response.__enter__ = mock.Mock(return_value=response) ++ response.__exit__ = mock.Mock() ++ response.read.return_value = json.dumps(RESPONSE_DATA_NO_MATCHING_KID) ++ ++ opener.open.return_value = response ++ jwks_client.get_jwk_set() ++ jwks_client._last_successful_fetch = 0 ++ ++ def open_response(*args: object, **kwargs: object) -> mock.Mock: ++ if opener.open.call_count == 1: ++ refresh_started.set() ++ release_refresh.wait(timeout=5) ++ return response ++ ++ opener.open.reset_mock() ++ opener.open.side_effect = open_response ++ errors: list[Exception] = [] ++ ++ def lookup() -> None: ++ try: ++ jwks_client.get_signing_key(kid) ++ except PyJWKClientError as error: ++ errors.append(error) ++ ++ first = threading.Thread(target=lookup) ++ ++ def second_lookup() -> None: ++ lookup() ++ second_done.set() ++ ++ second = threading.Thread(target=second_lookup) ++ with mock.patch("jwt.jwks_client.time.monotonic", return_value=31): ++ first.start() ++ assert refresh_started.wait(timeout=5) ++ second.start() ++ assert not second_done.wait(timeout=0.1) ++ release_refresh.set() ++ first.join(timeout=5) ++ second.join(timeout=5) ++ ++ assert len(errors) == 2 ++ assert not first.is_alive() ++ assert not second.is_alive() ++ assert opener.open.call_count == 1 ++ ++ def test_unknown_kid_refresh_ignores_cooldown_when_cache_disabled(self) -> None: ++ url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" ++ kid = "unknown-kid" ++ jwks_client = PyJWKClient(url, cache_jwk_set=False, cooldown_duration=30) ++ ++ with mock.patch("urllib.request.build_opener") as build_opener_mock: ++ opener = mock.Mock() ++ build_opener_mock.return_value = opener ++ response = mock.Mock() ++ response.__enter__ = mock.Mock(return_value=response) ++ response.__exit__ = mock.Mock() ++ response.read.side_effect = [ ++ json.dumps(RESPONSE_DATA_NO_MATCHING_KID), ++ json.dumps(RESPONSE_DATA_NO_MATCHING_KID), ++ json.dumps(RESPONSE_DATA_NO_MATCHING_KID), ++ json.dumps(RESPONSE_DATA_NO_MATCHING_KID), ++ ] ++ opener.open.return_value = response ++ ++ for _ in range(2): ++ with pytest.raises(PyJWKClientError, match="matches"): ++ jwks_client.get_signing_key(kid) ++ ++ assert opener.open.call_count == 4 ++ ++ @pytest.mark.parametrize( ++ "cooldown_duration", [float("nan"), float("inf"), float("-inf")] ++ ) ++ def test_unknown_kid_refresh_rejects_non_finite_cooldown( ++ self, cooldown_duration: float ++ ) -> None: ++ url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" ++ ++ with pytest.raises(PyJWKClientError, match="Cooldown duration"): ++ PyJWKClient(url, cooldown_duration=cooldown_duration) ++ + def test_get_jwt_set_invalid_lifespan(self): + url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" + +-- +2.35.6 diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb index 612b1c9cde..f260bb1817 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb @@ -16,6 +16,7 @@ SRC_URI += " \ file://CVE-2026-102267.patch \ file://CVE-2026-102268_CVE-2026-102270.patch \ file://CVE-2026-102271.patch \ + file://CVE-2026-101917.patch \ " SRC_URI[sha256sum] = "57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de" From patchwork Thu Nov 5 04:56:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 102948 Received: from rcdn-iport-2.cisco.com (rcdn-iport-2.cisco.com [173.37.86.73]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.27344.1793854576576944746 for ; Wed, 04 Nov 2026 20:56:17 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=DmvjWknu; spf=pass (domain: cisco.com, ip: 173.37.86.73, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=4660; q=dns/txt; s=iport01; t=1793854576; x=1795064176; h=from:to:subject:date:message-id:in-reply-to:references: mime-version:content-transfer-encoding; bh=oFFKNeeu/9yo48L7bIOHph0EGXQnH4NPqPhV6zXAYO0=; b=DmvjWknu0x+A/rl6/DUusy0O7zht3AGygowTOW0MpP8W2cYT076WWwWr xEsbm1Zv+YyG9EHjqc8Qs5yFUO9DrhJgHrCvJ+GOE+Dlmk7QbfF0VYXgL EzyApgmXi1bbJRUopiBLWB39Yjk9H56ASIA/UsWRwnf30xnqXQskSFTld BZ7d/uY8hlLQnuIiVawUDpINVFcGTEpeLdF+fU7hRtCjYt4Chw1aFXFHg 2HBCHJpFdwRlZyYeA6E1g3hE9J4cBWSTfZrp7XMiQVhvwAp/HsD1HB98i dfj7z7EMtTayhJMVGMBwRCHWDB1aEGtjlRrYkZbQks6J7hxDmDRATId3Q g==; X-CSE-ConnectionGUID: QGWTfeyKR4KoNFTZZgkkNA== X-CSE-MsgGUID: Ly4r4N0US/K8hoc/AQxfBQ== X-IPAS-Result: A0AnAABGC+xq/5D/Ja1aHQEBAQEJARIBBQUBgXwIAQsBglZ1YUJJA4xvhziCIQOeIYF+DwEBAQ9EDQQBAYUFAo4cAiY0CQ4BAgQDAgMBAQEBAQEBAQEBAQEKAQEFAQEBAgEHBYEOE4ZPDZASAQIBAycLARgBPRwDAQIvKyMIGYMCAYJ0AgERvEuBeTOBAYMpATEFgR7bMwELFAGBOAGFP4MDgRGED10YAYR9JxsbgXKBFYNpgQWBXAKIJgSCInoSgVqBDIwMAoFShnBIgR4DWSwBVRMNCgsHBYFoAzUSKhVuMh2BIz4XgQcbBgWBHYEegRKBB12BKyheIYEIgXYCglGCAAIBSUEOB0VTCSVDEkckIggSCQETGiwKgRo1PAkmPxUMZAsaBWEIAg0McjkQMB4kLwUcJkAbChgYDUgRLDcVGQQ+bgeSL4JkLYEOASsXaBJyKR6TGQGSPqERCiiDeIwklToaM4VbpRYLmH2OCpVhcIRpgWg8gVlwFYMiCUoZD44rDguDYIVkyG8jNQIJAy8BAQcCBw4DC4FokX4BAQ IronPort-Data: A9a23:fPZWPaww9bmBAJ5xKSB6t+dmxyrEfRIJ4+MujC+fZmUNrF6WrkUHy TNJCjuOb/uOYTDwc90gb4TkpkoFvJ6Gz94yT1BvpFhgHilAwSbn6Xt1DatR0we6dJCroJdPt p1GAjX4BJlqCCKa/lHyYuCJQUBUjcmgXqD7BPPPJhd/TAplTDZJoR94kobVuKYw6TSCK13L4 4yaT/H3Ygf/hWctaj5MscpvlTs21BjMkGJA1rABTagjUG/2zxE9EJ8ZLKetGHr0KqE8NvK6X evK0Iai9Wrf+Ro3Yvv9+losWhRXKlJ6FVHmZkt+A8BOsDAbzsAB+vpT2M4nVKtio27hc+adZ zl6ncfYpQ8BZsUgkQmGOvVSO3kW0aZuoNcrLZUj2CCe5xWuTpfi/xlhJHgZArEW9/dUOz1t8 to2FAI/YROAocvjldpXSsE07igiBNPgMIVavjRryivUSK55B5vCWK7No9Rf2V/chOgXQq2YP JVfM2cyKk2cP3WjOX9PYH46tOe0hnD8eidwo1OOrq1x6G/WpOB0+OW2aICMI4PQGq25mG68t z7co0KhPypdF4yfzjHC20u8iMX2yHaTtIU6UefQGuRRqFqLy2oeDRcbWVe2rby1h1CzX/pbK lcI4WwptaU0+UmhQ9XxUhH+p2SL1iPwQPJKGOE8rQXIwa3O7kPBWS4PTyVKb5ots8peqSEW6 2JlVujBXVRH2IB5g1rEnltIhVte4RQoEFI= IronPort-HdrOrdr: A9a23:oTXho6rCFEA64Vxz/JExbnQaV5oHeYIsimQD101hICG9Ffbo8/ xG88506faZslsssTQb6LO90cq7MBbhHOBOgLX5VI3KNGKNhILrFvAB0WKI+VLd8kPFmtK1rZ 0BT4FOTPvtEFN9kcH2pCO8E9om3Z271ZrAv5a485+oJjsaEp2JKGxCe2CmLnE= X-Talos-CUID: 9a23:EGE1iW5O1RaGySOGe9sszWErJs4oY17nkFTXBWGEO1RmS76zYArF X-Talos-MUID: 9a23:4Lr4KQjPH+aTMlE2R/WOjcMpENY0+fiIUH021rZWu86PcgVJKzm3tWHi X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,196,1787011200"; d="scan'208";a="530211727" Received: from rcdn-l-core-07.cisco.com ([173.37.255.144]) by rcdn-iport-2.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 05 Nov 2026 04:56:15 +0000 Received: from sjc-ads-2020.cisco.com (sjc-ads-2020.cisco.com [171.70.61.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "ciscoit-managed-infra-smtp-auth.cisco.com", Issuer "Internal Private TLS SubCA" (verified OK)) by rcdn-l-core-07.cisco.com (Postfix) with ESMTPS id C0221180003DA for ; Thu, 5 Nov 2026 04:56:15 +0000 (GMT) Received: by sjc-ads-2020.cisco.com (Postfix, from userid 1887505) id 19688CC1250; Wed, 4 Nov 2026 20:56:15 -0800 (PST) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Subject: [meta-python][scarthgap][PATCH 6/8] python3-pyjwt: fix CVE-2026-102275 Date: Wed, 4 Nov 2026 20:56:12 -0800 Message-Id: <20261105045614.2677776-6-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20261105045614.2677776-1-hthakar@cisco.com> References: <20261105045614.2677776-1-hthakar@cisco.com> MIME-Version: 1.0 X-Outbound-Client-TLS: VERIFIED;sjc-ads-2020.cisco.com [171.70.61.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256;ciscoit-managed-infra-smtp-auth.cisco.com X-Outbound-SMTP-Client: 171.70.61.208, sjc-ads-2020.cisco.com X-Outbound-Node: rcdn-l-core-07.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Thu, 05 Nov 2026 04:56:23 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/131858 From: Hetvi Thakar This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. Require the public component of an imported OKP private JWK to match the public key derived from its private component. [1] https://github.com/jpadilla/pyjwt/commit/3cd9ceec33ced359decbad75b413ad668ae6332c [2] https://github.com/jpadilla/pyjwt/security/advisories/GHSA-x33g-cr3x-6449 Signed-off-by: Hetvi Thakar --- .../python3-pyjwt/CVE-2026-102275.patch | 81 +++++++++++++++++++ .../python/python3-pyjwt_2.8.0.bb | 1 + 2 files changed, 82 insertions(+) create mode 100644 meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102275.patch diff --git a/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102275.patch b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102275.patch new file mode 100644 index 0000000000..9d281bee1b --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-102275.patch @@ -0,0 +1,81 @@ +From 64f02b83a79f25b687eaf990f7f871ece4faea8f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Wed, 14 Oct 2026 05:12:30 -0700 +Subject: [PATCH] fix: reject inconsistent OKP private keys + +CVE: CVE-2026-102275 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/3cd9ceec33ced359decbad75b413ad668ae6332c] + +Signed-off-by: Hetvi Thakar +--- + jwt/algorithms.py | 14 ++++++++++++-- + tests/test_algorithms.py | 29 +++++++++++++++++++++++++++++ + 2 files changed, 41 insertions(+), 2 deletions(-) + +diff --git a/jwt/algorithms.py b/jwt/algorithms.py +index fcfca89..38c0a2e 100644 +--- a/jwt/algorithms.py ++++ b/jwt/algorithms.py +@@ -893,8 +893,18 @@ if has_crypto: + return Ed25519PublicKey.from_public_bytes(x) + return Ed448PublicKey.from_public_bytes(x) + d = base64url_decode(obj.get("d")) ++ private_key: Ed25519PrivateKey | Ed448PrivateKey + if curve == "Ed25519": +- return Ed25519PrivateKey.from_private_bytes(d) +- return Ed448PrivateKey.from_private_bytes(d) ++ private_key = Ed25519PrivateKey.from_private_bytes(d) ++ else: ++ private_key = Ed448PrivateKey.from_private_bytes(d) ++ if ( ++ private_key.public_key().public_bytes( ++ encoding=Encoding.Raw, format=PublicFormat.Raw ++ ) ++ != x ++ ): ++ raise InvalidKeyError("Public key does not match private key") ++ return private_key + except ValueError as err: + raise InvalidKeyError("Invalid key parameter") from err +diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py +index c843903..afc569b 100644 +--- a/tests/test_algorithms.py ++++ b/tests/test_algorithms.py +@@ -906,6 +906,35 @@ class TestAlgorithmsRFC7520: + + @crypto_required + class TestOKPAlgorithms: ++ @pytest.mark.parametrize("curve", ("Ed25519", "Ed448")) ++ @pytest.mark.parametrize("as_dict", (False, True)) ++ def test_okp_jwk_should_reject_inconsistent_private_key( ++ self, curve: str, as_dict: bool ++ ) -> None: ++ with open(key_path(f"jwk_okp_key_{curve}.json")) as keyfile: ++ jwk = json.load(keyfile) ++ ++ d = base64url_decode(jwk["d"]) ++ jwk["d"] = ( ++ base64.urlsafe_b64encode(bytes([d[0] ^ 1]) + d[1:]).rstrip(b"=").decode() ++ ) ++ ++ with pytest.raises(InvalidKeyError): ++ OKPAlgorithm.from_jwk(jwk if as_dict else json.dumps(jwk)) ++ ++ @pytest.mark.parametrize("curve", ("Ed25519", "Ed448")) ++ @pytest.mark.parametrize("length", (0, 1, 56, 58)) ++ def test_okp_jwk_should_reject_private_key_with_invalid_public_length( ++ self, curve: str, length: int ++ ) -> None: ++ with open(key_path(f"jwk_okp_key_{curve}.json")) as keyfile: ++ jwk = json.load(keyfile) ++ ++ jwk["x"] = base64.urlsafe_b64encode(b"\x00" * length).rstrip(b"=").decode() ++ ++ with pytest.raises(InvalidKeyError): ++ OKPAlgorithm.from_jwk(jwk) ++ + hello_world_sig = b"Qxa47mk/azzUgmY2StAOguAd4P7YBLpyCfU3JdbaiWnXM4o4WibXwmIHvNYgN3frtE2fcyd8OYEaOiD/KiwkCg==" + hello_world = b"Hello World!" + +-- +2.35.6 diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb index f260bb1817..c235499d24 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb @@ -17,6 +17,7 @@ SRC_URI += " \ file://CVE-2026-102268_CVE-2026-102270.patch \ file://CVE-2026-102271.patch \ file://CVE-2026-101917.patch \ + file://CVE-2026-102275.patch \ " SRC_URI[sha256sum] = "57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de" From patchwork Thu Nov 5 04:56:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 102947 Received: from rcdn-iport-5.cisco.com (rcdn-iport-5.cisco.com [173.37.86.76]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.27343.1793854576557325274 for ; Wed, 04 Nov 2026 20:56:17 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=NMHCs/EN; spf=pass (domain: cisco.com, ip: 173.37.86.76, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=10557; q=dns/txt; s=iport01; t=1793854576; x=1795064176; h=from:to:subject:date:message-id:in-reply-to:references: mime-version:content-transfer-encoding; bh=nseztBQOARNR0EE2fofIzZWoT/MtJ5mRWTNXZwEYW20=; b=NMHCs/ENLHZ+PG6HmRnVBRBb8Yl6G4nJRSy3Nh1jnI6Me7vcJagdy0st TqXbJd49cSoDGAGW93ynLKOhFzBjPjPv1CzxPcuw6+CJsi2mSwLCGEMA8 hzuGxqm74fRhn+G5fVJMT9BwiY8jcQYkGMd99rgXaJpMpHKUAaj5d++VA 9l+KZV0q4mpIk800HbS1iy8rUTWFSTS9zzKLT7JUHaG09ljlL+b/heJLd dTJ7gmG2+jGf9YSMJ/zxhXODQkeYy9RbzZo+5yIDUQW9ZtDHJvwXhnvyQ SPCsMxpK39PpND43AIg9hDNKGJ/BaqIKwv24R1np4/ju9QCDZUmX0an2j A==; X-CSE-ConnectionGUID: PEdyv5oLRcCnvJMzx/lJOQ== X-CSE-MsgGUID: LGwe60vqSV24+CtVPuJlRg== X-IPAS-Result: A0BJAgBGC+xq/5P/Ja1aEwEBCQEBCxIMggULgld1YUJJA5QngiEDniGBfg8BAQEPRA0EAQGFBQKOHAImNAkOAQIEAwIDAQEBAQEBAQEBAQEBCgEBBQEBAQIBBwWBDhOGTw2QEgECAQMnCwEYAT0cAwECLysjCBmDAgGCdAIBEQa8RYF5M4EBgykBgVTbMwELFAGBOIVAiCNdGAGEfScbG4FygRWDaYEFgVwCiCYEgiJ6EoFajRgCgVKGcEiBHgNZLAFVEw0KCwcFgWgDNRIqFW4yHYEjPheBBxsGBYEdgR6BEoEHXYErKF4hgQiBdgKCUYIAAgFJQQ4HRVMJJUMSRyQiCBIJARMaLAqBGjU8CSY/FQxkCxoFYQgCDQxyORAwHiQvBRwmQBsKGBgNSBEsNxUZBD5uB5IvgxGBDgEHJBdogS0ekyGSN6ERCiiDeIwklToaM6pxC5h9jgqBTZQUIFCEaYFoPIFZcBWDIglKGQ+OLgsLg2DOUyM1AgkDLwEBBwIHDgMLgWiRfgEB IronPort-Data: A9a23:1Mm57alb/I+pYvZNX5k4COPo5gzQJ0RdPkR7XQ2eYbSJt1+Wr1Gzt xIbX2iCO67YNzOnLot2atux8x4BuJPdnIVgHABkpHxhH1tH+JHPbTi7wugcHM8zwunrFh8PA xA2M4GYRCwMZiaC4Errav6+/SEUOZigHtLUEPTDNj16WThqQSIgjQMLs+Mii+aEu/Dha++2k Y20+ZK31GONgWYubDtNs/7b8nuDgdyr0N8mlg1mDRx0lAe2e0k9VPo3Oay3Jn3kdYhYdsbSb /rD1ryw4lTC9B4rDN6/+p6jGqHdauePVeQmoiM+t5mK2nCulARrukoIHKZ0hXNsttm8t4sZJ OOhGnCHYVxB0qXkwIzxWvTDes10FfUuFLTveRBTvSEPpqHLWyOE/hlgMK05FbU19vdpXGZqz /czLi4vSwKTwOaU54vuH4GAhux7RCXqFJkUtnclyXTSCuwrBMmbBa7L/tRfmjw3g6iiH96HO JFfMmUpNkmdJUQTZT/7C7pm9AusrnnjczRboUi9rqss6G+Vxwt0uFToGIeJJoPWG50OwS50o Eri0Hv0XEkELeehk2bG2W6SvvLtmRn0Ddd6+LqQs6QCbEeo7msLBRsbUFG2rfW0hguyVsxSL 2QQ+zEytu417EGtQ9z3UhG0rXLCuQQTM+e8CMUg4w2Lj66R6AGDCy1dFnhKaccts4k9QjlCO kK1ou4FzAdH6NW9IU9xPJ/Nxd9uEUD59VM/WBI= IronPort-HdrOrdr: A9a23:54yaMa+q6fvo0Z0VGaluk+D0I+orL9Y04lQ7vn2ZLiYlFvBw+P rBoB1273LJYXMqKRIdcLO7Scy9qA3nlKKdiLN5VdzJYOClghrLEGgI1+TfKlPbdxEWjtQ86Y 5QN45jFdb3EV92yez+4AW+DpIc5ePvytHOuQ8bpE0dND2DrMpbnmFENjo= X-Talos-CUID: 9a23:tryG6WNvoUf1J+5DYARr5VBJHeQfeVbb0lX1MV//DUROcejA X-Talos-MUID: 9a23:Xfd6VgY5LObUdeBTkxDziW86Kt5T3f7yS2sRr60bmc2+Onkl X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,196,1787011200"; d="scan'208";a="544746037" Received: from rcdn-l-core-10.cisco.com ([173.37.255.147]) by rcdn-iport-5.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 05 Nov 2026 04:56:15 +0000 Received: from sjc-ads-2020.cisco.com (sjc-ads-2020.cisco.com [171.70.61.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "ciscoit-managed-infra-smtp-auth.cisco.com", Issuer "Internal Private TLS SubCA" (verified OK)) by rcdn-l-core-10.cisco.com (Postfix) with ESMTPS id C34F11800025B for ; Thu, 5 Nov 2026 04:56:15 +0000 (GMT) Received: by sjc-ads-2020.cisco.com (Postfix, from userid 1887505) id 200D3CC2EF2; Wed, 4 Nov 2026 20:56:15 -0800 (PST) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Subject: [meta-python][scarthgap][PATCH 7/8] python3-pyjwt: fix CVE-2026-101918 Date: Wed, 4 Nov 2026 20:56:13 -0800 Message-Id: <20261105045614.2677776-7-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20261105045614.2677776-1-hthakar@cisco.com> References: <20261105045614.2677776-1-hthakar@cisco.com> MIME-Version: 1.0 X-Outbound-Client-TLS: VERIFIED;sjc-ads-2020.cisco.com [171.70.61.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256;ciscoit-managed-infra-smtp-auth.cisco.com X-Outbound-SMTP-Client: 171.70.61.208, sjc-ads-2020.cisco.com X-Outbound-Node: rcdn-l-core-10.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Thu, 05 Nov 2026 04:56:23 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/131857 From: Hetvi Thakar This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. Normalize recursive JSON payload failures to DecodeError. Include the upstream follow-up [3] as a separate CVE-2026-101918_p2.patch. It makes the regression tests deterministic and verifies that payload recursion errors cause no JWKS request. [1] https://github.com/jpadilla/pyjwt/commit/5fde08a6cf906aa7698de2d6391d88b73006b17b [2] https://github.com/jpadilla/pyjwt/security/advisories/GHSA-42vr-xj54-vc7v [3] https://github.com/jpadilla/pyjwt/commit/9bc06658f875b9b40091539140bbbdc4639161c3 Signed-off-by: Hetvi Thakar --- .../python3-pyjwt/CVE-2026-101918_p1.patch | 121 ++++++++++++++++++ .../python3-pyjwt/CVE-2026-101918_p2.patch | 109 ++++++++++++++++ .../python/python3-pyjwt_2.8.0.bb | 2 + 3 files changed, 232 insertions(+) create mode 100644 meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101918_p1.patch create mode 100644 meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101918_p2.patch diff --git a/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101918_p1.patch b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101918_p1.patch new file mode 100644 index 0000000000..be2a17d507 --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101918_p1.patch @@ -0,0 +1,121 @@ +From 5fde08a6cf906aa7698de2d6391d88b73006b17b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Tue, 22 Sep 2026 11:44:19 -0400 +Subject: [PATCH] fix: normalize recursive JWT payload errors + +CVE: CVE-2026-101918 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/5fde08a6cf906aa7698de2d6391d88b73006b17b] + +Backport Changes: +- Add explicit exception chaining to the PyJWT 2.8.0 payload parser, + which lacks the upstream raise-from clause used by the regression tests. +- Add DecodeError to the existing single-line jwt.exceptions import in + tests/test_jwks_client.py; newer upstream-only exceptions are absent. + +Signed-off-by: Hetvi Thakar +--- + jwt/api_jwt.py | 4 ++-- + tests/test_api_jwt.py | 18 +++++++++++++++++- + tests/test_jwks_client.py | 22 +++++++++++++++++++++- + 3 files changed, 40 insertions(+), 4 deletions(-) + +diff --git a/jwt/api_jwt.py b/jwt/api_jwt.py +index 48d739a..ae0dc35 100644 +--- a/jwt/api_jwt.py ++++ b/jwt/api_jwt.py +@@ -176,8 +176,8 @@ class PyJWT: + """ + try: + payload = json.loads(decoded["payload"]) +- except ValueError as e: +- raise DecodeError(f"Invalid payload string: {e}") ++ except (ValueError, RecursionError) as e: ++ raise DecodeError(f"Invalid payload string: {e}") from e + if not isinstance(payload, dict): + raise DecodeError("Invalid payload string: must be a json object") + return payload +diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py +index 2598b12..0a5f91c 100644 +--- a/tests/test_api_jwt.py ++++ b/tests/test_api_jwt.py +@@ -6,6 +6,7 @@ from decimal import Decimal + + import pytest + ++import jwt as pyjwt + from jwt.api_jwt import PyJWT + from jwt.exceptions import ( + DecodeError, +@@ -16,7 +17,7 @@ from jwt.exceptions import ( + InvalidIssuerError, + MissingRequiredClaimError, + ) +-from jwt.utils import base64url_decode ++from jwt.utils import base64url_decode, base64url_encode + from jwt.warnings import RemovedInPyjwt3Warning + + from .utils import crypto_required, key_path, utc_timestamp +@@ -34,6 +35,21 @@ def payload(): + + + class TestJWT: ++ def test_decode_deeply_nested_payload_throws_decode_error(self) -> None: ++ nested_payload = b"[" * 100_000 + b"]" * 100_000 ++ token = ".".join( ++ ( ++ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", ++ base64url_encode(nested_payload).decode(), ++ "", ++ ) ++ ) ++ ++ with pytest.raises(DecodeError, match="Invalid payload") as exc: ++ pyjwt.decode(token, options={"verify_signature": False}) ++ ++ assert isinstance(exc.value.__cause__, RecursionError) ++ + def test_decodes_valid_jwt(self, jwt): + example_payload = {"hello": "world"} + example_secret = "secret" +diff --git a/tests/test_jwks_client.py b/tests/test_jwks_client.py +index ed730d7..43530df 100644 +--- a/tests/test_jwks_client.py ++++ b/tests/test_jwks_client.py +@@ -13,7 +13,8 @@ import jwt + from jwt import PyJWKClient + from jwt.jwks_client import _NoRedirectHandler + from jwt.api_jwk import PyJWK +-from jwt.exceptions import PyJWKClientConnectionError, PyJWKClientError ++from jwt.exceptions import DecodeError, PyJWKClientConnectionError, PyJWKClientError ++from jwt.utils import base64url_encode + + from .utils import crypto_required + +@@ -103,6 +104,25 @@ def mocked_timeout(): + + @crypto_required + class TestPyJWKClient: ++ def test_get_signing_key_from_jwt_rejects_deeply_nested_payload(self) -> None: ++ nested_payload = b"[" * 100_000 + b"]" * 100_000 ++ header = b'{"alg":"RS256","kid":"test-key"}' ++ token = ".".join( ++ ( ++ base64url_encode(header).decode(), ++ base64url_encode(nested_payload).decode(), ++ "", ++ ) ++ ) ++ ++ with mocked_success_response(RESPONSE_DATA_WITH_MATCHING_KID) as open_mock: ++ jwks_client = PyJWKClient("https://example.test/.well-known/jwks.json") ++ with pytest.raises(DecodeError, match="Invalid payload") as exc: ++ jwks_client.get_signing_key_from_jwt(token) ++ ++ assert isinstance(exc.value.__cause__, RecursionError) ++ assert open_mock.call_count == 0 ++ + def test_fetch_data_rejects_redirect_without_contacting_destination(self) -> None: + destination_requests = 0 + +-- +2.35.6 diff --git a/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101918_p2.patch b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101918_p2.patch new file mode 100644 index 0000000000..379e622212 --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-101918_p2.patch @@ -0,0 +1,109 @@ +From 9bc06658f875b9b40091539140bbbdc4639161c3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jos=C3=A9=20Padilla?= +Date: Wed, 23 Sep 2026 09:32:36 -0400 +Subject: [PATCH] fix: make recursive payload tests deterministic + +CVE: CVE-2026-101918 +Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/9bc06658f875b9b40091539140bbbdc4639161c3] + +Signed-off-by: Hetvi Thakar +--- + tests/test_api_jwt.py | 22 ++++++++++------------ + tests/test_jwks_client.py | 24 +++++++++++------------- + 2 files changed, 21 insertions(+), 25 deletions(-) + +diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py +index 0a5f91c..67d9a8a 100644 +--- a/tests/test_api_jwt.py ++++ b/tests/test_api_jwt.py +@@ -3,6 +3,7 @@ import time + from calendar import timegm + from datetime import datetime, timedelta, timezone + from decimal import Decimal ++from unittest import mock + + import pytest + +@@ -17,7 +18,7 @@ from jwt.exceptions import ( + InvalidIssuerError, + MissingRequiredClaimError, + ) +-from jwt.utils import base64url_decode, base64url_encode ++from jwt.utils import base64url_decode + from jwt.warnings import RemovedInPyjwt3Warning + + from .utils import crypto_required, key_path, utc_timestamp +@@ -35,18 +36,15 @@ def payload(): + + + class TestJWT: +- def test_decode_deeply_nested_payload_throws_decode_error(self) -> None: +- nested_payload = b"[" * 100_000 + b"]" * 100_000 +- token = ".".join( +- ( +- "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9", +- base64url_encode(nested_payload).decode(), +- "", +- ) +- ) ++ def test_decode_payload_recursion_error_throws_decode_error(self) -> None: ++ token = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.e30." + +- with pytest.raises(DecodeError, match="Invalid payload") as exc: +- pyjwt.decode(token, options={"verify_signature": False}) ++ with mock.patch( ++ "jwt.api_jwt.json.loads", ++ side_effect=[{"alg": "none", "typ": "JWT"}, RecursionError()], ++ ): ++ with pytest.raises(DecodeError, match="Invalid payload") as exc: ++ pyjwt.decode(token, options={"verify_signature": False}) + + assert isinstance(exc.value.__cause__, RecursionError) + +diff --git a/tests/test_jwks_client.py b/tests/test_jwks_client.py +index 43530df..4333722 100644 +--- a/tests/test_jwks_client.py ++++ b/tests/test_jwks_client.py +@@ -14,7 +14,6 @@ from jwt import PyJWKClient + from jwt.jwks_client import _NoRedirectHandler + from jwt.api_jwk import PyJWK + from jwt.exceptions import DecodeError, PyJWKClientConnectionError, PyJWKClientError +-from jwt.utils import base64url_encode + + from .utils import crypto_required + +@@ -104,21 +103,20 @@ def mocked_timeout(): + + @crypto_required + class TestPyJWKClient: +- def test_get_signing_key_from_jwt_rejects_deeply_nested_payload(self) -> None: +- nested_payload = b"[" * 100_000 + b"]" * 100_000 +- header = b'{"alg":"RS256","kid":"test-key"}' +- token = ".".join( +- ( +- base64url_encode(header).decode(), +- base64url_encode(nested_payload).decode(), +- "", +- ) +- ) ++ def test_get_signing_key_from_jwt_rejects_payload_recursion_error(self) -> None: ++ token = "eyJhbGciOiJSUzI1NiIsImtpZCI6InRlc3Qta2V5In0.e30." + + with mocked_success_response(RESPONSE_DATA_WITH_MATCHING_KID) as open_mock: + jwks_client = PyJWKClient("https://example.test/.well-known/jwks.json") +- with pytest.raises(DecodeError, match="Invalid payload") as exc: +- jwks_client.get_signing_key_from_jwt(token) ++ with mock.patch( ++ "jwt.api_jwt.json.loads", ++ side_effect=[ ++ {"alg": "RS256", "kid": "test-key"}, ++ RecursionError(), ++ ], ++ ): ++ with pytest.raises(DecodeError, match="Invalid payload") as exc: ++ jwks_client.get_signing_key_from_jwt(token) + + assert isinstance(exc.value.__cause__, RecursionError) + assert open_mock.call_count == 0 +-- +2.35.6 diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb index c235499d24..b3a1c49726 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.8.0.bb @@ -18,6 +18,8 @@ SRC_URI += " \ file://CVE-2026-102271.patch \ file://CVE-2026-101917.patch \ file://CVE-2026-102275.patch \ + file://CVE-2026-101918_p1.patch \ + file://CVE-2026-101918_p2.patch \ " SRC_URI[sha256sum] = "57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de" From patchwork Thu Nov 5 04:56:14 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 102949 Received: from rcdn-iport-1.cisco.com (rcdn-iport-1.cisco.com [173.37.86.72]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.27345.1793854576653952957 for ; Wed, 04 Nov 2026 20:56:17 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=Tq3qxdiD; spf=pass (domain: cisco.com, ip: 173.37.86.72, mailfrom: hthakar@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=1204; q=dns/txt; s=iport01; t=1793854577; x=1795064177; h=from:to:subject:date:message-id:in-reply-to:references: mime-version:content-transfer-encoding; bh=EtUErMX/FFL3bcoGajubbov8Jy+n2JGbkUIZtlZ0/Ew=; b=Tq3qxdiDLc9xbWbdqY26tAPllXORZ1m4myUXAuHSTjp5ULhAcl163UV/ C9/kJGxmQ8N40NXxU5bFkX1KZNXMg08EcfZEf7prJzoXDr5VEXS5TYWmG OohrAjWxsaDVnQciBvosy51MsCGeKXS367dLB9F1fkk8I25bZ/YSzWI5z r2yNEM7tOXvibqcRvsVZHlPBY8EDvJmW98yW6ypmf4uIqwmF7ox4bdgaC sousm2bju/mJUCwYn1ITSotwfQmz2wsxWZ+It89UQE1x7ihKmYK9rkA/s 6eTYw/XGK3r/yQZ/mf6a4AFUpOg+te/OFwixX7Iy4+vXv4hmLQg4v0j9Z A==; X-CSE-ConnectionGUID: yzn6MzWaS+GtsWg/DVst8A== X-CSE-MsgGUID: RPmyyf57TraBJcJXLq2z+A== X-IPAS-Result: A0BEAgCOCuxq/5D/Ja1aglmCV3VhQkmUKoIkniGBfg8BAQEPRA0EAQGFBQKOHAImNAkOAQIEAwIDAQEBAQEBAQEBAQEBCgEBBQEBAQIBBwWBDhOGTw2QEwIBAycLAVYgMSsrGYMCAYJ0AgERvEeBeTOBAYR+2zMBBQYUAYE4hUCII3aEfScbG4FyhH6BBQqBUgKBJ4EGhXkEgiKBDI5yiERIgR4DWSwBVRMNCgsHBYFoAzUSKhVuMh2BIz4XgQcbBgWBHYEegRKBB12BKyheIYEIgXYCglGCAAIBSUEOB0VTCSVDEkckIggSCQETGiwKgRo1PAkmPxUMZAsaBWEIAg0McjkQMB4kLwUcJkAbChgYDUgRLDcVGQQ+bgeSL4MRgQ4sgWymNqERCiiDeIwklToaM6pxmQiOCpZRhGmBaDyBWXAVgyJTGQ+OKw4LHINEzlMjNQsyAQEHAgcOAwuBaJAAgX4BAQ IronPort-Data: A9a23:QJAW66i/tw1jAIr8rdkMqBQrX161MREKZh0ujC45NGQN5FlHY01je htvUWqOPvqMMDPzcoolOYi29E1U7ZDXzd9jTlY5qH08Qi1jpJueD7x1DKtf0wB+jyHnZBg6h ynLQoCYdKjYdleF+FHwdOCn8ikkvU2xbuKUIPbePSxsThNTRi4kiBZy88Y0mYcAbeKRW2thg vus5ZeAULOZ82QsaDxNsvrf8EkHUMna4Vv0gHRvPZing3eG/5UlJMp3Db28KXL+Xr5VEoaSL 87fzKu093/u5BwkDNWoiN7TKiXmlZaLYGBiIlIPM0STqkAqSh4ai87XB9JAAatjsAhlqvgqo Dl7WTNcfi9yVkHEsLx1vxC1iEiSN4UekFPMCSDXXcB+UyQqflO0q8iCAn3aMqUJ6OApJHNx/ MdbBywWUR/Eq9yt3OKSH7wEasQLdKEHPasFsX1miDWcBvE8TNWbHOPB5MRT23E7gcUm8fT2P pVCL2EwKk6dPlsWZgt/5JEWxI9EglH/bz1Rq1uPjaE2+GPUigd21dABNfKIJoDQHpsKxBnwS mTuxVz8EwAUaYOj0zOv0HmOtsvWxzG4cddHfFG/3rsw6LGJ/UQUEBAQWF6xrPW1h0L7V99BJ kg8/is1sbN081SmSNT4VRC0rHOI+BkGVLJt//YS8gqBzO/Qpg2eHGVBFmEHY909v8hwTjsvv rOUo+7U6fVUmOX9YRqgGn2891te5QB9wbc+WBI5 IronPort-HdrOrdr: A9a23:dMwFbK0s6HgCStU+/uG1sgqjBJ4kLtp133Aq2lEZdPUzSL39qy nAppomPHPP5Qr5HUtQ+uxoW5PwJE80i6QV3WB5B97LN2PbUSmTXeNfBODZrAEIdReTygck78 ddWpk7LsHsBl5nisu/ygy5H9E8hOSjysmT9IDjJ7MHd3ASV0mmhD0JbDqmLg== X-Talos-CUID: 9a23:X/mUOW9ei2GA00i8RViVv18GHJ4AYELU9zT/P1SHBiFFdqaJGXbFrQ== X-Talos-MUID: 9a23:0c5+oApyRlheP5HDB/QezwtaMdZ57fnxNFoEu7cNmfmgLHZieB7I2Q== X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,196,1787011200"; d="scan'208";a="544236727" Received: from rcdn-l-core-07.cisco.com ([173.37.255.144]) by rcdn-iport-1.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 05 Nov 2026 04:56:15 +0000 Received: from sjc-ads-2020.cisco.com (sjc-ads-2020.cisco.com [171.70.61.208]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "ciscoit-managed-infra-smtp-auth.cisco.com", Issuer "Internal Private TLS SubCA" (verified OK)) by rcdn-l-core-07.cisco.com (Postfix) with ESMTPS id C3B0A180003DC for ; Thu, 5 Nov 2026 04:56:15 +0000 (GMT) Received: by sjc-ads-2020.cisco.com (Postfix, from userid 1887505) id 26BA3CC2EF3; Wed, 4 Nov 2026 20:56:15 -0800 (PST) From: "Hetvi Thakar -X (hthakar - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Subject: [meta-python][scarthgap][PATCH 8/8] python3-pyjwt: fix CVE-2026-32597 test indentation Date: Wed, 4 Nov 2026 20:56:14 -0800 Message-Id: <20261105045614.2677776-8-hthakar@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20261105045614.2677776-1-hthakar@cisco.com> References: <20261105045614.2677776-1-hthakar@cisco.com> MIME-Version: 1.0 X-Outbound-Client-TLS: VERIFIED;sjc-ads-2020.cisco.com [171.70.61.208];TLSv1.3;TLS_AES_256_GCM_SHA384;256;ciscoit-managed-infra-smtp-auth.cisco.com X-Outbound-SMTP-Client: 171.70.61.208, sjc-ads-2020.cisco.com X-Outbound-Node: rcdn-l-core-07.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Thu, 05 Nov 2026 04:56:23 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/131859 From: Hetvi Thakar Correct the indentation of the unknown critical-header regression test in the CVE-2026-32597 backport [1]. The malformed method definition prevents pytest from collecting tests/test_api_jws.py and blocks the JWS regression suite. [1] https://github.com/jpadilla/pyjwt/commit/051ea341b5573fe3edcd53042f347929b92c2b92 Signed-off-by: Hetvi Thakar --- .../recipes-devtools/python/python3-pyjwt/CVE-2026-32597.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-32597.patch b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-32597.patch index c38628d0b3..20e25aa911 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-32597.patch +++ b/meta-python/recipes-devtools/python/python3-pyjwt/CVE-2026-32597.patch @@ -99,7 +99,7 @@ index 3385716..434874b 100644 assert len(record) == 1 assert "foo" in str(record[0].message) + -+ def test_decode_rejects_unknown_crit_extension( ++ def test_decode_rejects_unknown_crit_extension( + self, jws: PyJWS, payload: bytes + ) -> None: + secret = "secret"