Files
ja4-platform/services/bot-detector/DOCUMENTATION.md
toto 6b3cc54652 docs: réécriture audit, DOCUMENTATION.md et IMPROVEMENTS.md pour architecture modulaire
- AUDIT: conformité mise à jour 97.9% (142/145), références modulaires
- DOCUMENTATION.md: 1083 lignes, 7 sections, 11 modules documentés
- IMPROVEMENTS.md: A1-A10/B1-B10 annotés /🔄/ avec localisations

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-09 22:14:18 +02:00

42 KiB
Raw Blame History

Bot Detector IA — Documentation Technique

Architecture modulaire (11 modules) | Dernière mise à jour : 2025-07-15


Table des matières

  1. Vue d'ensemble
  2. Installation et configuration
  3. Modules
  4. Features ML
  5. Classification des menaces
  6. Tests
  7. Diagnostic

1. Vue d'ensemble

Le Bot Detector IA est un service de détection d'activité suspecte et de bots sur un trafic HTTP/TLS. Il tourne en boucle continue (toutes les 5 minutes par défaut), analyse des données agrégées issues de ClickHouse, et produit des scores d'anomalie via un ensemble triple-voix (Extended Isolation Forest + Autoencoder + XGBoost).

1.1 Architecture modulaire

services/bot-detector/bot_detector/
├── __init__.py              # Paquetage Python
├── __main__.py         (41) # Point d'entrée, boucle principale
├── config.py          (154) # Variables d'environnement, constantes, imports optionnels
├── log.py              (65) # Journalisation structurée (structlog JSON)
├── infra.py            (89) # Client ClickHouse, health check HTTP, arrêt propre
├── browser.py         (170) # Détection multifactorielle 5 axes des navigateurs
├── scoring.py         (279) # Validation, seuil adaptatif, SHAP, HDBSCAN, dérive
├── models.py          (478) # EIF (isotree), AutoEncoder (PyTorch), XGBoost, persistance
├── preprocessing.py   (117) # Nettoyage, imputation, listes de features
├── pipeline.py        (378) # run_semi_supervised_logic() — orchestrateur ML
├── cycle.py           (371) # fetch_and_analyze(), cycle principal, multi-fenêtres
└── tests/
    ├── test_detector.py      # 36 tests auto-contenus
    └── conftest.py

1.2 Flux de données

ClickHouse                                          ClickHouse
 view_ai_features_1h ──┐                     ┌──► ml_all_scores
 view_thesis_features ──┤    ┌─────────────┐  │
 view_ip_recurrence ────┼──► │  cycle.py   │──┤
 audit_logs (feedback) ─┤    │  pipeline.py│  │
 view_ai_features_24h ──┘    │  models.py  │  └──► ml_detected_anomalies
    (si multiwindow)         └─────────────┘

Étapes du flux :

  1. cycle.py récupère les features agrégées sur 1h (et 24h si ENABLE_MULTIWINDOW=true)
  2. preprocessing.py nettoie les colonnes et enrichit via la détection navigateur
  3. pipeline.py orchestre l'ensemble ML sur deux modèles parallèles :
    • Complet (~63 features, correlated=1) : couches L3 à L7 (TCP + TLS + HTTP)
    • Applicatif (~51 features, correlated=0) : couche L7 uniquement (HTTP)
  4. models.py entraîne/charge les trois voix de l'ensemble (EIF, AE, XGBoost)
  5. scoring.py normalise, explique (SHAP), clusterise (HDBSCAN) et détecte la dérive
  6. Les résultats sont insérés dans ml_detected_anomalies et ml_all_scores

1.3 Caractéristiques clés

Propriété Valeur
Ensemble ML EIF + Autoencoder + XGBoost (triple-voix)
Supervision Semi-supervisée (baseline humaine asn_label='isp')
Fenêtre d'analyse 1h glissante (+ 24h optionnel via ENABLE_MULTIWINDOW)
Cycle d'exécution 300 s (configurable CYCLE_INTERVAL_SEC)
Contamination 0.1 % (ISOLATION_CONTAMINATION=0.001)
Seuil d'anomalie Adaptatif : min(percentile_5(scores_négatifs), -0.05)
Détection navigateur 5 axes pondérés, seuil 0.55
Explainabilité SHAP top-5 features par anomalie
Clustering HDBSCAN min_cluster_size=3 + escalade campagne

2. Installation et configuration

2.1 Dépendances

Le service est construit via Docker (Dockerfile). Dépendances principales :

  • Python 3.11+
  • clickhouse-connect — client ClickHouse
  • pandas, numpy — manipulation de données
  • scikit-learn — IsolationForest (fallback), StandardScaler, DBSCAN
  • structlog — journalisation JSON
  • joblib — sérialisation des modèles EIF

Dépendances optionnelles (imports conditionnels dans config.py) :

Paquet Variable de disponibilité Rôle
isotree EIF_AVAILABLE Extended Isolation Forest (ndim>1)
torch TORCH_AVAILABLE Autoencoder (PyTorch)
xgboost XGB_AVAILABLE Modèle supervisé XGBoost
shap SHAP_AVAILABLE Explainabilité SHAP
hdbscan HDBSCAN_AVAILABLE Clustering hiérarchique

Si un paquet optionnel est absent, le service tourne en mode dégradé (fallback sklearn pour EIF, pas d'AE, pas de XGBoost, DBSCAN au lieu de HDBSCAN, pas de SHAP).

2.2 Variables d'environnement

Toutes lues via os.getenv() dans config.py (pas de pydantic-settings).

Connexion ClickHouse

Variable Défaut Description
CLICKHOUSE_HOST clickhouse Hôte ClickHouse
CLICKHOUSE_PORT 8123 Port HTTP
CLICKHOUSE_USER default Utilisateur
CLICKHOUSE_PASSWORD (vide) Mot de passe
CLICKHOUSE_DB_PROCESSING / CLICKHOUSE_DB ja4_processing Base de traitement (tables ML, vues, agrégations)
CLICKHOUSE_DB_LOGS ja4_logs Base des logs bruts

Cycle et opération

Variable Défaut Description
CYCLE_INTERVAL_SEC 300 Intervalle entre cycles (secondes)
MAX_CONSECUTIVE_FAILURES 3 Échecs ClickHouse consécutifs avant DEGRADED
HEALTH_PORT 8080 Port du health check HTTP
BOT_DETECTOR_LOG /var/log/bot_detector/decisions.jsonl Fichier de journal
LOG_BACKUP_COUNT 7 Nombre de rotations conservées

Modèles ML

Variable Défaut Description
MODEL_DIR /var/lib/bot_detector Répertoire de persistance des modèles
MODEL_HISTORY_COUNT 10 Versions conservées par modèle
N_ESTIMATORS 300 Nombre d'arbres EIF/IF
ISOLATION_CONTAMINATION 0.001 Fraction d'anomalies attendues (0 < x < 0.5)
RETRAIN_INTERVAL_HOURS 24 Fréquence de ré-entraînement EIF
ANOMALY_THRESHOLD -0.05 Seuil statique de score pour insertion
ANOMALY_PERCENTILE 5 Percentile pour le seuil adaptatif (020)
DRIFT_THRESHOLD 0.30 Fraction de features en dérive déclenchant un retrain
MIN_VALID_FEATURE_RATIO 0.50 Ratio minimum de features valides (sinon cycle ignoré)

Autoencoder

Variable Défaut Description
AE_WEIGHT 0.30 Poids de l'AE dans la combinaison EIF+AE (α)
AE_EPOCHS 50 Nombre d'époques d'entraînement
AE_LATENT_DIM 16 Dimension de l'espace latent
AE_LEARNING_RATE 1e-3 Taux d'apprentissage Adam

XGBoost

Variable Défaut Description
XGB_WEIGHT 0.20 Poids de XGBoost dans le score final (β)
XGB_MIN_LABELS 100 Nombre minimum de labels SOC pour entraîner
XGB_RETRAIN_INTERVAL_HOURS 168 Intervalle de ré-entraînement (7 jours)

Détection navigateur

Variable Défaut Description
BROWSER_CONFIDENCE_THRESHOLD 0.55 Seuil de confiance pour LEGITIMATE_BROWSER
BROWSER_COHORT_RATIO 0.70 Ratio de cohorte pour la propagation par JA4

Fonctionnalités optionnelles

Variable Défaut Description
ENABLE_SHAP true Active le calcul SHAP (ET shap installé)
ENABLE_CLUSTERING true Active le clustering HDBSCAN/DBSCAN des anomalies
CLUSTERING_MIN_SAMPLES 3 Taille minimale d'un cluster
ENABLE_MULTIWINDOW false Active l'analyse sur fenêtre 24h
MULTIWINDOW_VIEW view_ai_features_24h Nom de la vue 24h dans ClickHouse
ENABLE_FEEDBACK true Active l'intégration du feedback SOC
FEEDBACK_WINDOW_DAYS 7 Fenêtre de feedback SOC (jours)
DEDUP_TTL_MIN 60 TTL de déduplication inter-cycles (0 = désactivé)
RECURRENCE_WEIGHT 0.005 Pénalité de score par log1p(récurrence)

2.3 Docker

# Construction de l'image de production
docker build -f services/bot-detector/bot_detector/Dockerfile -t bot-detector .

# Tests
docker build -f services/bot-detector/bot_detector/Dockerfile.tests -t bd-tests .
docker run --rm bd-tests

# Ou via le Makefile racine
make test-bot-detector

2.4 Systemd / Docker Compose

Le service tourne en boucle infinie. En Docker Compose, il est configuré avec restart: unless-stopped. Le health check est exposé sur le port HEALTH_PORT (8080).


3. Modules

3.1 config.py — Configuration et imports optionnels

Rôle : Centralise toutes les variables d'environnement et gère les imports conditionnels des dépendances optionnelles.

Mécanisme d'imports optionnels :

try:
    from isotree import IsolationForest as ExtendedIsolationForest
    EIF_AVAILABLE = True
except ImportError:
    EIF_AVAILABLE = False

Ce patron est répété pour torch, xgboost, shap, et hdbscan. Les variables *_AVAILABLE sont importées par les autres modules pour choisir les algorithmes.

Validation des paramètres : Les valeurs numériques à plage contrainte (CONTAMINATION, DRIFT_THRESHOLD, AE_WEIGHT, XGB_WEIGHT, BROWSER_CONFIDENCE_THRESHOLD, etc.) sont validées à l'import avec des bornes (0, 1) ou (0, 0.5) selon le paramètre.

Exclusions structurelles (STRUCTURAL_EXCLUDED_FEATURES) : Dictionnaire définissant les features exclues par modèle. Le modèle Applicatif exclut 15 features TCP/TLS non disponibles sans corrélation (ex. is_rare_ja4, tcp_shared_count, ja3_diversity_ratio, tls12_ratio, etc.).


3.2 log.py — Journalisation structurée

Rôle : Fournit la journalisation JSON via structlog.

Fonctions exportées :

Fonction Description
log_info(msg, **kw) Message informatif (console + structlog)
log_decision(event, cycle_id, model, data) Événement décisionnel (fichier JSONL rotatif)

Le fichier JSONL est configuré en rotation (50 Mo × LOG_BACKUP_COUNT fichiers). Chaque événement contient : timestamp, event, cycle_id, model_name, et les données spécifiques à l'événement.

Événements journalisés :

Événement Déclencheur
SERVICE_START Démarrage du service
SERVICE_STOP Arrêt propre (SIGTERM/SIGINT)
CYCLE_START Début d'un cycle d'analyse
CYCLE_END Fin du cycle (résumé des insertions)
MODEL_LOADED Réutilisation d'un modèle existant
MODEL_TRAINED Nouvel entraînement
DRIFT_DETECTED Dérive conceptuelle → retrain forcé
FEATURE_WARNING Features manquantes/constantes détectées
KNOWN_BOT Bot connu identifié par réputation
ANOMALY Anomalie ML détectée (score, SHAP, campaign_id)
ANUBIS_DENY IP bloquée par Anubis
LEGITIMATE_BROWSER Navigateur légitime confirmé (5 axes)
SKIPPED_LOW_DATA Cycle ignoré (baseline < 500 sessions)
SKIPPED_INVALID_FEATURES Cycle ignoré (ratio features valides insuffisant)
CONSECUTIVE_FAILURES Erreur ClickHouse répétée

3.3 infra.py — Infrastructure

Rôle : Client ClickHouse, health check HTTP, gestion de l'arrêt propre.

Health check : Serveur HTTP en thread daemon sur HEALTH_PORT (8080).

GET /  →  200 OK       (service opérationnel)
GET /  →  503 DEGRADED (≥ MAX_CONSECUTIVE_FAILURES échecs consécutifs)

L'état est contrôlé via set_healthy(bool) / is_healthy() (thread-safe avec threading.Lock).

Arrêt propre : Gestionnaires de signaux SIGTERM et SIGINT journalisent SERVICE_STOP et terminent proprement via sys.exit(0).

Client ClickHouse : get_client() délègue à ja4_common.clickhouse.get_client(), qui gère le singleton de connexion et la reconnexion automatique.

Classification des menaces : score_to_threat_level(score) convertit le score brut IF en niveau de menace textuel :

Condition Niveau
score < -0.30 CRITICAL
score < -0.15 HIGH
score < -0.05 MEDIUM
score < 0 LOW
score ≥ 0 NORMAL

Note

: Les niveaux KNOWN_BOT, ANUBIS_DENY, ANUBIS_ALLOW et LEGITIMATE_BROWSER sont attribués en amont par pipeline.py, sans passer par cette fonction.


3.4 browser.py — Détection multifactorielle des navigateurs

Rôle : Évalue la probabilité qu'une session provienne d'un navigateur légitime via 5 axes pondérés indépendants.

Fonctions exportées :

Fonction Description
_compute_browser_axes(df) Calcule les 5 axes + browser_confidence pour chaque ligne
_parse_ja4_columns(ja4_series) Parse les champs structurels du fingerprint JA4
_infer_browser_family(df, ja4_parsed, axes) Infère la famille navigateur (Chromium, Firefox, Safari, Tor_Browser)

Axe 1 — JA4 Known (poids 0.25)

Recherche dans le dictionnaire dict_browser_ja4. Score binaire : 1.0 si la famille navigateur est identifiée, 0.0 sinon.

Axe 2 — JA4 Structure (poids 0.15)

Analyse structurelle du fingerprint JA4 :

Composant Poids Condition de score 1.0
TLS 1.3 0.35 Version TLS ≥ 1.3
Protocole h2/h3 0.25 ALPN = h2 ou h3
Cipher count 0.20 10 ≤ nombre de ciphers ≤ 25
Extension count 0.20 10 ≤ nombre d'extensions ≤ 25

Axe 3 — HTTP Modern (poids 0.25)

Composant Poids Condition de score 1.0
modern_browser_score ≥ 50 0.35 Score de conformité navigateur
has_accept_language 0.20 Présence du header Accept-Language
sec_fetch_absence_rate < 0.3 0.25 Headers Sec-Fetch présents
generic_accept_ratio < 0.3 0.10 Accept non générique (*/*)
ua_ch_mismatch = 0 0.10 Cohérence UA / Client Hints

Axe 4 — Navigation Behavior (poids 0.15)

Composant Poids Condition de score 1.0
has_cookie 0.25 Présence de cookies
has_referer 0.25 Présence du Referer
asset_ratio > 0.15 0.25 Chargement de ressources statiques
direct_access_ratio < 0.5 0.25 Navigation par liens (pas d'accès direct)

Axe 5 — TLS/TCP Coherence (poids 0.20)

Composant Poids Condition de score 1.0
alpn_http_mismatch = 0 0.25 Cohérence ALPN/HTTP
no_window_scale_ratio = 0 0.20 Window scaling TCP présent
tls12_ratio < 0.1 0.20 Peu de TLS 1.2
http10_ratio = 0 0.15 Pas de HTTP/1.0
is_alpn_missing = 0 0.20 ALPN présent dans le ClientHello

Score final et classification

browser_confidence = Σ (axe_i × poids_i)    pour i = 1..5

Condition LEGITIMATE_BROWSER :

  • browser_confidence ≥ BROWSER_CONFIDENCE_THRESHOLD (0.55) ET
  • famille navigateur identifiée (Chromium, Firefox, Safari, ou Tor_Browser)

Inférence de famille (_infer_browser_family) : Compare les colonnes structurelles du JA4 avec des profils prédéfinis par famille. Requiert browser_confidence ≥ 0.45 pour attribuer une famille.

Propagation par cohorte : Les sessions avec le même JA4 héritent de la classification si BROWSER_COHORT_RATIO (70%) des sessions de ce JA4 sont LEGITIMATE_BROWSER.


3.5 preprocessing.py — Prétraitement des données

Rôle : Nettoyage des DataFrames et définition des listes de features.

Listes de features :

Liste Nombre Usage
FEATURES 57 Modèle Applicatif (L7 pur, correlated=0)
FEATURES_COMPLET 68 Modèle Complet (FEATURES + 11 features TCP/TLS)

Note

: Le nombre effectif de features utilisées par chaque modèle est inférieur aux listes, car validate_features() exclut les features manquantes, constantes ou à variance nulle, et STRUCTURAL_EXCLUDED_FEATURES (dans config.py) exclut des features structurellement non pertinentes par modèle.

preprocess_df(df) — Pipeline de nettoyage :

  1. Nettoyage des noms de colonnes : supprime les préfixes de table (c.split('.')[-1])
  2. Remplissage des colonnes texte : fillna('') pour src_ip, ja4, host, bot_name, anubis_bot_name, anubis_bot_action, etc.
  3. Identification navigateur : calcule les 5 axes browser via _compute_browser_axes(), infère la famille navigateur, ajoute browser_confidence, is_known_browser, browser_consistency_score et les 5 colonnes axis_*
  4. Signal Anubis : calcule anubis_is_flagged (bot nommé par Anubis, action ni ALLOW ni DENY)
  5. Imputation intelligente :
    • Features binaires (has_cookie, is_ua_rotating, etc.) → fillna(-1) (sentinelle pour donnée manquante)
    • Features numériques → remplacement des ±inf par NaN, puis fillna(median)

3.6 models.py — Modèles ML

Rôle : Entraînement, chargement, prédiction et persistance des trois voix de l'ensemble ML.

Extended Isolation Forest (EIF)

Bibliothèque principale : isotree (fallback : sklearn.ensemble.IsolationForest)

# Paramètres isotree
ExtendedIsolationForest(
    ntrees=300,
    ndim=min(3, len(features)),   # partitions multi-dimensionnelles
    sample_size='auto',
    missing_action='impute',
    random_seed=42,
    nthreads=-1,
)

# Fallback sklearn
IsolationForest(
    n_estimators=300,
    contamination=CONTAMINATION,   # 0.001
    random_state=42,
    n_jobs=-1,
)

Calibration des scores isotree : Les scores isotree sont convertis pour être comparables à sklearn via sklearn_equiv = 0.5 - isotree_score.

Persistance : joblib.dump() / joblib.load() → fichier .joblib.

Autoencoder (TrafficAutoEncoder)

Bibliothèque : PyTorch (torch.nn.Module). Disponible uniquement si TORCH_AVAILABLE=True.

Architecture adaptative :

Encoder : input → dim1 → dim2 → latent_dim
Decoder : latent_dim → dim2 → dim1 → input

dim1 = min(64, max(n_features, latent_dim + 4))
dim2 = min(32, max(dim1 // 2, latent_dim + 2))
latent_dim = AE_LATENT_DIM (16 par défaut)

Chaque couche utilise BatchNorm1d + ReLU. La dernière couche du décodeur utilise Sigmoid. Perte : MSELoss. Optimiseur : Adam(lr=AE_LEARNING_RATE, weight_decay=1e-5). Entraînement sur AE_EPOCHS (50) époques, batch_size=256.

Score : L'erreur de reconstruction (MSE par échantillon) sert de score d'anomalie.

Persistance : torch.save(state_dict()) → fichier .pt.

XGBoost (supervisé)

Bibliothèque : xgboost.XGBClassifier. Disponible uniquement si XGB_AVAILABLE=True.

XGBClassifier(
    n_estimators=200,
    max_depth=6,
    learning_rate=0.1,
    scale_pos_weight=<dynamique>,   # ratio négatifs/positifs
    eval_metric='logloss',
    random_state=42,
    n_jobs=-1,
    tree_method='hist',
)

Données d'entraînement : Labels issus du feedback SOC (audit_logs). Les faux positifs (FP) servent d'exemples négatifs, les vrais positifs (TP) d'exemples positifs. Requiert ≥ XGB_MIN_LABELS (100) labels avant activation.

Ré-entraînement : Toutes les XGB_RETRAIN_INTERVAL_HOURS (168h = 7 jours).

Persistance : model.save_model() → fichier .json.

Cycle de vie des modèles

Démarrage cycle
      │
      ▼
Existe un .current ?  ──NON──► Entraîner nouveau modèle
      │
     OUI
      │
      ▼
Âge < RETRAIN_INTERVAL ?
      │                  │
     OUI                NON
      │                  │
      ▼                  └──► Entraîner nouveau modèle
Drift check (scoring.py)
      │
Drift ≥ DRIFT_THRESHOLD ?
      │              │
     NON            OUI
      │              │
Charger modèle  Entraîner nouveau modèle

Versioning : model_{name}_{YYYYMMDD_HHMMSS}.{joblib|pt|json} + .meta.json (features, contamination, nb_samples, baseline_stats). Pointeur atomique : model_{name}.current. Historique limité à MODEL_HISTORY_COUNT versions.

Validation gate : Un modèle est rejeté si val_anomaly_rate > 0.20 sur le jeu de validation (split 80/20).

Feature pruning : Les features avec variance < 1e-6 sont éliminées avant entraînement.


3.7 scoring.py — Scoring et post-traitement

Rôle : Validation des features, seuil adaptatif, normalisation, explainabilité SHAP, clustering HDBSCAN, et détection de dérive conceptuelle.

validate_features(df, features, name, cycle_id)

Filtre les features avant entraînement et scoring :

  • Exclut les features absentes du DataFrame
  • Exclut les features constantes (std = 0, non discriminantes)
  • Exclut les features entièrement à zéro (pipeline non alimenté)
  • Retourne None si le ratio de features valides < MIN_VALID_FEATURE_RATIO (0.50), ce qui fait ignorer le cycle

compute_adaptive_threshold(scores)

Calcule un seuil d'anomalie dynamique basé sur la distribution courante :

neg_scores = scores[scores < 0]
adaptive = np.percentile(neg_scores, ANOMALY_PERCENTILE)   # défaut : 5e percentile
effective_threshold = min(adaptive, ANOMALY_THRESHOLD)      # garde-fou : -0.05

Le seuil ne peut pas remonter au-dessus du seuil statique mais s'adapte vers le bas.

normalize_scores(scores)

Normalisation min-max des scores négatifs sur l'intervalle [0, 1], où 1 = le plus anormal. Les scores positifs (normaux) ne sont pas normalisés.

_compute_shap_top_features(model, X, features, n_top=5)

Calcul des contributions SHAP par anomalie :

  • isotree : shap.PermutationExplainer
  • sklearn : shap.TreeExplainer
  • Retourne les top-5 features les plus contributives par ligne

Activé uniquement si ENABLE_SHAP=true ET SHAP_AVAILABLE=True.

_cluster_anomalies(anomalies, features, ae_model=None)

Clustering des anomalies pour identifier les campagnes coordonnées :

  • HDBSCAN (si disponible) : min_cluster_size=CLUSTERING_MIN_SAMPLES (3), min_samples=max(2, CLUSTERING_MIN_SAMPLES - 1), cluster_selection_method='eom'
  • DBSCAN (fallback) : eps=0.5, min_samples=CLUSTERING_MIN_SAMPLES
  • Si un modèle AE est disponible, le clustering opère dans l'espace latent de l'autoencoder (dimension 16) plutôt que sur les features brutes

Résultat : colonne campaign_id (-1 = isolé, ≥0 = membre d'un cluster).

_compute_drift_score(baseline_stats, current_baseline, features)

Détection de dérive conceptuelle entre la baseline d'entraînement et la baseline courante. Deux méthodes :

  1. Méthode principale : Comparaison par quantiles interpolés (KS-like). Fraction de features avec p < 0.05.
  2. Fallback z-score (_compute_drift_score_zscore) : z = |mean_current - mean_trained| / std_trained. Fraction de features avec z > 2.0.

Si la fraction dépasse DRIFT_THRESHOLD (0.30), le modèle est ré-entraîné et l'événement DRIFT_DETECTED est journalisé.


3.8 pipeline.py — Orchestrateur semi-supervisé

Rôle : Orchestre le flux ML complet via run_semi_supervised_logic().

Signature :

def run_semi_supervised_logic(df, features, name, cycle_id, recurrence_map) -> (threats, all_scored)

Étapes détaillées (13 phases) :

  1. Triage initial : Sépare le DataFrame en trois groupes :

    • known_bots : IPs avec un bot_name renseigné (dictionnaires de réputation)
    • anubis_allow : IPs avec anubis_bot_action='ALLOW'
    • unknown_traffic : tout le reste
    • Baseline humaine : unknown_traffic avec asn_label='isp'
  2. Validation des features : validate_features() exclut les features invalides. Retour anticipé si ratio < MIN_VALID_FEATURE_RATIO.

  3. Vérification de la baseline : Minimum 500 sessions humaines requis. Sinon SKIPPED_LOW_DATA.

  4. EIF : entraînement ou chargement : load_or_train_model() avec détection de dérive intégrée. Entraîné uniquement sur la baseline humaine.

  5. EIF : scoring : decision_function() sur tout le trafic inconnu. Pour isotree : score_equiv = 0.5 - isotree_score.

  6. Autoencoder : entraînement et scoring : Erreur de reconstruction MSE normalisée.

  7. Combinaison EIF + AE :

    combined = (1 - α) × eif_norm + α × ae_norm
    

    α = AE_WEIGHT = 0.30. Soit : 70% EIF + 30% AE.

  8. XGBoost : prédiction supervisée : predict_proba() → probabilité de malveillance.

  9. Fusion EIF+AE+XGB :

    final = (1 - β) × combined + β × xgb_prob
    

    β = XGB_WEIGHT = 0.20. Le score final se décompose en : 56% EIF + 24% AE + 20% XGBoost.

  10. Seuil adaptatif : compute_adaptive_threshold() sur les scores bruts EIF.

  11. Pénalité de récurrence :

    raw_anomaly_score -= log1p(recurrence_count) × RECURRENCE_WEIGHT
    
  12. Classification des menaces :

    • score_to_threat_level() pour le score brut
    • Override ANUBIS_DENY pour les IPs Anubis deny
    • Classification LEGITIMATE_BROWSER si browser_confidence ≥ 0.55 + famille identifiée + threat_level ∈ {NORMAL, LOW} + action ≠ DENY
  13. Post-traitement des anomalies (score brut < seuil adaptatif) :

    • SHAP : explication top-5 features → champ reason
    • HDBSCAN : clustering → campaign_id
    • Escalade campagne : si un cluster contient ≥ 5 membres, les IPs HIGH sont escaladées en CRITICAL

Labeling bots connus et Anubis : Les known_bots reçoivent threat_level='KNOWN_BOT', anomaly_score=0.0. Les anubis_allow reçoivent threat_level='KNOWN_BOT'. Les IPs Anubis deny sont forcées à ANUBIS_DENY.

Retour : (threats, all_scored) — respectivement les anomalies/bots à insérer dans ml_detected_anomalies et tous les scores pour ml_all_scores.


3.9 cycle.py — Cycle principal de détection

Rôle : Orchestre l'acquisition de données, l'appel au pipeline ML, et l'insertion des résultats dans ClickHouse.

fetch_and_analyze() — Flux complet :

  1. Connexion ClickHouse via get_client()
  2. Requête {DB}.view_ai_features_1h → DataFrame principal
  3. Enrichissement avec les features thèse §5 : {DB}.view_thesis_features_1h (jointure sur src_ip)
  4. preprocess_df(df) — nettoyage, imputation, détection navigateur
  5. Chargement de la carte de récurrence : {DB}.view_ip_recurrence
  6. Chargement du feedback SOC : {DB}.audit_logs (si ENABLE_FEEDBACK=true)
  7. Application du feedback : FP → asn_label='isp', TP → asn_label='soc_confirmed_bot'
  8. Exécution du modèle Complet (correlated=1, FEATURES_COMPLET)
  9. Exécution du modèle Applicatif (correlated=0, FEATURES)
  10. Multi-fenêtres (si ENABLE_MULTIWINDOW=true) :
    • Requête {DB}.{MULTIWINDOW_VIEW} (défaut view_ai_features_24h)
    • Exécution des modèles Complet_24h et Applicatif_24h
    • Fusion OR : une IP est flaggée si anomalie dans ≥ 1 fenêtre (score le plus bas conservé)
  11. Insertion ml_all_scores : Toutes les sessions scorées
  12. Déduplication intra-cycle : drop_duplicates(subset=['src_ip'], keep='first') en gardant le score le plus bas
  13. Déduplication inter-cycles (_filter_recent_detections) : Interroge ml_detected_anomalies pour les IPs insérées dans les dernières DEDUP_TTL_MIN minutes. Réinsertion uniquement si le score s'est dégradé de ≥ 0.05
  14. Insertion ml_detected_anomalies : Anomalies et bots connus filtrés

Gestion des erreurs : Compteur _consecutive_failures. Après MAX_CONSECUTIVE_FAILURES (3) échecs, set_healthy(False) → health check renvoie 503.

Tables d'insertion :

Table Contenu
{DB}.ml_all_scores Toutes les sessions scorées (anomalies + normales + bots)
{DB}.ml_detected_anomalies Anomalies confirmées + bots connus uniquement

3.10 __main__.py — Point d'entrée

Rôle : Point d'entrée du service. Pas d'argparse — le comportement est entièrement contrôlé par les variables d'environnement.

Flux :

  1. Import de toute la configuration via from .config import *
  2. Affichage d'un bandeau de démarrage avec toutes les valeurs de configuration
  3. Journalisation de l'événement SERVICE_START
  4. Boucle infinie :
    • fetch_and_analyze()
    • time.sleep(CYCLE_INTERVAL_SEC)
    • Les exceptions sont capturées, journalisées, et la boucle continue

Lancement :

python -m bot_detector

4. Features ML

4.1 Vue d'ensemble

Le service utilise 7 familles de features totalisant ~68 features uniques. Le modèle Complet utilise les 68, le modèle Applicatif en utilise ~57 (sans les features TCP/TLS).

4.2 Famille 1 — Volume et débit

Feature Description Modèle
hits Nombre total de requêtes sur la fenêtre Les deux
hit_velocity Requêtes par seconde Les deux
fuzzing_index Score de diversité anormale des chemins/paramètres Les deux
post_ratio Fraction de requêtes POST Les deux
port_exhaustion_ratio Fraction de ports sources distincts / total Les deux
orphan_ratio Requêtes sans réponse associée Les deux
max_keepalives Max requêtes sur une connexion keep-alive Les deux
tcp_shared_count Connexions TCP partagées entre sessions HTTP Les deux
head_ratio Fraction de requêtes HEAD (B4) Les deux
burst_ratio Ratio de requêtes en rafale (thèse §5) Les deux
pause_ratio Ratio de pauses entre rafales (thèse §5) Les deux

4.3 Famille 2 — Chemins et contenu

Feature Description Modèle
path_diversity_ratio Diversité des chemins URL accédés Les deux
url_depth_variance Variance de la profondeur des URL Les deux
anomalous_payload_ratio Fraction de payloads avec patterns anormaux Les deux
path_transition_entropy Entropie des transitions entre chemins (thèse §5) Les deux
login_post_concentration Concentration de POST sur les pages de login (P1) Les deux
unusual_content_type_ratio Ratio de Content-Types inhabituels (P1) Les deux
non_standard_port_ratio Ratio de ports non standard (P1) Les deux

4.4 Famille 3 — Headers et protocole

Feature Description Modèle
header_count Nombre d'en-têtes HTTP envoyés Les deux
header_order_shared_count Partage d'un même ordre d'en-têtes entre IPs Les deux
header_order_confidence Confiance dans l'ordre d'en-têtes Les deux
distinct_header_orders Nombre d'ordres d'en-têtes distincts Les deux
has_accept_language Présence de Accept-Language Les deux
modern_browser_score Score composite de conformité navigateur Les deux
ua_ch_mismatch Incohérence User-Agent / Client Hints Les deux
sec_fetch_absence_rate Absence des headers Sec-Fetch (B5) Les deux
generic_accept_ratio Ratio de headers Accept génériques (B6) Les deux
http10_ratio Ratio de requêtes HTTP/1.0 (B7) Les deux
missing_accept_enc_ratio Ratio de requêtes sans Accept-Encoding Les deux
http_scheme_ratio Ratio de schémas HTTP (vs HTTPS) Les deux
sec_ch_mobile_mismatch Incohérence Sec-CH-UA-Mobile (P1) Les deux
has_xff Présence de X-Forwarded-For (P1) Les deux

4.5 Famille 4 — Session et navigation

Feature Description Modèle
has_cookie Présence de cookies Les deux
has_referer Présence du Referer Les deux
asset_ratio Fraction de requêtes vers des ressources statiques Les deux
direct_access_ratio Fraction d'accès directs (sans Referer) Les deux
is_ua_rotating Rotation de User-Agent détectée Les deux
multiplexing_efficiency Efficacité du multiplexage HTTP/2 Les deux
request_size_variance Variance de la taille des requêtes Les deux
is_fake_navigation Fausse navigation détectée (P0) Les deux
host_diversity Diversité des hosts accédés (thèse §5) Les deux
host_sweep_speed Vitesse de balayage des hosts (thèse §5) Les deux
host_coverage_uniformity Uniformité de couverture des hosts (thèse §5) Les deux

4.6 Famille 5 — TLS et réseau

Feature Description Modèle
distinct_ja4_count Fingerprints JA4 distincts par IP Les deux
ja4_asn_concentration Concentration d'un même JA4 dans un ASN Les deux
ja4_country_concentration Concentration d'un même JA4 par pays Les deux
is_rare_ja4 JA4 peu commun dans la population Les deux
ip_id_zero_ratio Ratio de paquets IP avec ID=0 Les deux
mss_mobile_mismatch Incohérence MSS TCP / profil mobile Les deux
tcp_jitter_variance Variance de la gigue inter-paquets TCP Complet
alpn_http_mismatch Incohérence ALPN négocié / protocole HTTP Complet
is_alpn_missing ALPN absent dans le ClientHello Complet
sni_host_mismatch Incohérence SNI TLS / Host HTTP Complet
ja3_diversity_ratio Ratio JA3/JA4 — rotation de fingerprint (B1) Complet
syn_timing_cv Coefficient de variation du timing SYN→ClientHello (B2) Complet
tls12_ratio Ratio de requêtes exclusivement TLS 1.2 (B3) Complet
ip_df_variance Variance du bit DF (Don't Fragment) (B8) Complet
avg_ttl TTL moyen (fingerprinting OS) Complet
ttl_std Écart-type du TTL Complet
no_window_scale_ratio Ratio de sessions sans window scaling TCP Complet
ja4_drift_ratio Dérive JA4 intra-session (thèse §5.5) Complet
true_window_size Taille réelle de la fenêtre TCP (P0) Complet
window_mss_ratio Ratio fenêtre TCP / MSS (P0) Complet

4.7 Famille 6 — Intelligence et réputation

Feature Description Modèle
anubis_is_flagged Signalé par Anubis (action ≠ ALLOW/DENY) Les deux
is_known_browser Browser identifié (axe 1 rétro-compat) Les deux
browser_consistency_score Score de cohérence navigateur (rétro-compat) Les deux
browser_confidence Confiance navigateur (5 axes) Les deux
src_port_density Densité des ports sources (entropie) Les deux

4.8 Famille 7 — Thèse §5 et temporel

Feature Description Modèle
temporal_entropy Entropie de Shannon de la distribution temporelle Les deux
cadence_cv Coefficient de variation de la cadence (thèse §5) Les deux
lag1_autocorrelation Autocorrélation lag-1 des inter-arrivées (thèse §5) Les deux
benford_deviation Déviation par rapport à la loi de Benford (thèse §5) Les deux

4.9 Axes de détection navigateur (utilisés comme features)

Feature Description Modèle
axis_ja4_known Score de l'axe 1 (JA4 connu) Les deux
axis_ja4_struct Score de l'axe 2 (structure JA4) Les deux
axis_http_modern Score de l'axe 3 (HTTP moderne) Les deux
axis_nav_behavior Score de l'axe 4 (comportement navigation) Les deux
axis_tls_coherence Score de l'axe 5 (cohérence TLS/TCP) Les deux

5. Classification des menaces

5.1 Arbre de décision complet

La classification des menaces suit un ordre de priorité strict, appliqué dans pipeline.py :

1. anubis_bot_action = 'DENY'
   └──► ANUBIS_DENY (override systématique)

2. bot_name != '' (dictionnaires de réputation IP/JA4)
   └──► KNOWN_BOT

3. anubis_bot_action = 'ALLOW'
   └──► KNOWN_BOT (bot identifié mais autorisé)

4. browser_confidence ≥ 0.55 AND famille identifiée AND threat ∈ {NORMAL, LOW}
   └──► LEGITIMATE_BROWSER

5. Score brut IsolationForest < -0.30
   └──► CRITICAL

6. Score brut < -0.15
   └──► HIGH

7. Score brut < -0.05
   └──► MEDIUM

8. Score brut < 0
   └──► LOW

9. Sinon
   └──► NORMAL

5.2 Escalade par campagne

Après le clustering HDBSCAN, les IPs appartenant à un cluster de ≥ 5 membres voient leur threat level escaladé de HIGH à CRITICAL. Cette logique détecte les campagnes de botnet coordonnées qui individuellement ne seraient que HIGH.

5.3 Niveaux de menace

Niveau Score brut Interprétation
ANUBIS_DENY Bloqué par la WAF Anubis
KNOWN_BOT 0.0 Bot identifié par réputation
LEGITIMATE_BROWSER Navigateur humain confirmé (5 axes)
CRITICAL < -0.30 Comportement extrêmement anormal
HIGH < -0.15 Fort signal d'anomalie
MEDIUM < -0.05 Anomalie modérée
LOW < 0 Légèrement inhabituel
NORMAL ≥ 0 Trafic normal

6. Tests

6.1 Organisation

Les tests sont dans bot_detector/tests/test_detector.py (36 tests). Ils suivent un patron auto-contenu : chaque test ré-implémente la logique clé plutôt que d'importer directement depuis le module principal. Cela évite les chaînes d'imports lourdes (joblib, sklearn, torch, xgboost).

6.2 Patron des tests

  • Autoencoder : Helper local _make_ae() crée un modèle minimal en mémoire
  • XGBoost : Modèles créés in-memory pour chaque test
  • EIF/IF : Tests de scoring sur des données synthétiques
  • Dépendances optionnelles : pytest.skip() si torch ou xgboost non installés

6.3 Exécution

# Via Docker (recommandé)
make test-bot-detector

# Ou directement
docker build -f services/bot-detector/bot_detector/Dockerfile.tests -t bd-tests .
docker run --rm bd-tests

# En local (nécessite les dépendances)
cd services/bot-detector
pip install -r bot_detector/requirements.txt pytest pytest-mock
pytest bot_detector/tests/test_detector.py -v

# Un test spécifique
pytest bot_detector/tests/test_detector.py -v -k "test_benford"

6.4 Couverture des tests

Les 36 tests couvrent :

  • Calcul des scores de menace (score_to_threat_level)
  • Validation des features (manquantes, constantes, zéro)
  • Seuil adaptatif par percentile
  • Normalisation des scores
  • Entraînement et scoring de l'autoencoder
  • Prédiction XGBoost
  • Détection de dérive conceptuelle
  • Clustering HDBSCAN/DBSCAN
  • Détection navigateur (5 axes)
  • Déviation de Benford, autocorrélation lag-1
  • Prétraitement des données

7. Diagnostic

7.1 Requêtes ClickHouse utiles

-- Dernières anomalies CRITICAL
SELECT detected_at, src_ip, ja4, threat_level, anomaly_score, reason
FROM ja4_processing.ml_detected_anomalies
WHERE threat_level = 'CRITICAL'
ORDER BY detected_at DESC
LIMIT 20;

-- Distribution des threat levels sur la dernière heure
SELECT threat_level, count() AS cnt
FROM ja4_processing.ml_detected_anomalies
WHERE detected_at > now() - INTERVAL 1 HOUR
GROUP BY threat_level
ORDER BY cnt DESC;

-- Campagnes coordonnées (HDBSCAN clusters)
SELECT campaign_id, count() AS members, groupArray(src_ip) AS ips
FROM ja4_processing.ml_detected_anomalies
WHERE campaign_id >= 0 AND detected_at > now() - INTERVAL 1 HOUR
GROUP BY campaign_id
ORDER BY members DESC;

-- Scores moyens par modèle (dernière heure)
SELECT model_name, avg(anomaly_score) AS avg_score, count() AS total
FROM ja4_processing.ml_all_scores
WHERE window_start > now() - INTERVAL 1 HOUR
GROUP BY model_name;

-- Navigateurs légitimes détectés
SELECT src_ip, ja4, inferred_browser_family, browser_confidence
FROM ja4_processing.ml_all_scores
WHERE threat_level = 'LEGITIMATE_BROWSER' AND window_start > now() - INTERVAL 1 HOUR
ORDER BY browser_confidence DESC
LIMIT 20;

-- Volume de la baseline humaine (santé du pipeline)
SELECT count() AS human_sessions
FROM ja4_processing.view_ai_features_1h
WHERE asn_label = 'isp' AND bot_name = '';

7.2 Commandes jq pour les logs JSONL

# Anomalies CRITICAL récentes
jq 'select(.event=="ANOMALY" and .threat_level=="CRITICAL")' decisions.jsonl

# Top features SHAP des anomalies HIGH
jq 'select(.event=="ANOMALY" and .threat_level=="HIGH") | .reason' decisions.jsonl

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

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

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

# Résumé des cycles (performances)
jq 'select(.event=="CYCLE_END")' decisions.jsonl

# Navigateurs légitimes confirmés
jq 'select(.event=="LEGITIMATE_BROWSER")' decisions.jsonl

7.3 Problèmes courants

Symptôme Cause probable Solution
SKIPPED_LOW_DATA à chaque cycle Baseline humaine < 500 sessions Vérifier que view_ai_features_1h retourne des lignes avec asn_label='isp'
SKIPPED_INVALID_FEATURES > 50% des features constantes/absentes Vérifier le schéma de view_ai_features_1h — colonnes manquantes ou agrégations NULL
CONSECUTIVE_FAILURES ClickHouse inaccessible Vérifier CLICKHOUSE_HOST/PORT, réseau Docker, état du service ClickHouse
Health check 503 ≥ 3 échecs consécutifs Consulter les logs pour l'erreur sous-jacente
Pas d'anomalies détectées Seuil trop strict ou features constantes Vérifier ANOMALY_THRESHOLD, inspecter la distribution des scores dans ml_all_scores
SHAP non disponible Paquet shap non installé Installer shap ou vérifier ENABLE_SHAP=true
Autoencoder désactivé torch non installé Installer torch — le service tourne sans AE mais avec moins de précision
Trop de LEGITIMATE_BROWSER Seuil navigateur trop bas Augmenter BROWSER_CONFIDENCE_THRESHOLD (défaut 0.55)
FEATURE_WARNING fréquents Colonnes non alimentées dans la vue Voir CLICKHOUSE_FEATURES_DIAGNOSTIC.md

7.4 Vérification de l'état du service

# Health check
curl -s http://localhost:8080/
# → OK ou DEGRADED

# Vérifier les logs Docker
docker logs bot-detector --tail 50

# Vérifier la configuration active (première ligne du démarrage)
docker logs bot-detector 2>&1 | head -30