Les Honeypots fonctionnent encore : 3 facons d'arreter les inscriptions spam sans CAPTCHAs
La semaine derniere, j'ai remarque quelque chose de suspect dans les inscriptions a ma liste d'attente SEO Friend. Des dizaines d'adresses email de domaines jetables, toutes soumises en quelques millisecondes l'une de l'autre, provenant d'adresses IP en Russie et aux Pays-Bas. Comportement classique de ferme de bots.
La solution evidente ? Coller un CAPTCHA sur le formulaire. Mais les CAPTCHAs sont ennuyeux. Ils frustrent les vrais utilisateurs, nuisent aux taux de conversion, et honnetement - les bots modernes deviennent assez bons pour les resoudre de toute facon. Je voulais quelque chose d'invisible pour les humains mais mortel pour les bots.
Entrez le honeypot.
Qu'est-ce qu'un Honeypot ?
Un honeypot est un piege concu pour attraper les systemes automatises en exploitant leur comportement previsible. En securite web, un honeypot de formulaire fonctionne sur un principe simple : les bots remplissent automatiquement chaque champ de formulaire qu'ils trouvent, tandis que les humains ne remplissent que les champs qu'ils peuvent voir.
Si vous ajoutez un champ cache que les humains ne peuvent pas voir, et que ce champ est rempli - vous avez attrape un bot.
C'est une vieille technique, mais elle fonctionne encore remarquablement bien en 2025. Combinee avec des verifications de timing, vous pouvez arreter la grande majorite des inscriptions spam sans aucune friction utilisateur.
Le probleme : Les inscriptions de fermes de bots
Avant d'implementer mon honeypot, voici a quoi je faisais face. Des IPs de centres de donnees en Russie, aux Pays-Bas et en Allemagne frappaient mon formulaire de liste d'attente. Les soumissions arrivaient a une vitesse inhumaine - 0.2 a 0.5 secondes apres le chargement de la page. Les adresses email etaient toutes de domaines jetables comme tempmail et guerrillamail. Et ils remplissaient chaque champ de formulaire qu'ils pouvaient trouver, ce qui deviendrait leur perte.
Pourquoi les bots font-ils ca ? Ils recoltent des systemes email - soumettant des formulaires pour voir si les emails de confirmation rebondissent, validant que votre pipeline email fonctionne. Ils polluent les bases de donnees avec des donnees inutiles pour gaspiller votre temps et votre quota d'emails. Ils testent des adresses email pour des attaques de credential stuffing plus tard. Parfois c'est du sabotage concurrent - gonflant vos metriques avec des donnees poubelles pour que vous ne puissiez pas faire confiance a vos chiffres.
Les bots venaient des memes blocs IP /24, soumettaient en fractions de seconde et utilisaient des emails jetables evidents. Candidats parfaits pour la detection honeypot.
Exemple 1 : Le piege a champ cache
C'est la technique honeypot classique qui arrete les bots depuis le debut des annees 2000. Le concept est magnifiquement simple : ajoutez un champ de formulaire invisible aux humains mais visible aux bots. Quand un bot remplit automatiquement chaque champ de la page (ce qu'ils font toujours), ils rempliront aussi votre champ cache - et vous les avez attrapes.
Voici le HTML que vous ajouterez a votre formulaire, juste a cote de votre vrai champ email :
<form id="waitlist-form" action="/api/signup" method="POST">
<!-- Le vrai champ que les humains remplissent -->
<label for="email">Adresse email</label>
<input type="email" id="email" name="email" required>
<!-- Le champ honeypot - completement invisible aux humains -->
<input
type="text"
name="website"
style="position:absolute;left:-9999px"
tabindex="-1"
autocomplete="off"
aria-hidden="true"
>
<button type="submit">Rejoindre la liste d'attente</button>
</form>Chaque attribut sur ce champ honeypot sert un objectif specifique. Le type="text" le fait ressembler a un champ de texte normal pour tout bot analysant votre HTML. Le name="website" est deliberement anodin - les bots sont programmes pour remplir les champs courants comme "website", "url", "company" et "phone", donc utiliser un de ces noms garantit qu'ils mordront a l'hamecon. Evitez les noms evidents comme "honeypot" ou "trap" parce que certains bots sophistiques cherchent ces patterns.
Le style="position:absolute;left:-9999px" est la cle de l'invisibilite. En positionnant l'element 9999 pixels a gauche de l'ecran, aucun humain ne le verra jamais, mais les bots analysant le DOM le trouveront sans probleme. Le tabindex="-1" empeche les utilisateurs clavier de tabber accidentellement dans le champ en naviguant votre formulaire. Le autocomplete="off" empeche le remplissage automatique du navigateur de le toucher - vous ne voulez pas que Chrome remplisse utilement votre honeypot avec les donnees sauvegardees d'un utilisateur legitime. Enfin, aria-hidden="true" dit aux lecteurs d'ecran d'ignorer completement ce champ, maintenant l'accessibilite pour les utilisateurs malvoyants.
Cote serveur, la verification est triviale. Si le champ website contient une valeur quelconque, vous avez attrape un bot :
app.post('/api/signup', async (c) => {
const { email, website } = await c.req.parseBody();
// Si le champ honeypot a une valeur, un bot l'a rempli
if (website) {
console.log('Bot detecte via honeypot:', email);
// Retourne faux succes - le bot pense que ca a marche
return c.json({
success: true,
message: 'Merci de vous etre inscrit !'
});
}
// Vrai humain - traiter normalement
await saveToDatabase(email);
return c.json({ success: true, message: 'Bienvenue !' });
});Le detail critique ici : retournez une fausse reponse de succes au lieu d'une erreur. Si vous retournez un message d'erreur, les bots sophistiques pourraient detecter qu'ils ont ete attrapes et essayer differentes approches - peut-etre sauter des champs, peut-etre utiliser une IP differente. En retournant le succes, l'operateur du bot voit des coches vertes dans ses logs et passe a des cibles plus faciles. Votre base de donnees reste propre, et ils ne savent jamais ce qui les a frappes.
Exemple 2 : La verification de timing
Les humains prennent du temps pour lire une page et remplir un formulaire. Meme le dactylographe le plus rapide a besoin de plusieurs secondes pour scanner le formulaire, cliquer dans le champ email, taper son adresse et cliquer sur soumettre. Une vraie personne ne peut tout simplement pas charger une page et soumettre un formulaire en 300 millisecondes.
Les bots peuvent. Et ils le font, constamment.
Cette technique honeypot mesure le temps entre le chargement de la page et la soumission du formulaire. Tout ce qui est sous 3 secondes est presque certainement automatise.
D'abord, ajoutez un champ cache a votre formulaire qui stockera l'horodatage de chargement de page :
<form id="waitlist-form" action="/api/signup" method="POST">
<label for="email">Adresse email</label>
<input type="email" id="email" name="email" required>
<!-- Champ de verification de timing - capture quand la page a charge -->
<input type="hidden" name="loadedAt" id="loadedAtField">
<button type="submit">Rejoindre la liste d'attente</button>
</form>
<script>
// Des que la page charge, enregistre l'horodatage
document.getElementById('loadedAtField').value = Date.now();
</script>Quand la page charge, JavaScript definit immediatement la valeur du champ cache a l'horodatage Unix actuel en millisecondes. Cette valeur est soumise avec le reste des donnees du formulaire. L'utilisateur ne la voit jamais, n'interagit jamais avec - c'est completement invisible.
Cote serveur, vous comparez le temps de soumission au temps de chargement :
app.post('/api/signup', async (c) => {
const { email, loadedAt } = await c.req.parseBody();
// Calcule combien de temps l'utilisateur a mis pour soumettre
if (loadedAt) {
const elapsed = Date.now() - parseInt(loadedAt, 10);
// Moins de 3 secondes ? Aucun humain ne tape aussi vite.
if (elapsed < 3000) {
console.log(`Bot detecte: soumis en ${elapsed}ms`);
// Retourne erreur de rate limit - legerement plus credible
return c.json({
error: 'Veuillez attendre un moment avant de soumettre.'
}, 429);
}
}
// A pris un temps raisonnable - probablement humain
await saveToDatabase(email);
return c.json({ success: true, message: 'Bienvenue !' });
});J'utilise 3 secondes (3000 millisecondes) comme seuil. En testant, j'ai trouve que meme les utilisateurs rapides avec le remplissage automatique active prennent au moins 4-5 secondes pour soumettre. Les bots les plus lents que j'ai attrapes etaient autour de 800 millisecondes. Trois secondes donne une marge confortable pour les deux.
Pourquoi retourner une erreur 429 (Too Many Requests) pour les violations de timing au lieu d'un faux succes ? Parce que les violations de timing pourraient occasionnellement attraper des cas limites - comme un utilisateur avec une connexion extremement rapide qui spam-clique soumettre. Un message "veuillez attendre" est plus convivial que de reussir silencieusement sans vraiment les inscrire. Vous pouvez aussi retourner un faux succes ici si vous preferez ; les deux approches fonctionnent.
Exemple 3 : Le systeme complet en deux parties
En pratique, vous voulez les deux techniques travaillant ensemble. Le champ cache attrape les bots qui remplissent automatiquement les formulaires. La verification de timing attrape les bots qui essaient d'etre malins et sautent les champs caches. Ensemble, ils forment une barriere presque impenetrable contre les soumissions automatisees.
Voici l'implementation complete que j'utilise sur SEO Friend :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Rejoindre la liste d'attente</title>
</head>
<body>
<form id="waitlist-form">
<div class="form-group">
<label for="email">Adresse email</label>
<input
type="email"
id="email"
name="email"
required
placeholder="vous@exemple.com"
>
</div>
<!-- HONEYPOT 1: Piege a champ cache -->
<!-- Les bots remplissent ceci ; les humains ne le voient jamais -->
<input
type="text"
name="website"
style="position:absolute;left:-9999px"
tabindex="-1"
autocomplete="off"
aria-hidden="true"
>
<!-- HONEYPOT 2: Verification de timing -->
<!-- Enregistre le temps de chargement de page pour validation de vitesse -->
<input type="hidden" name="loadedAt" id="loadedAt">
<button type="submit">Rejoindre la liste d'attente</button>
</form>
<script>
// Definir l'horodatage des que la page charge
document.getElementById('loadedAt').value = Date.now();
document.getElementById('waitlist-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const response = await fetch('/api/signup', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
alert('Merci d\'avoir rejoint la liste d\'attente !');
} else {
alert(result.error || 'Quelque chose s\'est mal passe.');
}
});
</script>
</body>
</html>Et le handler cote serveur qui lie tout ensemble :
import { Hono } from 'hono';
const app = new Hono();
app.post('/api/signup', async (c) => {
const body = await c.req.parseBody();
const email = body.email;
const website = body.website; // Champ Honeypot
const loadedAt = body.loadedAt; // Champ de timing
// Obtenir IP pour le logging (Cloudflare fournit cet header)
const ip = c.req.header('cf-connecting-ip') || 'unknown';
// VERIFICATION 1: Honeypot de champ cache
// Si ce champ a une valeur, un bot l'a rempli
if (website) {
console.log(`[BLOQUE] Honeypot declenche | IP: ${ip} | Email: ${email}`);
// Faux succes - le bot ne sait pas qu'il a echoue
return c.json({
success: true,
message: 'Merci de vous etre inscrit !'
});
}
// VERIFICATION 2: Validation de timing
// Si le formulaire a ete soumis en moins de 3 secondes, c'est automatise
if (loadedAt) {
const elapsed = Date.now() - parseInt(loadedAt, 10);
if (elapsed < 3000) {
console.log(`[BLOQUE] Trop rapide (${elapsed}ms) | IP: ${ip} | Email: ${email}`);
return c.json({
error: 'Veuillez attendre un moment avant de soumettre.'
}, 429);
}
}
// VERIFICATION 3: Validation email basique
if (!email || !email.includes('@')) {
return c.json({ error: 'Veuillez entrer un email valide.' }, 400);
}
// TOUTES LES VERIFICATIONS PASSEES - C'est un vrai humain
console.log(`[SUCCES] Nouvelle inscription | IP: ${ip} | Email: ${email}`);
// Sauvegarder dans votre base de donnees
await db.insert({
email,
ip,
signedUpAt: new Date().toISOString()
});
return c.json({
success: true,
message: 'Bienvenue dans la liste d\'attente !'
});
});
export default app;Le logging est important. En suivant les tentatives bloquees, vous pouvez voir exactement combien de bots vous attrapez et d'ou. Dans ma premiere semaine, j'ai logue plus de 200 declenchements honeypot depuis des IPs aux Pays-Bas et en Russie. Tous pensaient qu'ils s'etaient inscrits avec succes. Aucun n'a atteint ma base de donnees.
Resultats
Apres avoir implemente ce systeme honeypot en deux parties, les inscriptions spam sont passees de 20-50 par jour a essentiellement zero. Les bots essaient toujours - je les vois dans mes logs serveur - mais ils sont tous attrapes et nourris de fausses reponses de succes. Ils pensent qu'ils gagnent. Ma base de donnees reste propre.
Les vrais utilisateurs n'ont rien remarque. Il n'y a pas de CAPTCHA a resoudre, pas de friction ajoutee au flux d'inscription. Ils remplissent leur email, cliquent soumettre, et c'est fait. Les verifications honeypot se passent invisiblement en arriere-plan.
L'implementation a pris environ 30 minutes - la plupart de ce temps a ete pour configurer le logging pour que je puisse surveiller ce qui etait bloque. Il n'y a pas de maintenance continue, pas de frais mensuels pour des services anti-spam, pas de cles API a gerer.
Parfois les solutions les plus simples sont les meilleures. Les honeypots existent depuis des decennies parce qu'ils fonctionnent. A une epoque d'IA et de machine learning de plus en plus sophistiques, il y a quelque chose de satisfaisant a arreter des bots avec un champ de texte cache et un horodatage.
Questions frequentes
Les honeypots fonctionnent-ils contre tous les bots ?
Non. Les bots sophistiques specifiquement concus pour contourner les honeypots peuvent sauter les champs caches ou ajouter des delais artificiels. Cependant, ceux-ci sont rares. La grande majorite des bots sont de simples scrapers qui remplissent tout automatiquement et soumettent immediatement. Les honeypots attrapent 95%+ du spam automatise, ce qui est generalement suffisant pour rendre votre probleme de spam gerable.
Cela va-t-il casser l'accessibilite ?
Pas si c'est implemente correctement. L'attribut aria-hidden="true" dit aux lecteurs d'ecran d'ignorer completement le champ honeypot, donc les utilisateurs malvoyants ne seront pas confus par un champ avec lequel ils ne peuvent pas interagir. Le tabindex="-1" empeche les utilisateurs clavier de tabber dedans. Les utilisateurs naviguant avec des technologies d'assistance vivront le formulaire exactement comme prevu.
Que se passe-t-il si un utilisateur legitime remplit le honeypot d'une facon ou d'une autre ?
C'est presque impossible avec une implementation correcte. Le champ est positionne 9999 pixels au-dela du bord gauche de l'ecran - les utilisateurs ne peuvent litteralement pas le voir ou cliquer dedans. Le remplissage automatique du navigateur est desactive avec autocomplete="off". En deux semaines d'utilisation en production avec des milliers de visiteurs, j'ai eu zero faux positifs.
Devrais-je utiliser un CAPTCHA a la place ?
Les CAPTCHAs ajoutent de la friction. Les etudes montrent qu'ils peuvent reduire les taux de conversion de 3-8%. Les utilisateurs les detestent - je les deteste. Les honeypots sont invisibles ; les utilisateurs ne savent meme pas qu'ils existent. Commencez avec les honeypots. N'ajoutez des CAPTCHAs que si vous recevez encore du spam significatif apres avoir implemente une protection honeypot correcte, ce qui est peu probable pour la plupart des sites.
Fred
AUTHORFull-stack developer with 10+ years building production applications. I've built frontends that users actually enjoy using (and that don't break in IE).
Need a developer who gets it?
POC builds, vibe-coded fixes, and real engineering. Let's talk.
Hire Me →
