https://github.com/python/cpython/commit/743482a87e3769f8b71db9db35e7ffa798e36b15
commit: 743482a87e3769f8b71db9db35e7ffa798e36b15
branch: 3.13
author: Petr Viktorin <[email protected]>
committer: Yhg1s <[email protected]>
date: 2026-06-09T22:20:07+02:00
summary:

[3.13] gh-149231: tomllib: Limit the number of parts in a key (GH-149233) 
(GH-149815) (#149848)

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

(cherry picked from commit bc7c102f3462a9f014f3ac2546acfb471b2a7eae)
(cherry picked from commit 724a5e5e3b4ab3e943992193d79f83429d56d6d8)

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 9e677a337a2835f..e6c9155d1a930e9 100644
--- a/Lib/test/test_tomllib/test_misc.py
+++ b/Lib/test/test_tomllib/test_misc.py
@@ -113,3 +113,16 @@ def test_inline_table_recursion_limit(self):
                               nest_count=nest_count):
                 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)
diff --git a/Lib/tomllib/_parser.py b/Lib/tomllib/_parser.py
index 9c80a6a547dce91..a1f7ffd6d24e313 100644
--- a/Lib/tomllib/_parser.py
+++ b/Lib/tomllib/_parser.py
@@ -6,8 +6,9 @@
 
 from collections.abc import Iterable
 import string
+import sys
 from types import MappingProxyType
-from typing import Any, BinaryIO, NamedTuple
+from typing import Any, BinaryIO, NamedTuple, Final
 
 from ._re import (
     RE_DATETIME,
@@ -19,6 +20,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 = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
 
 # Neither of these sets include quotation mark or backslash. They are
@@ -385,6 +393,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 000000000000000..c265b54db8bed47
--- /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