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