Remplacer reCAPTCHA sur WordPress : l’histoire de Gaitcha

Un chemin d empreintes de pas hésitant monte vers une grande case cochée gravée dans la roche

Tout a commencé par un message sur le Slack de la communauté WordPress francophone.

Sommaire

Arnaud Morillon posait une question que beaucoup se posent : comment remplacer reCAPTCHA sans sacrifier l’efficacité ? Ses contraintes étaient claires (accessibilité, éco-conception, RGPD) et il avait déjà creusé les alternatives : Altcha, mosparo, mcaptcha, capjs. Il cherchait des retours d’expérience.

J’ai répondu en passant que le plus simple était peut-être de coder un petit module sur mesure basé sur l’analyse comportementale. J’en utilisais déjà un sur des sites clients, et il marchait bien. La conversation a continué, d’autres ont réagi.

Et à un moment je me suis dit : « Punaise, ça me donne envie d’en faire un vrai plugin. »

C’est ce qui a donné Gaitcha. Voici ce qui s’est passé depuis, dans l’ordre. Et si tu veux cocher la case avant de lire, la démo est ici.

Gros plan sur le widget Gaitcha coché, Je suis humain, sur un écran vu de biais

Le tour des alternatives à reCAPTCHA, vite fait

Le reproche qu’on fait à reCAPTCHA est connu : chaque visiteur qui remplit un formulaire envoie ses interactions à Google.

Sur le plan légal, le sujet a bougé (je ne suis pas juriste, je te donne ce que j’ai lu). La CNIL explique dans sa FAQ sur les traceurs qu’un captcha qui sert uniquement à sécuriser le site se passe de consentement, mais qu’il en faut un quand le fournisseur réutilise les données pour son propre compte. Elle cite reCAPTCHA, et elle fait remarquer qu’un captcha qu’on peut refuser ne protège plus grand-chose. Depuis le 2 avril 2026, Google se positionne comme sous-traitant sur reCAPTCHA. Ca règle une partie du problème, mais ça veut aussi dire que le responsable du traitement c’est toi (ou ton client), et que les données partent toujours chez Google.

Moi je préfère que ces données ne sortent pas du site. J’ai à coeur de ne pas tracer les gens : respecter le consentement c’est bien, ne pas en avoir besoin c’est mieux. Un captcha qui n’envoie rien à personne, ça me motivait d’autant plus. Sauf que les alternatives ont toutes un truc qui me gêne.

Les services tiers (hCaptcha, Cloudflare Turnstile) changent de fournisseur, pas d’architecture : on dépend toujours d’une infrastructure externe qui peut changer de modèle. Altcha en est un bon exemple. Le coeur du projet reste open source et auto-hébergeable, mais l’extension WordPress a changé de modèle avec sa v2 : licence propriétaire, un plan gratuit qui couvre le login, l’inscription et les commentaires, et les connecteurs pour les plugins de formulaires dans l’offre payante. Ca a surpris pas mal de monde dans la communauté.

Mosparo est open source et auto-hébergé, mais il demande d’installer et de maintenir une instance à part. Pour protéger un formulaire de contact, je trouve ça lourd. Le honeypot (un champ caché que les bots remplissent sans le savoir) ne résiste qu’aux bots les plus naïfs : n’importe quel script qui lit le HTML avant de soumettre passe à travers.

Restait la preuve de travail (Altcha, mcaptcha, capjs). On demande au navigateur de résoudre un calcul, sans rien demander à l’utilisateur. L’idée est séduisante, mais un calcul reste un calcul : un navigateur piloté par Playwright ou Puppeteer le résout comme n’importe quel visiteur, et un script peut même le faire sans navigateur. La preuve de travail fait payer chaque envoi, elle ne dit pas si c’est un humain qui est derrière. Seule, elle ne me suffisait pas (j’y reviens plus bas, parce que j’ai fini par en mettre une dans Gaitcha).

Jouer au bot pour apprendre à le reconnaître

L’idée derrière Gaitcha, c’est d’observer comment le visiteur arrive jusqu’à la case et comment il la coche : ses mouvements de souris, son timing au clavier, ses gestes sur mobile. Une main qui vise une case dépasse un peu sa cible, corrige, décélère en approchant. Il y a de l’inertie et des petites hésitations.

Pour affiner le scoring, j’ai passé pas mal de temps à jouer au bot, avec Playwright piloté par une IA locale. Et là j’ai eu une surprise. J’imaginais que les bots cliquaient toujours pile au centre du bouton, ou en haut à gauche, de façon mécanique. En fait non, les bots malins savent déjà s’adapter. Ils ajoutent du bruit dans leurs trajectoires, ils simulent de l’accélération, ils placent leurs clics de façon variable. C’est plus fin que ce à quoi je m’attendais.

Du coup le scoring ne cherche pas des trajectoires « trop propres ». Il regarde les changements de direction, les variations de vitesse, la décélération à l’arrivée, et il cherche ce qui ne colle pas avec un vrai geste physique.

Deux trajectoires de pointeur vers la case à cocher : une main au trackpad et un script Playwright qui simule du bruit
Deux vrais parcours vers la case, un point par position reçue par le navigateur. À gauche ma main au trackpad, avec un accroc et une correction. À droite un script Playwright écrit pour cette illustration, qui simule le bruit et le ralentissement : l’écart est plus fin qu’on ne l’imagine.

Même logique au clavier, quand on fait Tab pour atteindre la case puis Espace pour la cocher. La littérature sur la dynamique de frappe situe la durée d’un appui entre 60 et 130 ms en général, avec des variations d’une touche à l’autre. Dans mes tests, le keyboard.press() de Playwright enfonce et relâche la touche en 1 à 5 ms. Gaitcha considère qu’en dessous de 20 ms de moyenne, personne n’a appuyé sur rien. Il y a aussi les chevauchements : quelqu’un qui tabule vite enfonce souvent la touche suivante avant d’avoir relâché la précédente, alors qu’un script exécute ses frappes l’une après l’autre. Là aussi certains bots savent simuler un timing touche après touche, donc le score regarde plusieurs choses à la fois (durées, intervalles, régularité, corrections).

La partie qui m’a le plus amusé, ce sont les traces que laisse un navigateur piloté à distance dans les événements eux-mêmes. Deux exemples vus pendant mes tests :

  • dans un vrai navigateur, la position du clic à l’écran et sa position dans la page sont décalées (il y a la fenêtre, la barre d’onglets). Avec Playwright, les deux valeurs étaient identiques
  • une vraie souris (pas un trackpad) remonte plusieurs positions entre deux images, et le navigateur les regroupe dans un seul événement. Un événement injecté par script n’a jamais rien à regrouper

Ces signaux pèsent peu dans le score et ils ne prouvent rien à eux seuls (Safari ne donne pas le second, par exemple). Ils complètent le reste.

Il faut quand même remettre tout ça à sa place : aujourd’hui la majorité du spam de formulaire vient encore de bots HTTP, qui postent directement sans charger la page. Ceux-là ne passent pas, ils n’ont ni jeton, ni comportement à montrer. Mais je vois de plus en plus de bots capables de piloter un vrai navigateur, et c’est pour eux que l’analyse du comportement existe.

Juin : la preuve de travail que je ne voulais pas

Je disais plus haut que la preuve de travail seule ne me suffisait pas. J’en ai quand même ajouté une en juin, en première étape, et pour une raison précise : avant ça, un script pouvait demander des jetons à la chaîne pour préparer ses envois.

Maintenant le navigateur doit résoudre un calcul avant d’obtenir son jeton. Pour un visiteur ça se passe en arrière-plan, la durée dépend de son appareil. Pour quelqu’un qui veut envoyer des milliers de formulaires, c’est un coût à payer à chaque fois.

Les deux couches font des boulots différents : le calcul rend le volume cher, le comportement regarde qui coche la case.

Côté visiteur

Il y a une case à cocher et c’est tout.

Un emplacement grisé réserve la place du widget dès le chargement (donc pas de saut de mise en page). A la première interaction avec la page, le calcul se lance, puis la case devient cliquable. Le visiteur la coche, et son geste est analysé à l’envoi du formulaire. Pas de puzzle, pas d’image floue, pas de « sélectionne tous les feux tricolores ».

Et l’accessibilité ? L’European Accessibility Act est applicable depuis le 28 juin 2025 aux produits et services concernés, et les captchas basés sur la vision ou l’audio posent de vraies questions d’exclusion. Gaitcha a trois profils de scoring : souris, clavier et tactile. Quelqu’un qui navigue uniquement au clavier n’est pas pénalisé par l’absence de données souris, le score bascule sur les signaux clavier. Je n’annonce aucune certification : il me manque des retours de personnes qui utilisent des technologies d’assistance au quotidien. Si c’est ton cas ou celui de tes utilisateurs, ça m’intéresse vraiment.

Septembre : 7 700 mails dans la nuit

Le plugin tourne maintenant sur des sites que je ne gère pas, et c’est là que c’est devenu intéressant.

Début septembre, un membre de la communauté s’est réveillé avec plus de 7 700 mails reçus dans la nuit via un formulaire Elementor, jusqu’à plusieurs dizaines d’envois par minute. Il avait un honeypot et une liste de mots interdits. Il a installé Gaitcha pendant la vague, et les envois ont cessé d’arriver alors que le flot continuait derrière. D’autres ont remplacé leur honeypot depuis plusieurs semaines et n’ont plus eu de problème pour l’instant.

Je garde les pieds sur terre : une vague à ce rythme c’est un bot HTTP, le cas le plus facile. Ca ne dit rien d’un bot écrit exprès pour Gaitcha.

Les retours m’ont surtout fait corriger des choses. En juillet, on m’a remonté que le champ s’appelait « Sans titre » dans l’éditeur de Gravity Forms, sans moyen de le renommer ni de le masquer. C’était corrigé dans une release le soir même. Le même testeur pensait que le formulaire partait sans que la case soit cochée : il était connecté en admin, et les administrateurs sont dispensés de vérification par défaut (teste ton formulaire déconnecté, on tombe tous dedans). Avant ça, Ninja Forms m’avait donné du fil à retordre avec un formulaire impossible à renvoyer après un premier essai sans coche.

Ce que Gaitcha ne fait pas

Il ne remplace pas le rate limiting. Le journal des interactions vient du navigateur, donc un script écrit exprès pour Gaitcha peut le fabriquer et passer. Le captcha rend le spam automatisé plus cher, il ne l’empêche pas à lui seul. Garde la limitation de débit et la validation de tes champs.

Il ne fait rien contre le spam posté à la main. Quelqu’un qui vient remplir ton formulaire pour un centime coche la case comme tout le monde. Ca se traite au niveau du formulaire et du contenu.

Le scoring est volontairement permissif. Ma grosse angoisse en déployant, c’était les faux positifs (bloquer de vrais utilisateurs). Si la vérification échoue, le visiteur peut recocher et retenter. Le seuil par défaut est à 0.5 et tu peux le durcir avec un filtre.

C’est un plugin jeune. Il n’y a pas de benchmark indépendant, et le recul à grande échelle reste à construire.

La fiche, pour l’installer

Ce que c’est

Deux dépôts, gratuits et open source (GPL-2.0-or-later) :

(Gait, en anglais, c’est la démarche, la façon dont quelqu’un se déplace. Vu qu’on analyse comment l’utilisateur « se déplace » pour cocher la case, c’est tout à propos. Et ça sonne un peu comme gotcha. C’est pas tout à fait un hasard.)

Comment ça marche

Pas de compte, pas de clé d’API, pas de service captcha à appeler :

  1. A la première interaction, le navigateur demande un challenge à ton WordPress et résout la preuve de travail
  2. Le serveur lui délivre un jeton signé (HMAC), avec un nom de champ aléatoire
  3. Quand la case est cochée, le journal des interactions est figé
  4. A l’envoi, PHP vérifie la signature et l’expiration du jeton, puis calcule le score

Les interactions vont du navigateur du visiteur à ton serveur, et elles s’arrêtent là. Un jeton vit 120 secondes (il est renouvelé tout seul) et ne peut servir qu’une fois : le plugin garde pour ça un état temporaire dans les options WordPress, c’est son seul stockage, et tu peux le remplacer par ton propre système si le site le demande. Le détail est sur la page Fonctionnement du site.

Les formulaires couverts

Les plugins de formulaires : Gravity Forms, WS Form Pro, Contact Form 7, Formidable Forms, WPForms, Fluent Forms, Ninja Forms et Elementor Pro Forms. Dans les builders, c’est un champ Gaitcha à ajouter au formulaire. Dans Contact Form 7, c’est le tag [gaitcha]. Chaque connecteur se charge uniquement si le plugin correspondant est actif.

Les formulaires natifs de WordPress aussi : connexion, inscription, mot de passe perdu et commentaires. Ils sont désactivés par défaut (pour ne pas faire doublon si le site a déjà autre chose). Sur la connexion ça ajoute une vérification devant les tentatives automatisées, mais ça ne remplace pas une vraie protection des comptes.

Pour écrire ces connecteurs, j’ai dû tester chacun de ces plugins en profondeur. Ils ne se valent pas tous, et certains m’ont réservé des surprises (un prochain article sera sur WS Form).

La case Gaitcha cochée, Je suis humain, dans le formulaire de contact de wabeo.fr, au-dessus du bouton Envoyer le message
Le widget en style minimal, dans le formulaire de contact de ce site (connecteur Formidable).

Installation

  1. Tu télécharges le ZIP gaitcha-for-wp depuis la dernière release GitHub et tu l’envoies dans WordPress. Attention à prendre le ZIP de la release et pas l’archive « Source code » : elle ne contient pas les dépendances
  2. Le plugin génère sa clé à l’activation
  3. Dans les réglages tu choisis les formulaires natifs à protéger, le thème (clair, sombre ou auto) et le style du widget (classique ou minimal, pour les sites où la case doit se faire discrète)
  4. Tu testes ton formulaire déconnecté (les admins sont dispensés de vérification)

Gaitcha n’est pas sur le dépôt officiel de WordPress. Les mises à jour s’affichent quand même dans le tableau de bord comme pour n’importe quel plugin, elles viennent des releases GitHub (c’est le seul appel externe du plugin, côté admin). Le guide WordPress détaille tout ça, avec les filtres pour régler le seuil du score ou la difficulté du calcul.

Tester Gaitcha

Si tu veux garder la vérification de tes formulaires sur ton serveur, tu peux l’essayer en cinq minutes :

S’il te manque un connecteur, dis-le moi et je l’ajouterai. Et si tu tombes sur un cas tordu (un faux positif, un formulaire AJAX qui coince, un lecteur d’écran qui ne s’en sort pas), ouvre une issue : c’est ce qui a fait avancer le plugin jusqu’ici.

Soyez le premier à commenter !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *