Index: idna-3.7/idna/core.py
===================================================================
--- idna-3.7.orig/idna/core.py
+++ idna-3.7/idna/core.py
@@ -230,6 +230,17 @@ def check_label(label: Union[str, bytes,
         label = label.decode('utf-8')
     if len(label) == 0:
         raise IDNAError('Empty Label')
+    # Reject oversized labels before per-codepoint validation runs.
+    # CONTEXTJ/CONTEXTO checks scan the whole label per codepoint, so an
+    # uncapped label drives validation into quadratic time
+    # (GHSA-65pc-fj4g-8rjx / CVE-2024-3651). encode()/decode() cap the
+    # whole-domain length; this cap protects direct callers of
+    # alabel/ulabel/check_label and the idna2008 incremental codec.
+    # Use the whole-domain bound rather than the per-label DNS bound so
+    # that UTS #46 lenient decoding of labels longer than 63 chars is
+    # preserved.
+    if not valid_string_length(label, trailing_dot=True):
+        raise IDNAError("Label too long")
 
     check_nfc(label)
     check_hyphen_ok(label)
Index: idna-3.7/tests/test_idna.py
===================================================================
--- idna-3.7.orig/tests/test_idna.py
+++ idna-3.7/tests/test_idna.py
@@ -78,6 +78,30 @@ class IDNATests(unittest.TestCase):
         self.assertFalse(idna.valid_label_length('a' * 64))
         self.assertRaises(idna.IDNAError, idna.encode, 'a' * 64)
 
+    def test_oversized_label_rejected_promptly(self):
+        # The whole-domain cap in encode()/decode() does not cover direct
+        # callers of alabel/ulabel/check_label, nor the idna2008
+        # incremental codec which calls alabel/ulabel per label. Without a
+        # per-label cap, a single oversized CONTEXTO-heavy label still
+        # drives validation into quadratic time.
+        import codecs
+        import time
+
+        import idna.codec  # noqa: F401  (register the idna2008 codec)
+
+        payload = "・" * 8000 + "漢"
+        start = time.perf_counter()
+        self.assertRaises(idna.IDNAError, idna.check_label, payload)
+        self.assertRaises(idna.IDNAError, idna.alabel, payload)
+        self.assertRaises(idna.IDNAError, idna.ulabel, payload)
+        self.assertRaises(
+            idna.IDNAError,
+            codecs.getincrementalencoder("idna2008")().encode,
+            payload,
+            True,
+        )
+        self.assertLess(time.perf_counter() - start, 1.0)
+
     def test_check_bidi(self):
 
         l = '\u0061'
