# Sécurité anti-abus — La Voix du Peuple > Document technique décrivant les protections contre les attaques sybil, les floods de bots et les brigading coordonnés. Mis à jour : mai 2026. --- ## Contexte et risque La plateforme est conçue sans authentification (choix philosophique préservant l'anonymat). Cette absence rend triviale, sans protection, la manipulation des données par : - **Sybil attacks** : multiplication des soumissions depuis la même entité - **Bot floods** : soumissions automatisées en masse - **Brigading coordonné** : campagnes organisées pour inonder la synthèse de contenus orientés Les protections suivantes traitent ce risque sans remettre en cause l'anonymat des contributeurs légitimes. --- ## Couches de protection (ordre d'application) ### 1. Honeypot anti-bot **Principe** : un champ de formulaire est présent dans le HTML mais rendu invisible aux utilisateurs réels (`display: none`, `position: absolute`, `aria-hidden="true"`). Les bots qui analysent le DOM et remplissent tous les champs déclenchent le honeypot. **Comportement** : - **Client** : si le champ `_hp` a une valeur lors de la soumission, l'appel API n'est pas effectué. Réponse simulée silencieuse côté JS. - **Serveur** : si `_hp` est présent et non vide dans le corps JSON, le serveur retourne un `201` factice sans enregistrer quoi que ce soit (`logger.info("Honeypot déclenché")`). **Fichiers** : `artifacts/voix-du-peuple/src/pages/home.tsx` · `artifacts/flask-api/app.py` --- ### 2. hCaptcha (stub — activer en production) **Principe** : widget CAPTCHA humain présenté avant la soumission. hCaptcha est choisi pour : - Gratuit (tier communautaire) - RGPD-compliant (pas de cookies tiers, données UE) - Pas de dépendance à Google **État actuel** : stub intégré, désactivé par défaut. **Pour activer** : 1. Créer un compte sur [hcaptcha.com](https://www.hcaptcha.com/) 2. Créer un site, récupérer la clé de site et la clé secrète 3. Configurer les variables d'environnement : ```env # Frontend (.env dans artifacts/voix-du-peuple/) VITE_HCAPTCHA_SITE_KEY=votre-cle-de-site # Backend (.env ou variable système) HCAPTCHA_SECRET_KEY=votre-cle-secrete ``` 4. Reconstruire le frontend : `pnpm build` **Comportement quand activé** : - Le widget hCaptcha s'affiche dans le formulaire avant le bouton "Contribuer" - Le bouton est désactivé tant que le CAPTCHA n'est pas validé - Le token est transmis dans l'en-tête `X-HCaptcha-Token` - Le backend vérifie le token via l'API hCaptcha (`https://hcaptcha.com/siteverify`) - Si la clé secrète n'est pas configurée côté serveur, la vérification est sautée (dégradation gracieuse) **Fichiers** : `artifacts/voix-du-peuple/src/pages/home.tsx` · `artifacts/flask-api/app.py` (`_verify_hcaptcha()`) --- ### 3. Rate limiting par IP + fingerprint **Outil** : Flask-Limiter v3 avec stockage Redis (si `REDIS_URL` défini) ou mémoire (dev). **Seuils par défaut** (configurables via variables d'environnement) : | Endpoint | Limite par défaut | Variable de contrôle | |----------|------------------|----------------------| | `POST /api/ideas` | 5/min · **3/heure** | `RATE_LIMIT_CONTRIBUTIONS` | | `POST /api/ideas/:id/flag` | 3/min · 10/heure | — | | `POST /api/admin/login` | 10/min | — | | Toutes routes | 60/heure · 200/jour | — | **Clé de rate limiting** : priorité au fingerprint FingerprintJS (hashé), sinon IP. Empêche de contourner la limite en changeant d'IP si le fingerprint est reconnu. **Pour activer Redis** : ```env REDIS_URL=redis://localhost:6379/0 ``` Sans Redis, le rate limiting est en mémoire (reset au redémarrage — suffisant pour une instance unique). **Fichiers** : `artifacts/flask-api/app.py` (`get_fingerprint_key()`, `RATE_LIMIT_CONTRIBUTIONS`) --- ### 4. Fingerprinting non-PII **Outil** : `@fingerprintjs/fingerprintjs` v4 (open source, pas de compte requis). **Principe** : FingerprintJS génère un `visitorId` côté client à partir de caractéristiques du navigateur (User-Agent, timezone, canvas fingerprint, etc.) sans créer de cookie tiers ni stocker de données personnelles. **Flux** : 1. À l'initialisation de l'app React (`App.tsx`), FingerprintJS est chargé 2. Le `visitorId` est stocké en mémoire (non persisté) 3. Il est envoyé sur chaque requête API dans l'en-tête `X-Visitor-Id` 4. Le backend le hash en SHA-256 (32 premiers hex) avant tout stockage 5. Le hash est enregistré en base (`ideas.fingerprint_hash`) pour analyse post-hoc si nécessaire **Données stockées** : uniquement le hash SHA-256 tronqué — non-réversible, non-PII au sens du RGPD. Aucun cookie, aucun suivi cross-site. **Fichiers** : `artifacts/voix-du-peuple/src/App.tsx` · `lib/api-client-react/src/custom-fetch.ts` · `artifacts/flask-api/app.py` · `artifacts/flask-api/database.py` --- ### 5. Cooldown par session (cookie httpOnly signé) **Principe** : après une soumission acceptée, un cookie httpOnly signé HMAC-SHA256 est posé. Toute tentative de soumission avant l'expiration du cooldown est rejetée avec un `429`. **Durée par défaut** : 3600 secondes (1 heure), configurable : ```env CONTRIBUTION_COOLDOWN_SECONDS=3600 ``` **Signature** : le cookie `_cv` contient `{timestamp}.{signature}` où la signature est `HMAC-SHA256(SECRET_KEY, timestamp_bytes)[:16]`. Impossible de forger sans connaître `SECRET_KEY`. **Prérequis** : ```env SECRET_KEY=une-longue-chaine-aleatoire-minimum-32-chars ``` Si `SECRET_KEY` n'est pas défini, le cooldown est désactivé (dégradation gracieuse). **Limite** : fonctionne pleinement en production (même domaine, Nginx reverse proxy). En développement cross-origin (Vite sur port différent de Flask), le cookie n'est pas envoyé automatiquement par le navigateur (CORS `supports_credentials=False`). **Fichiers** : `artifacts/flask-api/app.py` (`_sign_cooldown()`, `_verify_cooldown()`) --- ### 6. Détection de flood **Principe** : compteur en mémoire par IP (et par fingerprint si disponible) sur une fenêtre glissante de 5 minutes. Si le seuil est dépassé, une alerte `WARNING` est émise dans les logs. **Seuil par défaut** : 10 soumissions en 5 minutes, configurable : ```env FLOOD_THRESHOLD=10 ``` **Ce qui se passe** : l'alerte est loggée mais la soumission n'est pas bloquée (le rate limiter Flask-Limiter s'en charge). L'objectif est d'alerter l'opérateur pour investigation. **Format de l'alerte** : ``` WARNING ALERTE FLOOD — IP: 1.2.3.4 | fingerprint: abc123... | seuil: 10/5min ``` **Pour aller plus loin** : brancher sur un webhook (email Mailgun/Brevo, Slack/Mattermost) via un hook sur les logs `WARNING` avec le pattern `ALERTE FLOOD`. **Limite** : l'état est en mémoire et se réinitialise au redémarrage. Pour une persistance cross-restart, utiliser Redis directement avec `EXPIRE`. **Fichiers** : `artifacts/flask-api/app.py` (`_check_flood()`, `_flood_tracker`) --- ## Variables d'environnement récapitulatif ```env # Rate limiting REDIS_URL=redis://localhost:6379/0 # Optionnel — sinon mémoire RATE_LIMIT_CONTRIBUTIONS=5 per minute;3 per hour # Format flask-limiter # Cooldown session SECRET_KEY=une-longue-chaine-aleatoire-minimum-32-chars CONTRIBUTION_COOLDOWN_SECONDS=3600 # Flood detection FLOOD_THRESHOLD=10 # hCaptcha (désactivé si absent) HCAPTCHA_SECRET_KEY=votre-cle-secrete # Backend VITE_HCAPTCHA_SITE_KEY=votre-cle-de-site # Frontend (nécessite rebuild) ``` --- ## Ce que ces protections ne couvrent pas - **Bots sophistiqués JavaScript-capable** : FingerprintJS peut être contourné par un navigateur headless bien configuré. La combinaison IP + fingerprint + hCaptcha rend l'attaque coûteuse mais pas impossible. - **VPN / Tor** : le rate limiting IP peut être contourné. Le fingerprint compense partiellement. - **Submissions manuelles coordonnées** (brigading humain) : seul le contenu + la modération IA protège contre ce vecteur. - **Cross-origin en dev** : le cookie cooldown ne fonctionne pas en développement (ports différents, CORS sans credentials). --- ## Évolutions futures recommandées 1. **Redis** : déployer Redis et configurer `REDIS_URL` en production pour un rate limiting persistant et cross-process. 2. **hCaptcha** : activer dès que la plateforme est ouverte au public (clé gratuite, 5 minutes de setup). 3. **Alerte flood automatique** : brancher un webhook Brevo/Mailgun sur les logs `ALERTE FLOOD`. 4. **CAPTCHA invisible** : envisager hCaptcha en mode "invisible" (score-based) pour ne pas imposer de défi aux utilisateurs légitimes.