diff --git a/CHANGELOG.md b/CHANGELOG.md index f414f212..a66d4801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Reject a number written with non-ASCII digits (e.g. `1.٠`) or followed by a whitespace control character (e.g. `1` and a vertical tab), which `int()` and `float()` accepted but TOML does not allow. + ## [0.15.2] - 2026-10-16 ### Changed diff --git a/tests/test_parser.py b/tests/test_parser.py index 36c0ae98..2b30b079 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -274,6 +274,25 @@ def test_parser_accepts_uppercase_exponent_after_leading_zero() -> None: assert value == float(raw) +@pytest.mark.parametrize( + "raw", + [ + "\u0660", # ARABIC-INDIC DIGIT ZERO + "+\u0660", + "1.\u0660", + "1e\u0660", + "\u0661\u0662", + "1\x0b", # vertical tab + "1\x0c", # form feed + "1.5\x0b", + ], +) +def test_parser_rejects_non_ascii_digits_and_whitespace_in_numbers(raw: str) -> None: + # int() and float() accept these, but TOML numbers are ASCII digits only. + with pytest.raises(ParseError): + parse(f"v = {raw}\n") + + @pytest.mark.parametrize("sign", ["", "+", "-"]) @pytest.mark.parametrize( "raw", diff --git a/tomlkit/parser.py b/tomlkit/parser.py index dc08f928..016f71cd 100644 --- a/tomlkit/parser.py +++ b/tomlkit/parser.py @@ -778,6 +778,11 @@ def _parse_number(self, raw: str, trivia: Trivia) -> Item | None: if "_" in clean: return None + # int() and float() also accept non-ASCII digits and surrounding + # whitespace, which are not valid in a TOML number. + if not clean.isascii() or any(ch.isspace() for ch in clean): + return None + # Special float values must be lowercase in the original token. if clean in ("inf", "nan") and raw != clean: return None