Skip to content

Commit ecf605d

Browse files
committed
fix(auth): add homoglyph validation
This avoids confusing user names.
1 parent 4871150 commit ecf605d

File tree

5 files changed

+52
-0
lines changed

5 files changed

+52
-0
lines changed

docs/changes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Weblate 5.15
2424

2525
* :ref:`mt-deepl` integration now correctly handles translating to Chinese variants.
2626
* :doc:`/formats/csv` format saving translations with empty source fields when using monolingual base files.
27+
* Tighter validation of user and full names to avoid confusing homoglyphs.
2728

2829
.. rubric:: Compatibility
2930

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ dependencies = [
109109
"celery[redis]>=5.5.3,<5.7",
110110
"certifi>=2025.10.5",
111111
"charset-normalizer>=3.4.0,<4.0",
112+
"confusable-homoglyphs>=3.3.1,<3.4",
112113
"crispy-bootstrap3==2024.1",
113114
"crispy-bootstrap5==2025.6",
114115
"cryptography>=45.0.1",

uv.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

weblate/utils/tests/test_validators.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
validate_project_web,
2323
validate_re,
2424
validate_repo_url,
25+
validate_username,
2526
validate_webhook_secret_string,
2627
)
2728

@@ -65,6 +66,10 @@ def test_whitespace(self) -> None:
6566
def test_none(self) -> None:
6667
self.assertIsNone(clean_fullname(None))
6768

69+
def test_homoglyph(self) -> None:
70+
with self.assertRaises(ValidationError):
71+
validate_fullname("Alloρ")
72+
6873
def test_invalid(self) -> None:
6974
with self.assertRaises(ValidationError):
7075
validate_fullname("ahoj\x00bar")
@@ -78,6 +83,27 @@ def test_html(self) -> None:
7883
validate_fullname("<h1>User</h1>")
7984

8085

86+
class UserNameCleanTest(SimpleTestCase):
87+
def test_good(self) -> None:
88+
validate_username("ahoj")
89+
90+
def test_homoglyph(self) -> None:
91+
with self.assertRaises(ValidationError):
92+
validate_username("Alloρ")
93+
94+
def test_invalid(self) -> None:
95+
with self.assertRaises(ValidationError):
96+
validate_username("ahoj\x00bar")
97+
98+
def test_crud(self) -> None:
99+
with self.assertRaises(ValidationError):
100+
validate_username(".")
101+
102+
def test_html(self) -> None:
103+
with self.assertRaises(ValidationError):
104+
validate_username("<h1>User</h1>")
105+
106+
81107
class EmailValidatorTestCase(SimpleTestCase):
82108
def test_valid(self) -> None:
83109
validator = EmailValidator()

weblate/utils/validators.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from typing import cast
1818
from urllib.parse import urlparse
1919

20+
from confusable_homoglyphs import confusables
2021
from disposable_email_domains import blocklist
2122
from django.conf import settings
2223
from django.core.exceptions import ValidationError
@@ -155,6 +156,12 @@ def validate_fullname(val):
155156
raise ValidationError(
156157
gettext("Please avoid using special characters in the full name.")
157158
)
159+
160+
if confusables.is_dangerous(val):
161+
raise ValidationError(
162+
gettext("This name cannot be registered. Please choose a different one.")
163+
)
164+
158165
# Validates full name that would be rejected by Git
159166
if CRUD_RE.match(val):
160167
raise ValidationError(gettext("Name consists only of disallowed characters."))
@@ -183,6 +190,12 @@ def validate_username(value) -> None:
183190
"numbers or the following characters: @ . + - _"
184191
)
185192
)
193+
if confusables.is_dangerous(value):
194+
raise ValidationError(
195+
gettext(
196+
"This username cannot be registered. Please choose a different one."
197+
)
198+
)
186199

187200

188201
class EmailValidator(EmailValidatorDjango):

0 commit comments

Comments
 (0)