Regex GitHub @username mention
The reference Stack Overflow question's author wanted to spot "@username" mentions in a paragraph to link them to their GitHub profile. Their own regex, posted in the question, has a real flaw they noticed themselves: it doesn't recognize single-character usernames like "@a". Our ReDoS analysis uncovers a second, worse problem: it's vulnerable (polynomial complexity), verified on a string of plain repeated "@" characters. The same design flaw (a lookahead that scans the rest of the whole text instead of staying within the current username) also makes it fail on an otherwise valid username as soon as a double hyphen shows up later in the text. A 4-vote answer fixes the single-character bug but inherits the second flaw. Another attempt, drawn from a comment describing real "grandfathered" GitHub accounts with looser historical rules (like github.com/artur-), deliberately accepts forms the current rules forbid. Here's a safe pattern that fixes these flaws, compared against these 3 real attempts.
/\B@([a-z0-9](?:-(?=[a-z0-9])|[a-z0-9]){0,38}(?<=[a-z0-9]))/iComparison of existing patterns
| Source | pattern | Score on the corpus | ReDoS |
|---|---|---|---|
| Stack Overflow — regex de l'auteur de la question, ne reconnaît pas les pseudos à 1 caractère (bug constaté dans la question elle-même) et scrute tout le reste du texte pour un double tiret | \B@((?!.*(-){2,}.*)[a-z0-9][a-z0-9-]{0,38}[a-z0-9]) | 17 / 20 | vulnerable (@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@…) |
| Stack Overflow — motif suggéré en commentaire pour couvrir de vrais comptes historiques aux règles plus permissives, accepte tirets/underscores en tête ou en fin (contraire aux règles actuelles) | \B@[a-z0-9_-]{1,39} | 17 / 20 | safe |
| Stack Overflow — réponse à 4 votes, corrige le bug du pseudo à 1 caractère mais hérite du même lookahead scrutant tout le reste du texte | \B@(?!.*(-){2,}.*)[a-z0-9](?:[a-z0-9-]{0,37}[a-z0-9])?\b | 18 / 20 | undetermined |
Implementation
const re = new RegExp("\\B@([a-z0-9](?:-(?=[a-z0-9])|[a-z0-9]){0,38}(?<=[a-z0-9]))", "i");
re.test(input);Limits
- Reflète les règles de nommage ACTUELLES de GitHub, pas l'historique complet : un commentaire sur le fil source signale l'existence de vrais comptes plus anciens (comme github.com/artur-) conservés tels quels malgré des règles depuis durcies — un tel identifiant historique serait rejeté par ce motif.
- S'arrête au premier caractère invalide plutôt que de rejeter tout le token : sur « @ignore-last-dash- », il reconnaît « @ignore-last-dash » en ignorant le tiret final, un choix de conception hérité du fil source (comportement demandé explicitement par l'auteur de la question), pas une validation stricte de bout en bout.
- Ne vérifie pas que le nom d'utilisateur correspond à un compte GitHub réellement existant : seule la forme est vérifiée, pas l'inscription réelle sur la plateforme.
- Le « \B » qui précède le « @ » distingue une mention d'une adresse e-mail dans la plupart des cas courants, mais reste une heuristique : un texte inhabituel (ponctuation collée sans espace avant un « @ » de mention) peut théoriquement dérouter cette distinction.