Add automatic IP blacklisting after 2 illegal contributions

Persistence: PostgreSQL table ip_abuse (ip_hash SHA-256, rejection_count,
timestamps, last_idea_id, blacklisted_at, expires_at). No raw IP stored.

Logic:
- First illegal contribution: recorded, tolerated (benefit of the doubt)
- Second illegal contribution: 30-day block (IP_BLACKLIST_DAYS, configurable)
- Counter is cumulative — valid contributions do not reset it
- Block check fires before all other validations in both submit routes

Backend:
- database.py: check_ip_blacklist, record_ip_rejection, get_ip_blacklist,
  remove_ip_blacklist; ip_abuse table created in init_db()
- app.py: _get_ip_hash() helper; blacklist check + record in submit_idea()
  and submit_consultation_idea(); admin routes GET/DELETE /api/admin/ip-blacklist

Admin panel: new "Blacklist" tab showing active entries with hash, rejection
count, trigger idea id, dates; "Lever" button for manual removal

Docs:
- SECURITE_ANTI_ABUS.md: section 8 describing the mechanism, RGPD basis,
  limits, and IP_BLACKLIST_DAYS env var
- privacy-policy.tsx: IP row updated to reflect hash storage + legal basis

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 17:42:31 +02:00
parent 219292ea6c
commit 5c5152a387
5 changed files with 308 additions and 2 deletions
+30
View File
@@ -158,6 +158,33 @@ WARNING ALERTE FLOOD — IP: 1.2.3.4 | fingerprint: abc123... | seuil: 10/5min
---
### 8. Blacklist IP automatique
**Principe** : lorsqu'une IP soumet deux contributions illégales (rejetées par le filtre IA), elle est automatiquement bloquée pour une durée configurable. La première soumission illégale est tolérée (bénéfice du doute) ; la deuxième déclenche le blocage, quelle que soit la durée écoulée entre les deux.
**Caractéristiques RGPD** :
- L'adresse IP brute n'est jamais stockée. Seul un hash SHA-256 tronqué (32 caractères, non réversible) est conservé.
- Base légale : intérêt légitime (RGPD art. 6(1)(f)) — protection de la plateforme contre les contenus illicites.
- Durée de conservation : 30 jours par défaut (`IP_BLACKLIST_DAYS`), configurable.
- Mention dans la politique de confidentialité : oui (section « Quelles données sont collectées ? »).
**Implémentation** :
- Table `ip_abuse` (PostgreSQL) : `ip_hash`, `rejection_count`, horodatages, `last_idea_id`, `blacklisted_at`, `expires_at`.
- Au 2ème rejet : `blacklisted_at` et `expires_at` sont renseignés ; toute tentative ultérieure retourne `403 ip_blacklisted`.
- Le compteur est cumulatif : une contribution valide entre deux rejets ne remet pas le compteur à zéro.
- Persistant cross-workers et cross-restarts (PostgreSQL, pas mémoire).
**Gestion admin** :
- Le panel admin affiche les IPs blacklistées actives (hash + date + contribution déclencheuse).
- Un bouton « Lever » permet à l'admin de supprimer une entrée manuellement (faux positif, recours).
- Route : `GET /api/admin/ip-blacklist` · `DELETE /api/admin/ip-blacklist/<ip_hash>`
**Limite** : contournable par changement d'IP (VPN, Tor). Combiné avec le fingerprint FingerprintJS, le contournement est plus coûteux mais reste possible.
**Fichiers** : `artifacts/flask-api/database.py` (`check_ip_blacklist`, `record_ip_rejection`, `get_ip_blacklist`, `remove_ip_blacklist`) · `artifacts/flask-api/app.py` (`_get_ip_hash`, routes admin)
---
## Variables d'environnement récapitulatif
```env
@@ -175,6 +202,9 @@ 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)
# Blacklist IP
IP_BLACKLIST_DAYS=30 # Durée du blocage après 2 rejets illégaux
```
---