https://github.com/python/cpython/commit/bc7c102f3462a9f014f3ac2546acfb471b2a7eae
commit: bc7c102f3462a9f014f3ac2546acfb471b2a7eae
branch: main
author: Petr Viktorin <[email protected]>
committer: encukou <[email protected]>
date: 2026-05-04T13:49:07+02:00
summary:

gh-149231: tomllib: Limit the number of parts in a key (GH-149233)

Co-authored-by: Stan Ulbrych <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-05-01-16-45-31.gh-issue-149231.x2nBEE.rst
M Lib/test/test_tomllib/test_misc.py
M Lib/tomllib/_parser.py

diff --git a/Lib/test/test_tomllib/test_misc.py 
b/Lib/test/test_tomllib/test_misc.py
index abd0842d10b254..af7ab91bba7cd1 100644
--- a/Lib/test/test_tomllib/test_misc.py
+++ b/Lib/test/test_tomllib/test_misc.py
@@ -119,6 +119,19 @@ def test_inline_table_recursion_limit(self):
                 recursive_table_toml = nest_count * "key = {" + nest_count * 
"}"
                 tomllib.loads(recursive_table_toml)
 
+    def test_key_recursion_limit(self):
+        nest_count = tomllib._parser.MAX_KEY_PARTS - 2
+        nested_key_toml = "a." * nest_count + "a = 1"
+        tomllib.loads(nested_key_toml)
+
+        nest_count = tomllib._parser.MAX_KEY_PARTS + 2
+        nested_key_toml = "a." * nest_count + "a = 1"
+        with self.assertRaisesRegex(
+            RecursionError,
+            r"TOML key has more than the allowed [0-9]+ parts",
+        ):
+            tomllib.loads(nested_key_toml)
+
     def test_types_import(self):
         """Test that `_types` module runs.
 
diff --git a/Lib/tomllib/_parser.py b/Lib/tomllib/_parser.py
index 8aa01301dcea32..b89934808008ef 100644
--- a/Lib/tomllib/_parser.py
+++ b/Lib/tomllib/_parser.py
@@ -29,6 +29,13 @@
 
     from ._types import Key, ParseFloat, Pos
 
+# Pathologically excessive number of parts in a key runs into quadratic
+# behavior (e.g. in Flags.is_).
+# Even if keys aren't currently parsed using recursion, they name a
+# recursive structure, so it makes sense to limit it using getrecursionlimit()
+# and RecursionError.
+MAX_KEY_PARTS: Final = sys.getrecursionlimit()
+
 ASCII_CTRL: Final = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
 
 # Neither of these sets include quotation mark or backslash. They are
@@ -470,6 +477,10 @@ def parse_key(src: str, pos: Pos) -> tuple[Pos, Key]:
         pos = skip_chars(src, pos, TOML_WS)
         pos, key_part = parse_key_part(src, pos)
         key += (key_part,)
+        if len(key) > MAX_KEY_PARTS:
+            raise RecursionError(
+                f"TOML key has more than the allowed {MAX_KEY_PARTS} parts"
+            )
         pos = skip_chars(src, pos, TOML_WS)
 
 
diff --git 
a/Misc/NEWS.d/next/Library/2026-05-01-16-45-31.gh-issue-149231.x2nBEE.rst 
b/Misc/NEWS.d/next/Library/2026-05-01-16-45-31.gh-issue-149231.x2nBEE.rst
new file mode 100644
index 00000000000000..c265b54db8bed4
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-05-01-16-45-31.gh-issue-149231.x2nBEE.rst
@@ -0,0 +1 @@
+In :mod:`tomllib`, the number of parts in TOML keys is now limited.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to