Licence EUPL-1.2 + hardening anti-abus
P1 — Licence : - Ajout du fichier LICENSE (EUPL-1.2 complet) - README mis à jour : section licence, table docs, vars d'environnement - En-têtes EUPL ajoutés dans les fichiers sources principaux (Flask, React) P2 — Hardening anti-abus : - Rate limiting Redis-ready (REDIS_URL) avec clé fingerprint + IP - Honeypot anti-bot : champ caché côté client + vérification serveur - Fingerprinting non-PII via FingerprintJS (hash SHA-256, colonne ideas.fingerprint_hash) - Cooldown session : cookie httpOnly signé HMAC-SHA256 (SECRET_KEY requis) - Détection de flood : alerte WARNING si > FLOOD_THRESHOLD soumissions / 5 min - hCaptcha stub : intégré, activable via HCAPTCHA_SECRET_KEY + VITE_HCAPTCHA_SITE_KEY - Nouvelles dépendances : redis (backend), @fingerprintjs/fingerprintjs + @hcaptcha/react-hcaptcha (frontend) - docs/SECURITE_ANTI_ABUS.md : documentation complète des seuils et de la configuration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user