From b7484036d638122acf22ca4714f0c2ba8072e6e0 Mon Sep 17 00:00:00 2001 From: Anthony Date: Fri, 23 Oct 2026 10:57:29 +0530 Subject: [PATCH 1/2] Require ASCII digits in Russian INNs --- src/validators/i18n/ru.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/validators/i18n/ru.py b/src/validators/i18n/ru.py index 2b6da7f2..7311cffb 100644 --- a/src/validators/i18n/ru.py +++ b/src/validators/i18n/ru.py @@ -1,5 +1,9 @@ """Russia.""" +# standard +import re + +# local from validators.utils import validator @@ -30,7 +34,7 @@ def ru_inn(value: str): - For 10-digit INN: checks 10th control digit - For 12-digit INN: checks both 11th and 12th control digits """ - if not value: + if not value or not re.fullmatch(r"[0-9]+", value): return False try: From b0d688c2ff1e3a45938cc0a5518447d5b2393762 Mon Sep 17 00:00:00 2001 From: Anthony Date: Fri, 23 Oct 2026 10:58:01 +0530 Subject: [PATCH 2/2] Test non-ASCII digit rejection in Russian INNs --- tests/i18n/test_ru.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/i18n/test_ru.py b/tests/i18n/test_ru.py index 5c7017e4..e53d7795 100644 --- a/tests/i18n/test_ru.py +++ b/tests/i18n/test_ru.py @@ -65,3 +65,23 @@ def test_individual_inn_requires_both_check_digits(value: str): def test_individual_inn_check_digit_remainders(value: str): """Accept both ordinary remainders and remainder 10 mapped to zero.""" assert ru_inn(value) is True + + +@pytest.mark.parametrize("value", ["7830002293", "500100732259"]) +@pytest.mark.parametrize("zero", ["\u0660", "\uff10", "\u0966"]) +def test_rejects_non_ascii_inn_digits(value: str, zero: str): + """Reject digit lookalikes in company and individual INNs.""" + translated = value.translate( + str.maketrans("0123456789", "".join(chr(ord(zero) + n) for n in range(10))) + ) + assert isinstance(ru_inn(translated), ValidationError) + assert ru_inn(value) is True + + +@pytest.mark.parametrize("value", ["7830002293", "500100732259"]) +@pytest.mark.parametrize("position", [0, -1]) +def test_rejects_mixed_ascii_and_non_ascii_inn_digits(value: str, position: int): + """A single non-ASCII digit must not be normalized by int().""" + digits = list(value) + digits[position] = chr(ord("\u0660") + int(digits[position])) + assert isinstance(ru_inn("".join(digits)), ValidationError)