python3-pyjwt: Fix CVE-2026-48525

Reject a non-empty compact payload segment for b64=false tokens before
Base64URL decoding. The segment is unused for detached JWS verification,
so decoding it allowed unauthenticated CPU and memory consumption.

This patch applies the relevant subset of the upstream 2.13.0 fix.
The upstream commit is referenced in [1], and the public advisory is
referenced in [2].

[1] https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81
[2] https://github.com/advisories/GHSA-w7vc-732c-9m39

Signed-off-by: Hetvi Thakar <hthakar@cisco.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Hetvi Thakar
2026-08-05 22:50:59 -07:00
committed by Anuj Mittal
parent fffe550dfc
commit b9877579dc
2 changed files with 116 additions and 0 deletions
@@ -0,0 +1,115 @@
From 91ac94bdc85d24c6d35a5f6cdd58eb8c6c4df0f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jos=C3=A9=20Padilla?= <jpadilla@users.noreply.github.com>
Date: Mon, 3 Aug 2026 03:21:30 -0700
Subject: [PATCH] api_jws: reject non-empty detached payload segments
For b64=false tokens, reject a non-empty compact payload segment before
Base64URL decoding. The segment is unused when detached_payload is
supplied, so decoding attacker-controlled data only consumes CPU and
memory.
CVE: CVE-2026-48525
Upstream-Status: Backport [https://github.com/jpadilla/pyjwt/commit/95791b1759b8aa4f2203575d344d5c78564cdc81]
Backport Changes:
- Extracted only the CVE-2026-48525 detached-payload segment check and
regression test from the bundled upstream 2.13.0 commit. The other
requested CVE fixes are carried as separate patches.
- Adapted the hunk and test locations to PyJWT 2.8.0, used module-level
hashlib and hmac imports, and shortened explanatory comments without
changing the tested behavior.
- Excluded the separate RFC 7797 b64/crit hardening bundled upstream.
- Omitted the 2.13.0 version and changelog updates, CVE-2026-48523 (which
does not affect 2.8.0), and unrelated hardening from the bundled commit.
(cherry picked from commit 95791b1759b8aa4f2203575d344d5c78564cdc81)
Signed-off-by: Hetvi Thakar <hthakar@cisco.com>
---
jwt/api_jws.py | 17 +++++++++++++----
tests/test_api_jws.py | 34 +++++++++++++++++++++++++++++++++-
2 files changed, 46 insertions(+), 5 deletions(-)
diff --git a/jwt/api_jws.py b/jwt/api_jws.py
index 1750442..0d5ab2b 100644
--- a/jwt/api_jws.py
+++ b/jwt/api_jws.py
@@ -274,10 +274,19 @@ class PyJWS:
if not isinstance(header, dict):
raise DecodeError("Invalid header string: must be a json object")
- try:
- payload = base64url_decode(payload_segment)
- except (TypeError, binascii.Error) as err:
- raise DecodeError("Invalid payload padding") from err
+ if header.get("b64", True) is False:
+ # Detached compact serialization requires an empty payload
+ # segment. Reject it before decoding attacker-controlled data.
+ if payload_segment:
+ raise DecodeError(
+ "Payload segment must be empty when 'b64' is false."
+ )
+ payload = b""
+ else:
+ try:
+ payload = base64url_decode(payload_segment)
+ except (TypeError, binascii.Error) as err:
+ raise DecodeError("Invalid payload padding") from err
try:
signature = base64url_decode(crypto_segment)
diff --git a/tests/test_api_jws.py b/tests/test_api_jws.py
index 434874b..29f84a7 100644
--- a/tests/test_api_jws.py
+++ b/tests/test_api_jws.py
@@ -1,3 +1,5 @@
+import hashlib
+import hmac
import json
from decimal import Decimal
@@ -11,7 +13,7 @@ from jwt.exceptions import (
InvalidSignatureError,
InvalidTokenError,
)
-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, no_crypto_required
@@ -766,6 +768,36 @@ class TestJWS:
assert "b64" not in msg_header_obj
assert msg_payload
+ def test_decode_b64_false_rejects_non_empty_payload_segment(
+ self, jws: PyJWS, payload: bytes
+ ) -> None:
+ secret = "secret"
+ header = {
+ "typ": "JWT",
+ "alg": "HS256",
+ "b64": False,
+ "crit": ["b64"],
+ }
+ encoded_header = base64url_encode(
+ json.dumps(header, separators=(",", ":")).encode()
+ )
+ attacker_segment = b"A" * 1024
+ signing_input = b".".join([encoded_header, payload])
+ signature = hmac.new(
+ secret.encode(), signing_input, hashlib.sha256
+ ).digest()
+ token = b".".join(
+ [encoded_header, attacker_segment, base64url_encode(signature)]
+ ).decode()
+
+ with pytest.raises(DecodeError, match="Payload segment must be empty"):
+ jws.decode(
+ token,
+ secret,
+ algorithms=["HS256"],
+ detached_payload=payload,
+ )
+
def test_decode_detached_content_without_proper_argument(self, jws):
example_jws = (
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2V9"
@@ -9,6 +9,7 @@ SRC_URI += " \
file://CVE-2026-32597.patch \
file://CVE-2026-48522.patch \
file://CVE-2026-48524.patch \
file://CVE-2026-48525.patch \
"
SRC_URI[sha256sum] = "57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"