From 1a7737d6ebea1ae0157965358424585c4b4b75e7 Mon Sep 17 00:00:00 2001
From: Simo Sorce <simo@redhat.com>
Date: Mon, 17 Aug 2026 10:01:07 -0400
Subject: [PATCH] Limit dot splits during JWE and JWS parsing

When parsing compact JWE and JWS tokens, str.split('.') was previously
executed without a maxsplit argument. Malformed tokens containing excessive
dots could cause unnecessary memory allocation and processing overhead.

By specifying maxsplit parameters (5 for JWE and 3 for JWS), parsing
efficiency is improved and tokens with too many components are quickly
rejected as invalid.

Assisted-by: Gemini:Gemini 3.6 Flash
Signed-off-by: Simo Sorce <simo@redhat.com>
---
 jwcrypto/jwe.py   |  4 +++-
 jwcrypto/jws.py   |  4 +++-
 jwcrypto/tests.py | 33 +++++++++++++++++++++++++++++++++
 3 files changed, 39 insertions(+), 2 deletions(-)

Index: jwcrypto-1.5.7/jwcrypto/jwe.py
===================================================================
--- jwcrypto-1.5.7.orig/jwcrypto/jwe.py
+++ jwcrypto-1.5.7/jwcrypto/jwe.py
@@ -552,7 +552,9 @@ class JWE:
                         o['header'] = json_encode(djwe['header'])
 
             except ValueError as e:
-                data = raw_jwe.split('.')
+                # process up to 5 dots, if we have 5+ dots we get more than 5
+                # chunks and that means we have a malformed JWE
+                data = raw_jwe.split('.', 5)
                 if len(data) != 5:
                     raise InvalidJWEData() from e
                 p = base64url_decode(data[0])
Index: jwcrypto-1.5.7/jwcrypto/jws.py
===================================================================
--- jwcrypto-1.5.7.orig/jwcrypto/jws.py
+++ jwcrypto-1.5.7/jwcrypto/jws.py
@@ -461,7 +461,9 @@ class JWS:
                         o['payload'] = djws['payload']
 
             except ValueError:
-                data = raw_jws.split('.')
+                # process up to 3 dots, if we have 3+ dots we get more than 3
+                # chunks and that means we have a malformed JWS
+                data = raw_jws.split('.', 3)
                 if len(data) != 3:
                     raise InvalidJWSObject('Unrecognized'
                                            ' representation') from None
Index: jwcrypto-1.5.7/jwcrypto/tests.py
===================================================================
--- jwcrypto-1.5.7.orig/jwcrypto/tests.py
+++ jwcrypto-1.5.7/jwcrypto/tests.py
@@ -1158,6 +1158,23 @@ class TestJWS(unittest.TestCase):
 
         self.assertEqual(header, header_copy)
 
+    def test_compact_invalid_dots(self):
+        key = jwk.JWK.generate(kty='oct', size=256)
+        s = jws.JWS(payload='test')
+        s.add_signature(key, protected={"alg": "HS256"})
+        compact = s.serialize(compact=True)
+        self.assertEqual(compact.count('.'), 2)
+
+        token_3_dots = compact + ".extra"
+        token_10_dots = compact + "." + ".".join(["extra"] * 8)
+
+        j = jws.JWS()
+        with self.assertRaises(jws.InvalidJWSObject):
+            j.deserialize(token_3_dots)
+
+        with self.assertRaises(jws.InvalidJWSObject):
+            j.deserialize(token_10_dots)
+
     def test_decrypt_keyset(self):
         ks = jwk.JWKSet()
         key1 = jwk.JWK.generate(kty='oct', alg='HS256', kid='key1')
@@ -1450,6 +1467,22 @@ class TestJWE(unittest.TestCase):
                         recipient=E_A1_ex['key'])
             e.serialize(compact=True)
 
+    def test_compact_invalid_dots(self):
+        e = jwe.JWE(E_A1_ex['plaintext'], E_A1_ex['protected'])
+        e.add_recipient(E_A1_ex['key'])
+        compact = e.serialize(compact=True)
+        self.assertEqual(compact.count('.'), 4)
+
+        token_5_dots = compact + ".extra"
+        token_10_dots = compact + "." + ".".join(["extra"] * 6)
+
+        d = jwe.JWE()
+        with self.assertRaises(jwe.InvalidJWEData):
+            d.deserialize(token_5_dots)
+
+        with self.assertRaises(jwe.InvalidJWEData):
+            d.deserialize(token_10_dots)
+
     def test_JWE_Issue_136(self):
         plaintext = "plain"
         protected = json_encode(Issue_136_Protected_Header_no_epk)
