Files
ja4-platform/services/bot-detector/DOCUMENTATION.md
toto d469e39da7 feat: ja4-platform monorepo — 5 services unified, tests & RPM builds standardized
Services:
- ja4sentinel: TLS/JA4 fingerprint capture daemon (Go, libpcap)
- logcorrelator: JA4 log correlation engine (Go, ClickHouse)
- mod_reqin_log: Apache module (C, JSON request logging)
- bot_detector: ML bot detection pipeline (Python)
- dashboard: FastAPI/Streamlit analytics UI (Python)

Shared libraries:
- shared/go/ja4common: logger, config, shutdown, ipfilter (Go module)
- shared/python/ja4_common: ClickHouseClient, ClickHouseSettings (Python package)
- shared/clickhouse/: canonical SQL migrations (10 files)

Build & packaging:
- Unified 3-stage Dockerfile.package for Go RPMs (el8/el9/el10)
- go.work workspace linking sentinel, correlator, ja4common
- Makefile with test-all, build-all, rpm-* targets

Fixes applied:
- go.work: 1.21 → 1.24.6 (required by sentinel)
- correlator Dockerfiles: golang:1.21 → golang:1.24
- replace directives in go.mod for ja4common local path
- pyproject.toml: setuptools.backends → setuptools.build_meta
- Removed static libpcap linking (unavailable on Rocky 9)
- Fixed data races in output/writers_test.go (sync.Mutex + atomic.Int32)
- Rewrote corrupted test files (logger_test.go × 2)

Test coverage:
- correlator: 67.1% total (unixsocket 80.5%, config 91.7%, app 83.3%, multi 87.7%, stdout 100%)
- sentinel: all 10 packages pass (api, capture, config, fingerprint, ipfilter, logging, output, tlsparse)

Documentation:
- README.md + docs/ (architecture, development, 5 services, shared libs, DB schema & migrations)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-07 16:42:59 +02:00

32 KiB
Raw Blame History

Bot Detector IA — Documentation Technique

Version du code : v11 | Dernière mise à jour : 2026-03-17


Table des matières

  1. Vue d'ensemble
  2. Architecture système
  3. Pipeline de détection
  4. Modèles et features
  5. Approche semi-supervisée
  6. Gestion des modèles
  7. Données d'entrée — vue ClickHouse
  8. Données de sortie
  9. Configuration
  10. Observabilité
  11. Réputation et enrichissement
  12. Fondements scientifiques
  13. Améliorations implémentées (v11)
  14. Migration de schéma ClickHouse

1. Vue d'ensemble

Le Bot Detector IA est un service de détection d'activité suspecte et de bots sur un trafic HTTP. Il tourne en boucle continue (toutes les 5 minutes par défaut) et analyse des données agrégées issues de ClickHouse.

Principe général

ClickHouse (view_ai_features_1h)
        │
        ▼
┌───────────────────────┐
│  Séparation du trafic │
│  ├─ Bots connus       │  → Étiquetés via réputation IP / JA4 / ASN
│  ├─ Trafic humain     │  → Sert de baseline d'entraînement pour l'IF
│  └─ Trafic inconnu    │  → Scoré par Isolation Forest
└───────────────────────┘
        │
        ▼
┌───────────────────────┐
│  Isolation Forest     │
│  (semi-supervisé)     │
│  ├─ Modèle Complet    │  TCP + TLS + HTTP (35 features, correlated=1)
│  └─ Modèle Applicatif │  HTTP seul (31 features, correlated=0)
└───────────────────────┘
        │
        ▼
ClickHouse (ml_detected_anomalies)

Caractéristiques clés

Propriété Valeur
Algorithme Isolation Forest (sklearn)
Supervision Semi-supervisée (baseline humain + réputation)
Fenêtre d'analyse 1 heure glissante (optionnel : 24h avec ENABLE_MULTIWINDOW)
Cycle d'exécution 300 s (configurable)
Re-entraînement Toutes les 1 h (configurable) + retrain forcé sur dérive conceptuelle
Contamination 2 % (fraction d'anomalies attendues dans la baseline)
Seuil d'anomalie Adaptatif : min(percentile_5, -0.03)

2. Architecture système

┌─────────────────────────────────────────────────────────────────┐
│                         Docker Compose                          │
│                                                                 │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                   bot_detector_ai                        │   │
│  │                                                          │   │
│  │  ┌────────────┐  ┌──────────────┐  ┌─────────────────┐  │   │
│  │  │  Health    │  │  Main Loop   │  │  ClickHouse     │  │   │
│  │  │  :8080     │  │  (300s cycle)│  │  Client         │  │   │
│  │  │  (thread)  │  │              │  │  (reconnect)    │  │   │
│  │  └────────────┘  └──────────────┘  └─────────────────┘  │   │
│  │                                                          │   │
│  │  Volumes:                                                │   │
│  │  ├─ ./bot_detector_logs → /var/log/bot_detector          │   │
│  │  ├─ ./bot_detector_models → /var/lib/bot_detector        │   │
│  │  ├─ ./reputation/data/user_files/bot_ip.csv (ro)         │   │
│  │  ├─ ./reputation/data/user_files/bot_ja4.csv (ro)        │   │
│  │  └─ ./reputation/data/user_files/asn_reputation.csv (ro) │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                  │
└────────────────────────────┬────────────────────────────────────┘
                             │ HTTP :8123
                             ▼
                    ClickHouse externe
                    (test-sdv-anubis.sdv.fr)

Fichiers et répertoires

Chemin Rôle
bot_detector/bot_detector.py Code source principal
bot_detector/requirements.txt Dépendances Python
bot_detector/Dockerfile Image Python 3.11-slim
docker-compose.yml Orchestration Docker
.env Variables d'environnement (non commité)
bot_detector_logs/decisions.jsonl Journal JSONL structuré (rotation 50 MB × 7)
bot_detector_models/model_<name>_<version>.joblib Modèle sérialisé
bot_detector_models/model_<name>_<version>.meta.json Métadonnées du modèle
bot_detector_models/model_<name>.current Pointeur vers la version active
bot_detector_models/training_history.jsonl Historique des entraînements
reputation/bot_ip.csv ~288 k entrées IP/CIDR de bots connus
reputation/bot_ja4.csv Empreintes JA4 de bots
reputation/asn_reputation.csv Labels ASN (human / bot)

3. Pipeline de détection

3.1 Cycle principal (fetch_and_analyze)

1. Génération d'un cycle_id (timestamp)
2. Requête view_ai_features_1h → DataFrame df
3. Requête view_ip_recurrence → recurrence_map {src_ip: count}
4. Nettoyage des colonnes (fillna, astype)
5. Log CYCLE_START (total, human, known_bot, correlated)
6. Séparation df → correlated=1 / correlated=0
7. Appel run_semi_supervised_logic() × 2 (modèle Complet + Applicatif)
8. Concaténation, déduplication par src_ip (score le plus bas)
9. Insertion dans ml_detected_anomalies
10. Log CYCLE_END
11. Attente CYCLE_INTERVAL secondes

3.2 Logique semi-supervisée (run_semi_supervised_logic)

df (trafic de la fenêtre 1h)
    │
    ├─ A7 → validate_features() : exclusion des features manquantes ou constantes
    │
    ├─ bot_name != ''  →  known_bots  →  KNOWN_BOT (log + insertion)
    │
    └─ bot_name == ''  →  unknown_traffic
                              │
                              ├─ asn_label == 'human'  →  human_baseline
                              │       (min. 500 sessions requis)
                              │       └──► load_or_train_model()
                              │               ├─ A1 : drift check (z-score / features)
                              │               └─ Si drift ≥ DRIFT_THRESHOLD : retrain forcé
                              │
                              └─ reste du trafic inconnu
                                      │
                                      ▼
                               IsolationForest.decision_function() → raw_scores
                                      │
                               A10 : normalize_scores() → anomaly_score [-1, 0]
                                      │
                               A2 : effective_threshold = min(percentile_5, ANOMALY_THRESHOLD)
                                      │
                               A6 : raw_score -= log1p(recurrence) × RECURRENCE_WEIGHT
                                      │
                               raw_score < effective_threshold ?
                                      │
                                     YES → A4 : SHAP top-5 features → reason
                                           A8 : DBSCAN clustering → campaign_id
                                           ANOMALY (log + insertion)
                                      │
                                      NO  → ignoré

3.3 Niveaux de menace

Score Niveau Interprétation
< -0.30 CRITICAL Comportement extrêmement anormal
< -0.15 HIGH Fort signal d'anomalie
< -0.05 MEDIUM Anomalie modérée
≥ -0.05 LOW Légèrement inhabituel

Le seuil d'insertion (ANOMALY_THRESHOLD = -0.03) est plus permissif que LOW. Toutes les IP dont le score passe sous ce seuil sont insérées, quelle que soit leur catégorie de niveau.


4. Modèles et features

4.1 Architecture à deux niveaux

Modèle Condition Nb features Données utilisées
Complet correlated = 1 35 HTTP + TCP + TLS
Applicatif correlated = 0 31 HTTP uniquement

La corrélation (correlated) indique si les logs HTTP ont pu être enrichis avec les données TCP/TLS de la même connexion. En l'absence de corrélation (capture incomplète ou trafic chiffré sans inspection), seul le modèle Applicatif est utilisé.

4.2 Features communes (31 — modèle Applicatif)

Comportement HTTP de base

Feature Description
hits Nombre de requêtes sur la fenêtre
hit_velocity Requêtes par seconde
fuzzing_index Score de diversité anormale des chemins/paramètres
post_ratio Fraction de requêtes POST
port_exhaustion_ratio Fraction de ports sources différents / total ports
orphan_ratio Requêtes sans réponse associée

Gestion des connexions

Feature Description
max_keepalives Nb max de requêtes sur une même connexion keep-alive
tcp_shared_count Connexions TCP partagées entre plusieurs sessions HTTP

Empreinte navigateur (Browser Fingerprint)

Feature Description
header_count Nombre d'en-têtes HTTP envoyés
has_accept_language Présence de Accept-Language
has_cookie Présence de Cookie
has_referer Présence de Referer
modern_browser_score Score composite de conformité navigateur moderne
ua_ch_mismatch Incohérence entre User-Agent et Client Hints
ip_id_zero_ratio Ratio de paquets IP avec ID=0 (headless / stack minimale)
header_order_shared_count Partage d'un même ordre d'en-têtes entre IPs
header_order_confidence Confiance dans l'ordre d'en-têtes (entropie normalisée)
distinct_header_orders Nombre d'ordres d'en-têtes distincts observés

Patterns de navigation

Feature Description
request_size_variance Variance de la taille des requêtes
multiplexing_efficiency Efficacité du multiplexage HTTP/2
mss_mobile_mismatch Incohérence MSS TCP / profil mobile annoncé
asset_ratio Fraction de requêtes vers des ressources statiques
direct_access_ratio Fraction d'accès directs (sans referer)
is_ua_rotating Rotation de User-Agent détectée (flag 0/1)
distinct_ja4_count Nombre de fingerprints JA4 distincts par IP

Concentration et rareté

Feature Description
src_port_density Densité des ports sources (entropy)
ja4_asn_concentration Concentration d'un même JA4 dans un ASN
ja4_country_concentration Concentration d'un même JA4 par pays
is_rare_ja4 JA4 peu commun dans la population (flag 0/1)

Dimensions temporelles et de diversité (académiques)

Feature Description
temporal_entropy Entropie de la distribution temporelle des requêtes
path_diversity_ratio Diversité des chemins URL accédés
url_depth_variance Variance de la profondeur des URL
anomalous_payload_ratio Fraction de payloads avec patterns anormaux

4.3 Features additionnelles TCP/TLS (modèle Complet uniquement)

Feature Description
tcp_jitter_variance Variance de la gigue inter-paquets TCP
alpn_http_mismatch Incohérence entre ALPN négocié et protocole HTTP effectif
is_alpn_missing ALPN absent dans le TLS ClientHello
sni_host_mismatch Incohérence entre SNI TLS et Host HTTP

5. Approche semi-supervisée

5.1 Fondement théorique

L'Isolation Forest (Liu, Ting & Zhou, 2008) est un algorithme d'apprentissage non supervisé conçu pour la détection d'anomalies. Son principe : les anomalies, étant rares et différentes, sont isolées en moins de partitions dans un arbre de décision aléatoire que les points normaux.

Le score de décision (decision_function) est normalisé entre -1 (très anormal) et +1 (très normal). Le paramètre contamination fixe la fraction de points considérés comme anomalies dans l'ensemble d'entraînement.

5.2 Dimension semi-supervisée

L'approche est semi-supervisée car :

  1. Étiquetage partiel : Les bots connus (via réputation IP/JA4) et les humains (via réputation ASN) sont identifiés a priori.
  2. Entraînement sur la classe normale uniquement : L'IF est entraîné exclusivement sur la baseline humaine (asn_label = 'human', bot_name = ''). Il apprend ainsi le profil du trafic légitime.
  3. Détection par déviation : Tout trafic inconnu qui s'éloigne du profil humain est scoré négativement.

Cette approche suit le paradigme One-Class Classification (Tax & Duin, 2004) appliqué à la détection de bots, proche des travaux de Kruegel & Vigna (2003) sur la détection d'anomalies réseau.

5.3 Qualité de la baseline humaine

Le minimum de 500 sessions humaines est une garde-fou empirique. En dessous de ce seuil, l'IF ne dispose pas de suffisamment d'exemples pour définir un profil normal robuste, augmentant le risque de faux positifs.

En pratique, les cycles observés montrent entre 1 264 et 1 725 sessions humaines par fenêtre d'une heure.


6. Gestion des modèles

6.1 Cycle de vie d'un modèle

Démarrage cycle
      │
      ▼
Existe un .current ?  ──NON──► Entraîner nouveau modèle
      │
     OUI
      │
      ▼
Âge < RETRAIN_INTERVAL_H ?
      │                  │
     OUI                NON
      │                  │
      ▼                  └──► Entraîner nouveau modèle
A1 : Drift check               (MODEL_TRAINED)
(z-score vs baseline_stats)
      │
Drift ≥ DRIFT_THRESHOLD ?
      │              │
     NON            OUI
      │              │
Charger modèle  Entraîner nouveau modèle
(MODEL_LOADED)  (DRIFT_DETECTED + MODEL_TRAINED)

6.2 Versioning des modèles

Chaque modèle est identifié par un version_id au format YYYYMMDD_HHMMSS. Les fichiers associés sont :

  • model_{name}_{version_id}.joblib — modèle sérialisé (joblib/pickle)
  • model_{name}_{version_id}.meta.json — métadonnées (features, contamination, nb samples, etc.)
  • model_{name}.current — pointeur atomique vers la version active

L'historique est limité à MODEL_HISTORY_COUNT versions (72 en production = 3 jours à 1 h de retrain).

Le fichier .meta.json contient maintenant un champ baseline_stats avec les statistiques de distribution (mean, std, p25, p75) de chaque feature, utilisées pour la détection de dérive (A1).

6.3 Paramètres Isolation Forest

IsolationForest(
    n_estimators=300,       # Nombre d'arbres (compromis précision/temps)
    contamination=0.02,     # 2% d'anomalies estimées dans la baseline
    random_state=42,        # Reproductibilité
    n_jobs=-1               # Parallélisation sur tous les cores
)

7. Données d'entrée — vue ClickHouse

7.1 Vue principale : view_ai_features_1h

Agrégation sur 1 heure glissante, une ligne par src_ip. Colonnes clés :

Colonne Type Source
src_ip String TCP/IP
ja4 String TLS fingerprint (JA4+)
host String HTTP Host header
bot_name String Réputation IP/JA4 (vide si inconnu)
asn_number String GeoIP/ASN lookup
asn_org String Organisation ASN
asn_domain String Domaine ASN
country_code String Pays source
asn_label String human / bot / unknown
correlated Int 1 si TCP/TLS disponible, 0 sinon
hits Float Nb requêtes
hit_velocity Float Req/s
…(26+ features) Float Voir section 4.2

7.2 Vue de récurrence : view_ip_recurrence

SELECT src_ip, recurrence FROM {DB}.view_ip_recurrence

Donne le nombre de fois qu'une IP a déjà été détectée comme menace dans l'historique. Enrichit le champ recurrence dans la sortie.


8. Données de sortie

8.1 Table ClickHouse : ml_detected_anomalies

Toutes les anomalies et bots connus détectés sont insérés dans cette table. Colonnes notables :

Colonne Description
detected_at Timestamp de détection
src_ip IP source
ja4 Fingerprint TLS/JA4 (HTTP_CLEAR_TEXT si absent)
host Vhost ciblé
bot_name Nom du bot (vide si anomalie IF)
anomaly_score Score IF (0.0 pour bots connus)
threat_level CRITICAL / HIGH / MEDIUM / LOW / KNOWN_BOT
model_name Complet ou Applicatif
recurrence Nb d'apparitions historiques + 1
reason Description textuelle de l'anomalie
is_headless Dérivé de is_fake_navigation
…(toutes les features) Pour analyse post-mortem

8.2 Journal JSONL : decisions.jsonl

Événements structurés en JSON Lines, rotatifs (50 MB × 7 fichiers).

Événement Déclencheur
SERVICE_START Démarrage du conteneur
SERVICE_STOP Arrêt propre (SIGTERM/SIGINT)
CYCLE_START Début d'un cycle d'analyse
CYCLE_END Fin du cycle (résumé inserés)
MODEL_LOADED Réutilisation d'un modèle existant
MODEL_TRAINED Nouvel entraînement
KNOWN_BOT Bot connu identifié
ANOMALY Anomalie IF détectée
SKIPPED_LOW_DATA Cycle ignoré (baseline < 500)
CONSECUTIVE_FAILURES Erreur ClickHouse répétée

9. Configuration

Toutes les valeurs sont passées via variables d'environnement (fichier .env).

Variable Défaut Description
CLICKHOUSE_HOST clickhouse Hôte ClickHouse
CLICKHOUSE_DB mabase_prod Base de données
CLICKHOUSE_USER default Utilisateur
CLICKHOUSE_PASSWORD (vide) Mot de passe
ISOLATION_CONTAMINATION 0.001 Fraction d'anomalies attendues (0 < x < 0.5)
ANOMALY_THRESHOLD -0.05 Seuil statique de score pour insertion
CYCLE_INTERVAL_SEC 300 Délai entre cycles (secondes)
MAX_CONSECUTIVE_FAILURES 3 Échecs avant passage en DEGRADED
BOT_DETECTOR_LOG /var/log/bot_detector/decisions.jsonl Fichier de log
LOG_BACKUP_COUNT 7 Nb de rotations conservées
MODEL_DIR /var/lib/bot_detector Répertoire des modèles
RETRAIN_INTERVAL_HOURS 24 Fréquence de re-entraînement
MODEL_HISTORY_COUNT 10 Nb de versions de modèles conservées
HEALTH_PORT 8080 Port du health check HTTP
A1 DRIFT_THRESHOLD 0.30 Fraction de features déroutantes déclenchant un retrain forcé
A2 ANOMALY_PERCENTILE 5 Percentile pour le seuil adaptatif (020)
A3 ENABLE_MULTIWINDOW false Active l'analyse sur fenêtre 24h
A3 MULTIWINDOW_VIEW view_ai_features_24h Nom de la vue 24h dans ClickHouse
A4 ENABLE_SHAP true Active le calcul SHAP (désactivé si shap non installé)
A5 DEDUP_TTL_MIN 60 TTL de déduplication inter-cycles (0 = désactivé)
A6 RECURRENCE_WEIGHT 0.005 Pénalité de score par log(récurrence)
A7 MIN_VALID_FEATURE_RATIO 0.50 Ratio minimum de features valides pour procéder
A8 ENABLE_CLUSTERING true Active le clustering DBSCAN des anomalies
A8 CLUSTERING_MIN_SAMPLES 3 Taille minimale d'un cluster DBSCAN

10. Observabilité

10.1 Health check

GET http://localhost:8080/
# → 200 OK          service opérationnel
# → 503 DEGRADED    ≥ MAX_CONSECUTIVE_FAILURES échecs ClickHouse consécutifs

10.2 Logs opérationnels

Les logs console suivent le format [YYYY-MM-DD HH:MM:SS] message. Le fichier JSONL permet des analyses post-mortem avec des outils comme jq :

# Voir les dernières anomalies CRITICAL
jq 'select(.event=="ANOMALY" and .threat_level=="CRITICAL")' decisions.jsonl

# Voir les top features SHAP pour les anomalies HIGH
jq 'select(.event=="ANOMALY" and .threat_level=="HIGH") | .reason' decisions.jsonl

# Détecter les dérives de distribution
jq 'select(.event=="DRIFT_DETECTED")' decisions.jsonl

# Voir les campagnes coordonnées (campaign_id >= 0)
jq 'select(.event=="ANOMALY" and .campaign_id >= 0) | {src_ip, campaign_id, threat_level}' decisions.jsonl

# Compter les bots connus par nom
jq -r 'select(.event=="KNOWN_BOT") | .bot_name' decisions.jsonl | sort | uniq -c | sort -rn

# Résumé des cycles
jq 'select(.event=="CYCLE_END")' decisions.jsonl
Événement Déclencheur
SERVICE_START Démarrage du conteneur
SERVICE_STOP Arrêt propre (SIGTERM/SIGINT)
CYCLE_START Début d'un cycle d'analyse
CYCLE_END Fin du cycle (résumé insertés + dedup_ttl_min)
MODEL_LOADED Réutilisation d'un modèle existant (+ drift_score)
MODEL_TRAINED Nouvel entraînement
DRIFT_DETECTED Dérive conceptuelle détectée → retrain forcé
FEATURE_WARNING Features manquantes / constantes / agrégats globaux détectés (loggué uniquement si la situation change)
SKIPPED_INVALID_FEATURES Cycle ignoré (trop peu de features valides)
KNOWN_BOT Bot connu identifié
ANOMALY Anomalie IF détectée (+ effective_threshold, campaign_id, raw_anomaly_score)
SKIPPED_LOW_DATA Cycle ignoré (baseline < 500)
CONSECUTIVE_FAILURES Erreur ClickHouse répétée

10.3 Avertissements sur les features (A7)

Les avertissements de features ne sont affichés en console qu'une seule fois (à la première détection ou lors d'un changement). Les cycles suivants avec la même situation ne génèrent pas de bruit. L'événement FEATURE_WARNING reste dans le JSONL pour traçabilité.

Catégorie Message console Cause typique
zero Features à 0 (pipeline non-alimenté) Table source vide / LEFT JOIN sans match
unique_nonzero Features non-discriminantes (agrégat global) PARTITION BY sur valeur NULL → partition unique
missing Features absentes du schéma Colonne manquante dans la vue ClickHouse

Voir CLICKHOUSE_FEATURES_DIAGNOSTIC.md pour le détail des corrections ClickHouse nécessaires.

11.1 Sources de réputation

Fichier Format Contenu
bot_ip.csv ip_cidr,bot_name ~288 k IP/CIDR de bots référencés
bot_ja4.csv ja4,bot_name Fingerprints JA4 de bots
asn_reputation.csv asn_number,label Labels ASN (human/bot)

Ces fichiers sont montés en lecture seule dans le conteneur. Ils sont écrits par ClickHouse (FILE engine) et partagés via volume Docker.

11.2 Hiérarchie de classification

1. bot_name != '' (depuis view_ai_features_1h)
   → KNOWN_BOT : bot identifié par réputation IP ou JA4

2. asn_label == 'human' (depuis view_ai_features_1h)
   → Utilisé pour la baseline d'entraînement de l'IF

3. Trafic restant
   → Scoré par Isolation Forest
   → Anomalie si score < ANOMALY_THRESHOLD

12. Fondements scientifiques

12.1 Isolation Forest (Liu et al., 2008)

L'algorithme repose sur la propriété que les anomalies sont isolées plus rapidement dans des arbres de partitionnement aléatoire. La longueur moyenne du chemin d'isolation est normalisée pour produire un score entre 0 et 1 (transposé ici en -1 à +1 par decision_function).

Propriétés clés :

  • Complexité O(n log n) pour l'entraînement
  • Robuste aux données de haute dimensionnalité (3135 features ici)
  • Pas d'hypothèse sur la distribution des données
  • Efficace sur de grands volumes (n_estimators=300, n_jobs=-1)

12.2 JA4+ Fingerprinting (FoxIO, 2023)

JA4 est la 4e génération de fingerprints TLS/QUIC/HTTP, successeur de JA3. Il capture les caractéristiques du ClientHello TLS (versions, ciphers, extensions) en une empreinte compacte permettant d'identifier des familles de clients (navigateurs, bots, outils). L'utilisation de is_rare_ja4, distinct_ja4_count et ja4_asn_concentration exploite cette propriété.

12.3 One-Class Classification appliquée aux bots

L'approche s'inscrit dans la lignée des travaux sur la détection de bots web :

  • Stevanovic et al. (2013) : détection de bots par analyse comportementale de flux HTTP
  • Kruegel & Vigna (2003) : détection d'anomalies réseau par profils normaux
  • Barford & Yegneswaran (2007) : classification comportementale des botnets

La combinaison de features HTTP comportementales (velocity, fuzzing, post_ratio), de features d'empreinte (JA4, headers), et de features TCP/TLS (jitter, ALPN, SNI) reproduit l'approche multi-couche recommandée par la littérature récente.

12.4 Entropie temporelle comme signal d'anomalie

Le feature temporal_entropy mesure l'entropie de Shannon sur la distribution temporelle des requêtes dans la fenêtre. Un bot avec un timing régulier (scripted polling) produit une entropie faible, tandis qu'un humain naviguant naturellement produit une distribution plus aléatoire. Ce signal est utilisé dans les travaux de Wang et al. (2014) sur la détection de crawlers web.


13. Améliorations implémentées (v11)

A1 — Détection de dérive conceptuelle

Fonctionnement : À chaque cycle, avant de décider de charger ou de réentraîner le modèle, on compare la distribution courante de la baseline humaine avec celle sauvegardée lors du dernier entraînement. Pour chaque feature, un z-score est calculé :

z = |mean_current - mean_trained| / std_trained

Si la fraction de features avec z > 2.0 dépasse DRIFT_THRESHOLD (30% par défaut), un re-entraînement est forcé et l'événement DRIFT_DETECTED est loggué.

Métadonnées sauvegardées : baseline_stats dans le .meta.json contient {mean, std, p25, p75} par feature.

Références : Gama et al. (2014) — A Survey on Concept Drift Adaptation


A2 — Seuil adaptatif par percentile

Fonctionnement :

effective_threshold = min(np.percentile(raw_scores[raw_scores < 0], ANOMALY_PERCENTILE),
                          ANOMALY_THRESHOLD)

Le seuil effectif est le minimum entre le ANOMALY_PERCENTILE-ème percentile des scores négatifs et le seuil statique. Cela garantit que le seuil ne peut pas remonter au-dessus du seuil configuré, mais peut s'adapter vers le bas selon la distribution courante.

Le seuil utilisé est loggué dans chaque événement ANOMALY.


A3 — Analyse multi-fenêtres (optionnelle)

Activation : ENABLE_MULTIWINDOW=true + une vue view_ai_features_24h dans ClickHouse.

Fonctionnement : Deux paires de modèles supplémentaires (Complet_24h, Applicatif_24h) tournent sur la fenêtre de 24h. Les anomalies des deux fenêtres sont fusionnées via une logique OR : une IP est flaggée si elle est anormale dans au moins une fenêtre. En cas de doublon, le score le plus bas (le plus anormal) est conservé.

Utilité : Détection des bots low-and-slow invisibles sur 1h mais clairement anormaux sur 24h.


A4 — Explainabilité par SHAP

Fonctionnement : Pour chaque anomalie détectée, shap.TreeExplainer calcule la contribution de chaque feature au score d'anomalie. Les 5 features les plus négatives (les plus responsables de l'anomalie) sont incluses dans le champ reason :

[Complet] Score: -0.112 | SHAP: is_alpn_missing(-1.081) | tcp_jitter_variance(-1.073) |
           ja4_asn_concentration(-1.062) | temporal_entropy(-0.887) |
           direct_access_ratio(-0.886) | Threat: MEDIUM

Désactivation : ENABLE_SHAP=false ou si le package shap n'est pas installé.

Références : Lundberg & Lee (2017) — A Unified Approach to Interpreting Model Predictions


A5 — Déduplication inter-cycles avec TTL

Fonctionnement : Avant chaque insertion, la table ml_detected_anomalies est interrogée pour identifier les IPs déjà insérées dans les DEDUP_TTL_MIN dernières minutes. Une IP est réinsérée uniquement si son score brut s'est dégradé d'au moins 0.05 points.

Désactivation : DEDUP_TTL_MIN=0


A6 — Pondération du score par récurrence

Fonctionnement :

raw_score_adjusted = raw_score - log1p(recurrence) × RECURRENCE_WEIGHT

Une IP détectée 10 fois reçoit une pénalité de log(11) × 0.005 ≈ 0.012 sur son score brut, ce qui la rapproche du seuil de détection. Ce mécanisme simule un prior bayésien : les IPs récidivistes sont plus probablement malveillantes.


A7 — Validation de complétude des features

Fonctionnement : Avant entraînement et scoring, validate_features() détecte :

  • Les features absentes de la vue ClickHouse
  • Les features constantes (std = 0, donc non discriminantes)

Les features invalides sont exclues du modèle. Si la fraction de features valides est inférieure à MIN_VALID_FEATURE_RATIO (50%), le cycle est ignoré.

Bénéfice : Les features constantes (souvent dues à des colonnes non encore implémentées dans la vue) ne biaisent plus le modèle.


A8 — Clustering comportemental (DBSCAN)

Fonctionnement : Après détection, DBSCAN est appliqué sur les features normalisées des anomalies :

X_scaled = StandardScaler().fit_transform(anomalies[valid_features])
labels = DBSCAN(eps=0.5, min_samples=CLUSTERING_MIN_SAMPLES).fit_predict(X_scaled)
  • campaign_id = -1 : IP isolée (comportement unique)
  • campaign_id >= 0 : membre d'une campagne coordonnée

Le campaign_id est loggué dans les événements ANOMALY (JSONL). Il n'est pas encore dans le schéma ClickHouse (voir §14).

Références : Ester et al. (1996) — A Density-Based Algorithm for Discovering Clusters


A10 — Normalisation des scores entre modèles

Fonctionnement :

# Scores négatifs normalisés en [-1, 0], scores positifs inchangés
anomaly_score_normalized = normalize_scores(raw_score)

Le champ anomaly_score dans ClickHouse contient désormais le score normalisé, permettant une comparaison cohérente entre le modèle Complet (35 features) et le modèle Applicatif (31 features). Le score brut IF est conservé dans raw_anomaly_score (logs JSONL uniquement) et est utilisé pour l'assignation du threat level.


14. Migration de schéma ClickHouse

Les nouvelles colonnes suivantes sont disponibles dans les logs JSONL mais pas encore dans la table ml_detected_anomalies. Pour les activer :

ALTER TABLE mabase_prod.ml_detected_anomalies
    ADD COLUMN IF NOT EXISTS campaign_id   Int32   DEFAULT -1,
    ADD COLUMN IF NOT EXISTS raw_anomaly_score Float32 DEFAULT 0;

Après cette migration, ajouter ces colonnes à la liste cols dans fetch_and_analyze() (elles sont déjà calculées en mémoire).