From 88e41d1f3bd65e48b8700caff7f990ecb938e17f Mon Sep 17 00:00:00 2001 From: Hetvi Thakar Date: Thu, 15 Oct 2026 19:33:22 -0700 Subject: [PATCH] python3-pyjwt: fix CVE-2026-101918 This patch applies the upstream fix as referenced in [2], using the commit shown in [1]. [1] https://github.com/jpadilla/pyjwt/commit/5fde08a6cf906aa7698de2d6391d88b73006b17b [2] https://github.com/jpadilla/pyjwt/security/advisories/GHSA-42vr-xj54-vc7v Signed-off-by: Hetvi Thakar Signed-off-by: Anuj Mittal --- .../python/files/CVE-2026-101918.patch | 122 ++++++++++++++++++ .../python/python3-pyjwt_2.12.1.bb | 1 + 2 files changed, 123 insertions(+) create mode 100644 meta-python/recipes-devtools/python/files/CVE-2026-101918.patch diff --git a/meta-python/recipes-devtools/python/files/CVE-2026-101918.patch b/meta-python/recipes-devtools/python/files/CVE-2026-101918.patch new file mode 100644 index 0000000000..4041a554ff --- /dev/null +++ b/meta-python/recipes-devtools/python/files/CVE-2026-101918.patch @@ -0,0 +1,122 @@ +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: +- Adapted the PyJWKClient regression-test imports to the older Wrynose + test module, which lacks later unrelated PyJWKSet imports. + +(cherry picked from commit 5fde08a6cf906aa7698de2d6391d88b73006b17b) +Signed-off-by: Hetvi Thakar +--- + jwt/api_jwt.py | 2 +- + tests/test_api_jwt.py | 18 +++++++++++++++++- + tests/test_jwks_client.py | 26 +++++++++++++++++++++++++- + 3 files changed, 43 insertions(+), 3 deletions(-) + +diff --git a/jwt/api_jwt.py b/jwt/api_jwt.py +index f298ea9..d6917c0 100644 +--- a/jwt/api_jwt.py ++++ b/jwt/api_jwt.py +@@ -293,7 +293,7 @@ class PyJWT: + """ + try: + payload: dict[str, Any] = json.loads(decoded["payload"]) +- except ValueError as 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") +diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py +index dd28816..96f9179 100644 +--- a/tests/test_api_jwt.py ++++ b/tests/test_api_jwt.py +@@ -7,6 +7,7 @@ from decimal import Decimal + + import pytest + ++import jwt as pyjwt + from jwt.types import Options + from jwt.api_jwk import PyJWK + from jwt.api_jwt import PyJWT +@@ -21,7 +22,7 @@ from jwt.exceptions import ( + InvalidSubjectError, + 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 +@@ -177,6 +178,21 @@ class TestJWT: + + assert "Invalid payload string" in str(exc.value) + ++ 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_decode_with_non_mapping_payload_throws_exception(self, jwt: PyJWT) -> None: + secret = "secret" + example_jwt = ( +diff --git a/tests/test_jwks_client.py b/tests/test_jwks_client.py +index c66dfd7..cf6f6e1 100644 +--- a/tests/test_jwks_client.py ++++ b/tests/test_jwks_client.py +@@ -16,7 +16,12 @@ 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 + +@@ -298,6 +303,25 @@ class TestPyJWKClient: + "gty": "client-credentials", + } + ++ 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_get_jwk_set_caches_result(self) -> None: + url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" + +-- +2.35.6 diff --git a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb index 8953a781de..8b3ea9da1c 100644 --- a/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb +++ b/meta-python/recipes-devtools/python/python3-pyjwt_2.12.1.bb @@ -18,6 +18,7 @@ SRC_URI += "file://CVE-2026-48522.patch \ file://CVE-2026-102271.patch \ file://CVE-2026-101917.patch \ file://CVE-2026-102275.patch \ + file://CVE-2026-101918.patch \ " SRC_URI[sha256sum] = "c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b"