Block deceptive look-alike usernames (admin/support impersonation, brand homoglyphs, leet, typosquatting) at registration time.
Project description
django-username-guard
Bloque la création de comptes Django avec des noms d'utilisateur trompeurs :
mots réservés (admin, support, noreply...), et leurs variantes
typosquattées : admin1strateur, adminiistrateur, admlnistrateur,
аdmin (cyrillique), 4dmin, etc. — et l'imitation de marques
(leboncoin-support, lebоncoin, service_leboncoin...).
Pourquoi ?
Sur les places de marché type Leboncoin / Vinted, une partie des arnaques (exemple Numerama : « arnaque à l'IBAN ») repose sur des comptes qui se font passer pour le support officiel de la plateforme — souvent en exploitant des homoglyphes Unicode, du leet, ou des fautes de frappe sur la marque. Cet addon empêche la création initiale de tels comptes.
⚠️ Ce n'est pas une solution complète anti-phishing — c'est une couche parmi d'autres (badges officiels, vérification d'identité, détection de comportement, modération…). Mais bloquer ces noms à l'inscription élimine un vecteur d'ingénierie sociale très bon marché pour l'attaquant.
Comment ça marche
Trois couches de défense, toutes appliquées sur la forme normalisée du username :
- Normalisation — NFKC → casefold → mappage des homoglyphes
(cyrillique, grec, IPA) → suppression des accents → dé-leet
(
4→a,1→i,0→o,@→a...) → suppression des non-alphanumériques → collapse des lettres répétées (admiiin→admin). - Match exact / sous-chaîne / fuzzy contre la blocklist
(
admin,support...) : Damerau-Levenshtein ≤ 1, sous-chaîne avec budget de longueur (admin42bloqué,adminlovesmusicautorisé). - Protection de marque — pour les noms que tu déclares dans
BRANDS, match plus strict : toute sous-chaîne ou variante fuzzy est bloquée (pas de budget de longueur).leboncoin_helper,support-leboncoin,lebоncoin(cyrillique),leb0ncoin, tous bloqués.
Installation
pip install django-username-guard
# settings.py
INSTALLED_APPS = [
...,
"username_guard",
]
Utilisation
Sur ton modèle User personnalisé
# accounts/models.py
from django.contrib.auth.models import AbstractUser
from username_guard import DeceptiveUsernameValidator
class User(AbstractUser):
username_validators = [
*AbstractUser.username_validators,
DeceptiveUsernameValidator(),
]
Sur ton formulaire d'inscription
from username_guard.forms import GuardedUserCreationForm
# Drop-in replacement de UserCreationForm
Manuel
from django.core.exceptions import ValidationError
from username_guard import DeceptiveUsernameValidator
validate = DeceptiveUsernameValidator()
try:
validate("admin1strateur")
except ValidationError as e:
print(e.code, e.message) # deceptive_username | brand_impersonation
CLI (auditer une base existante)
python manage.py check_username admin1strateur leboncoin-support alice --show-normalized
Configuration
Tout est optionnel (settings.py) :
USERNAME_GUARD = {
# Termes réservés. Override complet :
# "BLOCKLIST": ("admin", "support", ...),
# Plus simple — ajouter à la liste par défaut EN/FR :
"EXTRA_BLOCKLIST": ("ceo", "founder", "monequipe"),
# Marques que tu veux protéger d'usurpation. RÈGLE STRICTE :
# toute sous-chaîne ou typo proche est rejetée.
"BRANDS": ("leboncoin", "monsite", "monapp"),
# Distance d'édition tolérée (0 = match exact uniquement)
"MAX_DISTANCE": 1,
# Rejeter les sous-chaînes (admin1, admin_, xadmin) ?
"REJECT_SUBSTRING": True,
# Budget de longueur pour la règle sous-chaîne (BLOCKLIST uniquement)
# admin (5) + 3 = 8 caractères max -> "admin42" bloqué, "adminlover" non
"SUBSTRING_PADDING": 3,
# Taille minimale d'un terme blocklist pour activer la règle sous-chaîne
"MIN_TERM_LEN_FOR_SUBSTRING": 4,
}
Exemples couverts
| Username | Bloqué ? | Code | Pourquoi |
|---|---|---|---|
admin |
✅ | deceptive_username |
match exact |
Administrateur |
✅ | deceptive_username |
casefold |
admin1strateur |
✅ | deceptive_username |
leet 1→i |
adminiistrateur |
✅ | deceptive_username |
ii→i |
admlnistrateur |
✅ | deceptive_username |
DL=1 |
аdmin (cyrillique) |
✅ | deceptive_username |
homoglyphe |
4dmin |
✅ | deceptive_username |
leet 4→a |
r00t |
✅ | deceptive_username |
leet 0→o |
support1 |
✅ | deceptive_username |
sous-chaîne bornée |
leboncoin (BRANDS) |
✅ | brand_impersonation |
match exact |
leboncoin-support |
✅ | brand_impersonation |
sous-chaîne marque |
lebоncoin (cyrillique) |
✅ | brand_impersonation |
homoglyphe о→o |
leb0ncoin |
✅ | brand_impersonation |
leet sur la marque |
service_leboncoin |
✅ | brand_impersonation |
sous-chaîne marque |
adminlovesmusic |
❌ | trop long, plausible | |
vivien, alice |
❌ | légitime |
Threat model
Couvert :
- Imitation de comptes admin / support génériques
- Imitation de marque via homoglyphes Unicode (cyrillique, grec, IPA)
- Leet-speak (
4,1,0,@,$...) - Lettres dupliquées (
adminiistrateur) - Fautes de frappe à 1 édition (
admlnistrateur) - Bruit de ponctuation (
ad-min_42)
Non couvert (par design) :
- Les noms parfaitement légitimes qui contiennent un mot-clé en sous-chaîne
longue (
adminlovesmusic). ActiveMIN_TERM_LEN_FOR_SUBSTRINGplus bas ou ajoute le terme dansBRANDSsi ton domaine l'exige. - Les attaques runtime (account takeover, changement de pseudo après vérification, badge officiel falsifié dans l'UI) — c'est hors-scope d'un validateur de création.
- La couverture complète d'UTS #39. Branche
confusable_homoglyphssi tu en as besoin (PR bienvenue pour rendre ça optionnel).
Performance
DL distance pure-Python lancée contre chaque terme : O(n × m) par username. Avec une blocklist de < 200 termes c'est négligeable (< 0.5 ms). Si tu pousses au-delà, on peut ajouter un index BK-tree — ouvre une issue.
Développement
git clone https://github.com/viviengiraud/django-username-guard
cd django-username-guard
uv sync --all-extras
uv run pytest
Voir CONTRIBUTING.md.
Licence
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_username_guard-0.2.0.tar.gz.
File metadata
- Download URL: django_username_guard-0.2.0.tar.gz
- Upload date:
- Size: 11.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
344d152a8613dc2a550b392808188e93d88e58c8c4d27307c95bd68f3ab98dbb
|
|
| MD5 |
f8b277f4348bae0f32a2310955ae9156
|
|
| BLAKE2b-256 |
475c2ffaae56e356c7dcf4c77b4d450f64991e29e84ef5da5bbe950082b0086a
|
File details
Details for the file django_username_guard-0.2.0-py3-none-any.whl.
File metadata
- Download URL: django_username_guard-0.2.0-py3-none-any.whl
- Upload date:
- Size: 12.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.9 {"installer":{"name":"uv","version":"0.11.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5a941189dacbf15fd712e95de5c72b15c424179d5fa4f37b9e31166ea3638c80
|
|
| MD5 |
98be4d6437e6590c48108804d557e832
|
|
| BLAKE2b-256 |
89953a97a31389ab49936b1759bdfec527310547c717eb582e68083fd6054858
|