Il y a deux façons de rater la protection d'un réseau de chat. La première est évidente : on se fait déborder, les salons se remplissent de publicités et les habitués partent. La seconde l'est beaucoup moins : on serre tellement les boulons que plus personne n'entre, et les habitués partent quand même. Le résultat est le même, et la seconde erreur est plus difficile à voir, parce qu'elle ne produit aucun incident — juste une courbe de fréquentation qui descend sans qu'on sache pourquoi.
J'ai monté mon premier réseau IRC à quatorze ans et je n'ai jamais complètement arrêté. Ce que je décris ici, ce sont les mécanismes que j'ai fini par garder, et surtout le raisonnement qu'il y a derrière — parce que les mécanismes vieillissent et que le raisonnement, lui, tient. C'est ce que je constatais l'autre jour en relisant vingt ans de mes propres pannes, et la protection d'un réseau en est sans doute le meilleur exemple.
Le coût se paie toujours quelque part
Un réseau de chat public n'est pas une banque. L'objectif n'est pas zéro intrusion, c'est un salon vivable ce soir. Ça change tout, parce que ça transforme chaque protection en arbitrage plutôt qu'en obligation.
Toute protection a un prix, et ce prix est payé par quelqu'un qui n'a rien fait : une personne qui attend trente secondes de plus, une autre qui doit créer un compte pour dire bonjour, une troisième qui est bloquée parce qu'elle partage son opérateur mobile avec dix mille abonnés. On ne peut pas supprimer ce coût. On peut seulement décider où on le met.
D'où la hiérarchie que je suis, et qui structure le reste de cet article : d'abord ce qui protège tout le réseau sans que personne ne le remarque, ensuite seulement ce qui se voit. Quand j'en arrive à poser des protections dans un salon, c'est que la couche d'avant a laissé passer. Ce sont des pansements, pas une architecture.
Les bases, et ce qui traîne toujours
Trois choses, qui ne sont pas discutables, et que je fais quand même mal de temps en temps.
Le transport chiffré. Un serveur IRC moderne écoute en TLS sur 6697, et il n'y a plus aucune raison d'accepter du 6667 en clair. En relisant mes propres dépôts pour écrire cet article, j'ai constaté que deux de mes fichiers d'exemple sont encore en 6667. Ce n'est pas une prise de position, c'est un reste — le durcissement échoue rarement sur les décisions, il échoue sur ce qui traîne.
(Autant l'avouer tout de suite : à quatorze ans, je ne chiffrais rien. Personne ne chiffrait. Un serveur IRC en clair sur 6667, un site en HTTP, des mots de passe qui traversaient le réseau à découvert — c'était la norme, et ça ne choquait personne, moi le premier. Ce qui m'a fait changer n'est pas une illumination, c'est Google : le jour où le HTTPS est devenu un critère de classement, puis où le navigateur s'est mis à afficher « non sécurisé » en toutes lettres à côté de l'adresse. Le certificat gratuit en trois commandes a fait le reste. J'aimerais raconter que j'ai compris avant qu'on m'y pousse — ce serait faux, et ça vaut pour à peu près tout le monde de ma génération.)
Les secrets. La vraie règle n'est pas « chiffrer les mots de passe », elle est plus bête : séparer ce qui se publie de ce qui ne se publie pas. Un mot de passe d'opérateur au milieu d'un fichier de config qui part un jour sur un dépôt, c'est l'accident classique. La parade tient en deux fichiers — la configuration publique d'un côté, les secrets de l'autre, fusionnés au démarrage, le second exclu du versionnement. C'est ce que fait alphaBaboon ↗ avec son config_secret.json. Ce n'est pas élégant, c'est efficace, et ça survit à l'étourderie.
Les relais anonymisants. Proxies ouverts, SOCKS, VPN grand public, serveurs loués à l'heure, Tor : ce n'est pas illégitime en soi, beaucoup de gens en utilisent par habitude, mais c'est le canal de l'immense majorité des nuisances automatisées. Les services d'identification d'IP font ça très bien — et ils sont facturés à la requête. D'où un principe qui vaut bien au-delà d'IRC : l'appel payant est le dernier maillon, pas le premier. Dans IRCBook ↗, l'API externe n'est interrogée que si les deux filtres gratuits d'avant n'ont pas tranché.
Tor est le cas facile, et il mérite qu'on s'y arrête une seconde. La liste des nœuds de sortie est publiée par le projet lui-même, en clair et gratuitement : il n'y a aucune API à interroger, aucune heuristique à écrire, il suffit de la récupérer régulièrement et de comparer. C'est le filtre le moins cher de tout l'arsenal, et c'est le seul dont le résultat soit certain plutôt que probable — une adresse est un nœud de sortie ou elle ne l'est pas.
Le reste est un arbitrage, et il est moins confortable. Tor sert à des gens qui en ont de très bonnes raisons, et le bloquer, c'est les exclure sans distinction. Sur un chat grand public francophone, j'ai tranché dans ce sens : les rares personnes qui arrivaient par Tor n'étaient à peu près jamais là pour discuter. Je ne présente pas ça comme un principe — c'est une décision prise sur ce que j'observais, et elle n'a pas de raison d'être la même sur un service où l'anonymat de l'utilisateur fait partie de la promesse.
La ruse de la porte d'entrée
C'est le mécanisme dont je suis le plus content, et c'est aussi le plus simple.
Le robot de supervision ne se connecte pas comme un utilisateur : il se déclare auprès de l'IRCd comme un serveur lié. Conséquence immédiate — il voit chaque connexion arriver, avec son adresse réelle, avant même que la personne ait rejoint quoi que ce soit. On passe de « sanctionner après » à « décider avant ».
(Celui-là, IRCBook, a commencé en 2008 et je l'ai tenu jusqu'en 2025. Dix-sept ans de production, et ça se voit : du Java 8, un projet Ant, un connecteur MySQL de 2010 traînant ses vulnérabilités connues, pas une ligne de TLS nulle part, et une injection SQL que je n'ai découverte qu'en relisant le code pour le republier. C'est le vrai visage de la maintenance longue — on ne réécrit pas, on greffe : la dernière chose que j'y ai ajoutée, en 2024, c'est un appel à une API de modération par IA, dans un fichier écrit seize ans plus tôt. Un logiciel qui survit aussi longtemps finit toujours par coûter plus cher à maintenir qu'il n'a coûté à écrire — et c'est précisément parce qu'il a tenu si longtemps qu'il vaut la peine d'être raconté.)
Reste la question à poser à cette adresse. Le réflexe est : est-elle mauvaise ? Et c'est une mauvaise question, parce qu'y répondre demande une liste noire, c'est-à-dire une course perdue d'avance contre quelqu'un qui change d'adresse plus vite que vous ne l'inscrivez.
La question que je pose est l'inverse :
SELECT COUNT(*) FROM antibot_tracking WHERE ip_address = ?
Le site web du chat inscrit l'adresse de chaque visiteur qui ouvre la page. Le robot ne demande donc pas si l'adresse est mauvaise, il demande si elle est passée par la porte d'entrée. Une connexion qui arrive sans jamais avoir chargé la page n'est pas venue par le chemin des humains.
Pourquoi ça marche aussi bien ? Pas par génie technique — c'est dix lignes. Ça marche parce que c'est une liste blanche, et qu'une liste blanche est bornée par ce que vous contrôlez au lieu d'être bornée par l'imagination de l'adversaire.
Mais il faut la voir pour ce qu'elle est : une première marche, qu'on affine ensuite. La question n'est jamais « ce filtre est-il infranchissable », elle est « combien coûte le ticket d'entrée ».
Dans sa version naïve, l'inscription se fait côté serveur, à l'affichage de la page. C'est déjà décisif contre quelqu'un qui attaque l'IRCd en direct sans jamais toucher au site — et ça ne vaut rien contre qui a compris : un curl sur l'adresse suffit à se faire inscrire.
On monte donc d'un cran : l'inscription passe en JavaScript, exécutée dans le navigateur. Un robot qui ne déroule pas le JS ne lit jamais l'appel, donc ne s'inscrit jamais, donc n'existe pas pour le serveur. Et d'un cran encore : on ne déclenche plus l'inscription au chargement de la page, mais au clic sur le bouton « Se connecter ». Il ne suffit plus d'être passé devant la porte, il faut avoir tendu la main vers la poignée. Au passage, ça resserre la fenêtre entre l'inscription et la connexion — une ligne dans la table cesse d'être le souvenir d'un visiteur d'hier pour devenir quelqu'un qui est en train d'entrer.
Chaque marche a son prix, et autant le nommer : à partir de la deuxième, qui navigue sans JavaScript ne peut plus se connecter du tout. Sur un chat qui tourne déjà entièrement dans le navigateur, ce prix est nul, le JS y est indispensable de toute façon. Sur un service où il ne le serait pas, la marche ne se monte pas.
Et ça marche un peu, aussi, parce que personne ne fait ça : celui qui se fait bloquer ne comprend pas ce qui l'a trahi, donc il n'adapte rien. Je l'assume comme ce que c'est — de l'obscurité. J'écrivais déjà exactement ça en 2012, à propos de deux modules qui masquent la liste des serveurs et des modules chargés : ça barre la route aux bidouilleurs, et « l'utilité est nulle si vous avez affaire à des personnes compétentes ». L'obscurité n'est pas de la sécurité. Mais c'est un filtre bon marché contre le nuisible moyen, qui est l'essentiel du volume.
Le prix, parce qu'il y en a un, et il est brutal : le système échoue en mode fermé. Base de données injoignable, tout le monde est banni, silencieusement. C'est un choix, pas un accident — pour un chat grand public, mieux vaut fermer une minute que laisser entrer une vague de robots — mais c'est le premier réflexe à avoir devant une vague de bannissements inexplicables. Les contrôles web, eux, échouent dans l'autre sens : API injoignable, tout passe. Savoir dans quel sens chacune de vos protections casse vaut mieux que de savoir comment elle fonctionne.
Et comme ce filtre est violent, il lui faut un amortisseur. Avant de sanctionner, le robot fait une résolution inverse de l'adresse et cherche dix-neuf marqueurs d'opérateurs grand public francophones — proxad, orange, voo, proximus, scarlet, edpnet, bbox… Une adresse résidentielle reconnue échappe aux contrôles suivants. C'est grossier, ça se contourne, et ça évite l'écrasante majorité des faux positifs pour trois lignes de code.
Mesurer la normale, pas l'anormale
Contre les vagues de connexions, j'ai longtemps cherché à décrire l'attaque. C'est peine perdue : il y en a trop de formes, et la prochaine ne ressemblera pas à la précédente.
Ce qu'on sait décrire, en revanche, c'est une soirée normale. On compte les connexions, on en garde la moyenne glissante, et on ne surveille pas la valeur mais sa dérivée. Un mardi soir monte doucement. Une vague, non. Dès que la pente décroche, on ferme la porte aux arrivants — juste aux arrivants : les gens déjà connectés ne voient rien passer. C'est ce qui rend la mesure acceptable, et c'est la raison pour laquelle je la préfère à tout le reste.
Deux nuances qui font la différence entre un compteur et un outil.
Les exceptions par opérateur. Une pointe de connexions depuis un seul gros fournisseur d'accès, c'est souvent un incident réseau chez lui, pas une attaque chez moi. Sans exemption, on ferme la porte à un opérateur entier au moment précis où ses abonnés se reconnectent tous en même temps. Avec, on garde le mécanisme et on perd le faux positif le plus coûteux.
Le doute n'est pas binaire. Entre « je laisse passer » et « je ferme », il y a « je demande un effort » — un formulaire, un captcha. L'idée n'est pas de vérifier que la personne est humaine, c'est de rendre la tentative chère. Avec une limite que j'ai fini par admettre : un captcha coûte cher à l'utilisateur légitime, et de moins en moins cher à un attaquant équipé. Selon l'époque et le type de vague, il m'arrive de sauter l'étape et de fermer sèchement. Ce n'est pas plus dur, c'est juste plus honnête sur ce que le captcha achète vraiment.
On en retrouve une version bien plus fruste dans sentinel-irc ↗, qui compte les pseudos distincts par adresse sur dix minutes. Fruste à dessein : les seuils y sont des constantes, le masque posé couvre l'adresse entière, et trois personnes derrière le même partage d'adresses mobile suffisent à déclencher vingt-quatre heures de bannissement. C'est un défaut que je connais et que je n'ai pas corrigé — autant l'écrire que laisser croire que tout est réglé.
Les mots, et les gens qu'ils blessent par erreur
Le filtrage de contenu est la partie où l'on fait le plus de dégâts, et de loin.
Sur UnrealIRCd, le spamfilter.conf fait le gros du travail, avec un usage détourné que j'aime bien : il ne sert pas seulement à bloquer, il sert à router. Un motif qui capte les messages privés les renvoie vers le robot, qui peut alors analyser ce qui se passe hors des salons publics — là où se déroulent en réalité la plupart des arnaques.
Voici ce que donne une relecture honnête de mes propres filtres, sur un mois :
| Ce que j'ai trouvé | Pourquoi |
|---|---|
| Un motif « WhatsApp » qui laissait passer huit tentatives d'affilée | Il ne couvrait qu'une graphie sur dix — watsap, what's app, whatss app… |
| Les arnaques aux recharges prépayées reçues en privé | Le filtre couvrait les salons, pas les messages privés |
| Un lien concurrent passé trente-trois fois | Le motif existait, et était contourné par une variante triviale |
| Un bannissement de quinze minutes sur les relais anonymisants | Les robots revenaient avant même la fin |
Et dans l'autre sens, la liste qui fait mal :
| Faux positif | Cause |
|---|---|
| « blanche » + « vends » lu comme un trafic de drogue | Deux mots courants, aucun contexte |
| « pcs » comme marqueur de paiement | Bloque aussi « 100 pcs », « pcs de rechange » |
| « tête de mes chats » | Un motif de violence pris au premier degré |
| « tu es d'où ? » | La question la plus fréquente d'un chat, signalée comme collecte d'informations |
| « trankille » | Une sous-chaîne d'insulte au milieu d'un mot inoffensif |
Ces deux tableaux disent la même chose sous deux angles, et c'est le cœur de cette section : un filtre ne se juge pas sur ce qu'il attrape, il se juge sur ce qu'il casse. Le motif WhatsApp inefficace et le motif « pcs » trop large sont la même erreur — un motif écrit une fois, jamais relu, et surtout jamais compté. Un filtre dont on ne journalise pas les déclenchements devient invisible dans les deux sens : celui qui ne matche plus rien ne manque à personne, celui qui matche tout rassure. J'ai écrit ailleurs qu'un indicateur qui répond toujours la même chose est pire qu'un indicateur absent ; c'est exactement le même piège.
La sortie par le haut, c'est de cesser de juger un message et de commencer à juger un contexte. Un mot de drogue vaut peu. Un mot de drogue plus un verbe commercial plus une quantité plus une ville, c'est autre chose. Un score additif fait ce travail sans jamais bloquer sur un mot isolé, et c'est ce qui divise les faux positifs.
Depuis deux ans j'y ai ajouté une couche d'analyse automatique — l'API de modération d'OpenAI, gratuite, sur des lots de huit messages gardés en mémoire et jetés après analyse. Deux choses valent la peine d'être dites, parce qu'elles ne sont pas celles qu'on attend.
D'abord, elle sous-estime le français. L'argot, les euphémismes, les contournements typographiques passent sous ses seuils. Je ne l'ai donc pas mise à la place des expressions régulières, je prends le maximum des deux scores. La machine n'a pas remplacé la liste de mots, elle l'a doublée.
Et surtout, j'ai désactivé deux de ses catégories en production — harcèlement et violence — parce qu'elles produisaient trop de faux positifs sur un chat francophone. Elles sont toujours calculées et journalisées, elles ne déclenchent simplement plus rien. Une IA de modération n'est pas un juge, c'est un capteur de plus, avec sa plage d'erreur et ses angles morts, et on a le droit de débrancher un capteur.
Ceux qui reviennent
Un bannissement ne règle rien, il déclenche une deuxième partie. J'ai décrit ailleurs comment on reconnaît quelqu'un qui revient ; côté automatisation, trois idées ont survécu.
La durée d'un bannissement est un calcul, pas une punition. Quinze minutes, c'est une invitation à revenir : le robot patiente et recommence. La bonne durée est celle qui rend l'attente plus coûteuse que le changement de méthode — deux heures pour un contournement, vingt-quatre pour une vague. Une sanction qu'on choisit « proportionnée au geste » se trompe de problème.
Ne jamais faire confiance à son propre cache pour une décision qui touche quelqu'un. GlineBotApp ↗ détecte les gens qui reviennent via un relais : leur adresse de façade est neuve, mais leur adresse réelle apparaît dans les messages de service. Avant de sanctionner, il ne se fie pas à sa liste interne — il redemande au serveur l'état réel des bannissements en cours, et n'agit que si la confirmation revient. Sans ça, une entrée périmée dans un cache suffit à bannir quelqu'un pour une infraction qui n'existe plus.
Toute détection automatique déclenchable par un tiers est une arme contre vos utilisateurs. Celle-là m'a coûté du temps avant de la voir. J'avais une détection « quatre adresses différentes pour un même pseudo en moins de quatre-vingt-dix secondes » — signature de quelqu'un qui teste ses relais. Bannir sur ce critère, ce serait viser la quatrième adresse, donc la dernière arrivée. Or n'importe qui peut se connecter trois fois sous votre pseudo depuis des adresses jetables et attendre que vous vous connectiez pour être la quatrième. La détection reste, elle alerte, elle ne bannit pas. C'est le seul endroit où je note la règle dans le code lui-même, parce que quelqu'un — moi compris — aurait fini par « corriger » ce qui ressemblait à un oubli.
Et un constat qui vaut mieux que toutes les astuces réseau : changer d'adresse est facile, changer d'appareil l'est beaucoup moins. C'est pour ça que je corrèle sur l'empreinte du client plutôt que sur l'IP.
Le salon, c'est le dernier recours
Reste tout ce qui se règle au niveau d'un salon : un Eggdrop qui fait de l'anti-clone et de l'anti-flood léger, un mode +l qui ajuste automatiquement la limite de participants pour absorber les arrivées massives, un mode +R qui réserve l'entrée aux pseudos enregistrés.
Ça marche. Mais je veux être clair sur le statut de ces outils : ce sont des solutions de secours. Quand j'en pose une, c'est que la protection globale a laissé passer, et je limite la gêne au seul salon touché plutôt qu'au réseau entier. Le +R en particulier coûte cher : il demande à un nouveau venu de s'enregistrer avant de dire bonjour, et une bonne partie ne reviendra pas. Je l'active pendant une attaque et je pense à le retirer après — le « penser à retirer après » étant, dans les faits, la partie difficile.
L'interrupteur
Une phrase résume ce que vingt ans m'ont appris sur l'automatisation : toute défense automatique finit par se tromper. Pas si, quand. La question utile n'est donc pas « est-ce que mon filtre est juste », elle est « combien de temps me faudra-t-il pour le couper ».
D'où la dernière pièce, et la moins spectaculaire : chaque contrôle se désactive à chaud depuis un salon d'opérateurs, sans redémarrage, et les critères de bannissement se modifient en base et sont relus à chaque message. .toggle, .status, et c'est tout.
Ce n'est pas du confort. C'est ce qui rend les protections précédentes acceptables : un durcissement qu'on ne peut pas retirer en dix secondes est un durcissement qu'on n'osera jamais poser assez fort. L'interrupteur n'affaiblit pas la défense, il l'autorise.
Le corollaire vaut pour les journaux. On enregistre, et autant l'assumer — mais alors il faut savoir quoi, combien de temps, et qui peut relire. Sur le tampon de modération, la réponse est simple : rien n'est conservé, le contenu vit en mémoire le temps de l'analyse, il est jeté ensuite, il n'est jamais réécrit sur un salon ni stocké en base. Une adresse IP et un journal de connexion sont des données personnelles ; les garder demande une raison, une durée et une information claire aux utilisateurs. Et il y a une frontière que je ne franchis pas : je cherche à savoir si quelqu'un d'exclu est revenu sur mon réseau, pas qui il est dans la vie. La seconde question ne m'appartient pas.
Ce que je retiens
- Le coût d'une protection est payé par des innocents. On ne le supprime pas, on choisit où on le met — et le bon endroit est toujours le plus haut possible, là où personne ne le voit.
- Une liste blanche bat une liste noire, parce qu'elle est bornée par ce que vous contrôlez. « Cette adresse est-elle passée par la porte d'entrée ? » est une meilleure question que « cette adresse est-elle mauvaise ? ».
- Sachez dans quel sens chaque protection casse. Mon filtre d'entrée ferme tout quand la base tombe ; les contrôles web laissent tout passer quand l'API tombe. Les deux sont défendables, à condition de l'avoir décidé.
- On ne sait pas décrire une attaque, on sait décrire une soirée normale. Surveillez la dérivée, pas le seuil.
- Un filtre se juge sur ce qu'il casse, et un filtre dont on ne compte pas les déclenchements ne se juge pas du tout. Le motif trop large et le motif mort sont la même négligence.
- Toute défense automatique finit par se tromper, donc l'interrupteur fait partie de la défense, pas du confort.
Le code est public : IRCBook ↗ pour le robot lié au serveur et sa liste blanche, GlineBotApp ↗ pour les contournements, sentinel-irc ↗ pour la journalisation et la corrélation, alphaBaboon ↗ pour le contenu — nommé d'après le chat dont je réglais déjà les ennuis de webcam en 2012. Le premier est le vieux Java évoqué plus haut, republié tel quel ; les trois autres sont récents et écrits en Python, un langage que je n'écris pas de tête. L'intérêt n'est de toute façon pas le code — il est dans les six lignes ci-dessus, et celles-là n'ont pas bougé depuis mes quatorze ans.