# Bot Detector Service Python de détection d'anomalies par apprentissage automatique semi-supervisé sur le trafic HTTP/TLS agrégé dans ClickHouse. Fonctionne en cycle continu (par défaut toutes les 5 minutes) avec un **ensemble à triple voix** (Extended Isolation Forest + Autoencoder + XGBoost) piloté par un **méta-learner à régression logistique**, enrichi par l'explicabilité **ExIFFI** et **SHAP**, le clustering HDBSCAN, la détection de flottes coordonnées (NetworkX) et la surveillance de performance par cycle. --- ## Architecture des modules Le service est découpé en **16 modules** organisés ainsi : ``` __main__.py Point d'entrée (python -m bot_detector) └─ cycle.py Boucle principale : requête ClickHouse → pipeline → insertion ├─ config.py Variables d'environnement, flags de disponibilité ├─ log.py Journalisation structurée JSON (structlog + RotatingFileHandler) ├─ infra.py Client ClickHouse (via ja4_common), health check HTTP, arrêt propre ├─ preprocessing.py Nettoyage du DataFrame, imputation, listes de features (FEATURES, FEATURES_COMPLET) │ └─ browser.py Identification multifactorielle des navigateurs (6 axes) ├─ pipeline.py Orchestration : filtrage → entraînement → MetaLearner → ExIFFI → scoring → fusion │ ├─ models.py EIF, TrafficAutoEncoder (PyTorch), XGBoost │ └─ scoring.py Normalisation, MetaLearner, seuil adaptatif, ExIFFI, SHAP, HDBSCAN, dérive KS+KL ├─ browser_matcher.py Scoring H2 statique à 7 dimensions pondérées │ └─ browser_signatures.py Signatures statiques Chrome/Firefox/Safari + rechargement ClickHouse ├─ browser_matcher_dynamic.py Scoring H2 dynamique temps réel (profils auto-appris) ├─ profile_builder.py Profiling HDBSCAN hors-ligne, centroïdes, lifecycle (cron quotidien) ├─ fleet.py Graphe bipartite JA4×ASN (NetworkX), fleet_score, fleet_detections ├─ metrics.py Métriques de cycle, alertes, ml_performance_metrics └─ (insère dans ml_all_scores + ml_detected_anomalies + fleet_detections + ml_performance_metrics) ``` | Module | Lignes | Rôle | |--------|--------|------| | `config.py` | 154 | Toute la configuration via `os.getenv()`, flags de disponibilité des librairies | | `log.py` | 65 | `log_info()`, `log_decision()`, `append_training_history()` — JSONL rotatif | | `infra.py` | 89 | Client ClickHouse (délègue à `ja4_common`), `score_to_threat_level()`, serveur de santé en thread daemon | | `browser.py` | 191 | Détection multifactorielle des navigateurs sur **6 axes** pondérés (ajout `axis_h2_coherence`) | | `browser_matcher.py` | 498 | Scoring H2 statique à 7 dimensions pondérées (SETTINGS, WINDOW_UPDATE, pseudo-order, etc.) | | `browser_signatures.py` | 166 | Signatures statiques Chrome/Firefox/Safari + rechargement dynamique depuis ClickHouse | | `browser_matcher_dynamic.py` | 387 | Scoring H2 dynamique temps réel contre profils auto-appris (`auto_browser_profiles`) | | `profile_builder.py` | 614 | Profiling HDBSCAN hors-ligne : clustering, centroïdes, fusion, lifecycle (cron quotidien) | | `scoring.py` | 564 | `MetaLearner` (régression logistique), normalisation, seuil adaptatif, ExIFFI, SHAP top-5, HDBSCAN, dérive KS+KL | | `models.py` | 484 | `TrafficAutoEncoder`, entraînement/chargement EIF, XGBoost, élagage de features | | `preprocessing.py` | 127 | `preprocess_df()` — nettoyage, typage, imputation, listes `FEATURES` / `FEATURES_COMPLET` | | `pipeline.py` | 441 | `run_semi_supervised_logic()` — orchestration complète d'un modèle, MetaLearner, ExIFFI | | `fleet.py` | 174 | `build_fleet_graph()`, `detect_fleet_communities()`, `enrich_with_fleet_score()` — NetworkX + HDBSCAN | | `metrics.py` | 166 | `record_cycle_metrics()`, `_emit_alerts()` — table `ml_performance_metrics` | | `cycle.py` | 415 | `fetch_and_analyze()` — boucle principale, feedback SOC, multiwindow | | `__main__.py` | 41 | Point d'entrée, bannière de démarrage, boucle `while True` | --- ## Configuration Toute la configuration est lue via `os.getenv()` dans `config.py`. Aucun fichier YAML ni pydantic-settings. ### Connexion ClickHouse | Variable | Type | Défaut | Description | |----------|------|--------|-------------| | `CLICKHOUSE_HOST` | str | `clickhouse` | Nom d'hôte du serveur ClickHouse (via `ja4_common`) | | `CLICKHOUSE_PORT` | int | `8123` | Port HTTP ClickHouse (via `ja4_common`) | | `CLICKHOUSE_USER` | str | `admin` | Utilisateur ClickHouse (via `ja4_common`) | | `CLICKHOUSE_PASSWORD` | str | `""` | Mot de passe ClickHouse (via `ja4_common`) | | `CLICKHOUSE_DB_PROCESSING` | str | `ja4_processing` | Base de données ML/agrégations (fallback : `CLICKHOUSE_DB`) | | `CLICKHOUSE_DB_LOGS` | str | `ja4_logs` | Base de données des logs HTTP | ### Modèle et entraînement | Variable | Type | Défaut | Description | |----------|------|--------|-------------| | `N_ESTIMATORS` | int | `300` | Nombre d'arbres pour l'Extended Isolation Forest | | `ISOLATION_CONTAMINATION` | float | `0.001` | Paramètre de contamination EIF (plage ]0, 0.5[) | | `ANOMALY_THRESHOLD` | float | `-0.05` | Seuil de score brut pour la détection d'anomalie | | `ANOMALY_PERCENTILE` | int | `5` | Percentile pour le seuil adaptatif | | `MODEL_DIR` | str | `/var/lib/bot_detector` | Répertoire de persistance des modèles | | `MODEL_HISTORY_COUNT` | int | `10` | Nombre de versions de modèle conservées | | `RETRAIN_INTERVAL_HOURS` | int | `24` | Intervalle de réentraînement EIF (heures) | | `DRIFT_THRESHOLD` | float | `0.30` | Seuil de dérive KS (fraction de features driftées) | | `MIN_VALID_FEATURE_RATIO` | float | `0.50` | Ratio minimal de features valides pour entraîner | | `PRUNE_VARIANCE_THRESHOLD` | float | `1e-6` | Seuil de variance pour l'élagage de features | | `VAL_ANOMALY_GATE` | float | `0.20` | Garde-fou : taux maximum d'anomalies en validation | ### Autoencoder | Variable | Type | Défaut | Description | |----------|------|--------|-------------| | `AE_WEIGHT` | float | `0.30` | Poids de l'Autoencoder dans le score combiné (plage ]0, 1[) | | `AE_EPOCHS` | int | `50` | Nombre d'époques d'entraînement | | `AE_LATENT_DIM` | int | `16` | Dimension de l'espace latent | | `AE_LEARNING_RATE` | float | `1e-3` | Taux d'apprentissage Adam | ### XGBoost | Variable | Type | Défaut | Description | |----------|------|--------|-------------| | `XGB_WEIGHT` | float | `0.20` | Poids de XGBoost dans le score final (plage ]0, 1[) | | `XGB_MIN_LABELS` | int | `100` | Nombre minimal de labels SOC pour activer XGBoost | | `XGB_RETRAIN_INTERVAL_HOURS` | int | `168` | Intervalle de réentraînement XGBoost (7 jours) | ### Détection navigateur | Variable | Type | Défaut | Description | |----------|------|--------|-------------| | `BROWSER_CONFIDENCE_THRESHOLD` | float | `0.55` | Confiance minimale pour classifier `LEGITIMATE_BROWSER` | | `BROWSER_COHORT_RATIO` | float | `0.70` | Si ≥ 70 % des sessions d'un JA4 sont navigateur → propagation | ### Clustering et explicabilité | Variable | Type | Défaut | Description | |----------|------|--------|-------------| | `ENABLE_CLUSTERING` | bool | `true` | Activer le clustering HDBSCAN | | `CLUSTERING_MIN_SAMPLES` | int | `3` | Taille minimale de cluster | | `ENABLE_SHAP` | bool | `true` | Activer l'explicabilité SHAP (requiert `shap` installé) | ### Cycle et opérations | Variable | Type | Défaut | Description | |----------|------|--------|-------------| | `CYCLE_INTERVAL_SEC` | int | `300` | Intervalle entre les cycles (secondes) | | `MAX_CONSECUTIVE_FAILURES` | int | `3` | Échecs consécutifs avant `healthy=False` | | `DEDUP_TTL_MIN` | int | `60` | TTL de déduplication inter-cycle (minutes) | | `RECURRENCE_WEIGHT` | float | `0.005` | Poids de la récurrence dans le score brut | | `ENABLE_MULTIWINDOW` | bool | `false` | Activer l'analyse multi-fenêtre 24h | | `MULTIWINDOW_VIEW` | str | `view_ai_features_24h` | Vue ClickHouse pour le mode multi-fenêtre | | `ENABLE_FEEDBACK` | bool | `true` | Activer l'intégration du feedback SOC | | `FEEDBACK_WINDOW_DAYS` | int | `7` | Fenêtre de feedback SOC (jours) | ### Journalisation et santé | Variable | Type | Défaut | Description | |----------|------|--------|-------------| | `BOT_DETECTOR_LOG` | str | `/var/log/bot_detector/decisions.jsonl` | Chemin du fichier de décisions | | `LOG_BACKUP_COUNT` | int | `7` | Nombre de fichiers de rotation conservés | | `HEALTH_PORT` | int | `8080` | Port du serveur de santé HTTP | --- ## Pipeline ML — Ensemble à triple voix ### Vue d'ensemble Le bot-detector utilise trois modèles en parallèle, combinés par une pondération configurable : ``` ┌──────────────────────┐ │ Extended Isolation │ │ Forest (isotree) │──→ eif_norm (0–1) └──────────────────────┘ │ │ × (1 − AE_WEIGHT) ┌──────────────────────┐ │ │ TrafficAutoEncoder │ ├──→ combined_norm │ (PyTorch) │──→ ae_norm (0–1) └──────────────────────┘ × AE_WEIGHT │ × (1 − XGB_WEIGHT) ┌──────────────────────┐ │ │ XGBoost │ ├──→ anomaly_score │ (supervisé, labels │──→ xgb_prob (0–1) │ SOC + Cleanlab) │ × XGB_WEIGHT └──────────────────────┘ ``` **Formule du score final :** ``` combined_norm = (1 − AE_WEIGHT) × eif_norm + AE_WEIGHT × ae_norm anomaly_score = (1 − XGB_WEIGHT) × combined_norm + XGB_WEIGHT × xgb_prob ``` Avec les poids par défaut (`AE_WEIGHT=0.30`, `XGB_WEIGHT=0.20`) : ``` anomaly_score = 0.56 × eif_norm + 0.24 × ae_norm + 0.20 × xgb_prob ``` ### Architecture duale Deux modèles indépendants tournent sur chaque cycle : | Modèle | Condition | Features | Données | |--------|-----------|----------|---------| | **Complet** | `correlated = 1` | 85 (`FEATURES_COMPLET`) | HTTP + TCP + TLS (L3→L7) | | **Applicatif** | `correlated = 0` | 73 (`FEATURES`) | HTTP seul (L7 pur) | En mode multi-fenêtre (`ENABLE_MULTIWINDOW=true`), deux variantes supplémentaires sont exécutées sur la vue 24h : `Complet_24h` et `Applicatif_24h`. ### Extended Isolation Forest (EIF) Modèle principal non supervisé. Utilise `isotree.IsolationForest` : ```python isotree.IsolationForest( ntrees=300, # N_ESTIMATORS ndim=min(3, n_features), sample_size='auto', missing_action='impute', random_seed=42, nthreads=-1, ) ``` **Fallback** si `isotree` n'est pas disponible : `sklearn.ensemble.IsolationForest(n_estimators=300, contamination=CONTAMINATION)`. **Calibration** : le score isotree brut (∈ [0, 1], >0.5 = anomalous) est converti en convention sklearn : `sklearn_equiv = 0.5 − isotree_score`. ### TrafficAutoEncoder (PyTorch) Architecture symétrique encodeur-décodeur : ``` Encodeur : n_features → dim1 → dim2 → 16 (latent) Décodeur : 16 → dim2 → dim1 → n_features dim1 = min(64, max(n_features, latent_dim + 4)) dim2 = min(32, max(dim1 // 2, latent_dim + 2)) ``` - Activations : `ReLU` + `BatchNorm1d` sur les couches cachées, `Sigmoid` en sortie du décodeur - Optimiseur : `Adam(lr=1e-3, weight_decay=1e-5)` - Perte : `MSELoss` - Entraînement : 50 époques, batch_size=256 - Score : erreur de reconstruction MSE par échantillon - Normalisation des entrées : min-max [0, 1] par feature ### XGBoost (supervisé) Entraîné sur les labels issus du feedback SOC (table `soc_feedback`), filtrés par Cleanlab : ```python xgb.XGBClassifier( n_estimators=200, max_depth=6, learning_rate=0.1, scale_pos_weight=auto, # max(1, n_neg / n_pos) eval_metric='logloss', tree_method='hist', random_state=42, n_jobs=-1, ) ``` - Labels positifs (bot) : `HIGH`, `CRITICAL`, `ANUBIS_DENY`, `KNOWN_BOT` - Labels négatifs (légitime) : `NORMAL`, `LEGITIMATE_BROWSER` - Activation requiert ≥ `XGB_MIN_LABELS` (100) labels - Réentraînement tous les `XGB_RETRAIN_INTERVAL_HOURS` (168h = 7 jours) - **Filtrage Cleanlab** : avant l'entraînement, un XGBoost rapide (80 arbres, 3-fold CV) produit des `pred_probs` qui alimentent `cleanlab.filter.find_label_issues()`. Les exemples identifiés comme bruités sont exclus du jeu d'entraînement. En cas d'échec, les labels bruts sont conservés. --- ## Détection multifactorielle des navigateurs Module `browser.py` — classifie chaque session sur 5 axes pondérés : | Axe | Clé | Poids | Composantes | |-----|-----|-------|-------------| | **1 — JA4 connu** | `axis_ja4_known` | 0.25 | Famille navigateur identifiée dans `dict_browser_ja4` → 1.0, sinon 0.0 | | **2 — Structure JA4** | `axis_ja4_struct` | 0.15 | TLS 1.3 (×0.35), h2/h3 (×0.25), nb ciphers 10–25 (×0.20), nb extensions 10–25 (×0.20) | | **3 — HTTP moderne** | `axis_http_modern` | 0.25 | modern_browser_score ≥ 50 (×0.35), Accept-Language (×0.20), Sec-Fetch < 0.3 (×0.25), generic_accept < 0.3 (×0.10), pas de ua_ch_mismatch (×0.10) | | **4 — Comportement navigation** | `axis_nav_behavior` | 0.15 | has_cookie (×0.25), has_referer (×0.25), asset_ratio > 0.15 (×0.25), direct_access < 0.5 (×0.25) | | **5 — Cohérence TLS/TCP** | `axis_tls_coherence` | 0.20 | Pas d'alpn_mismatch (×0.25), window_scale OK (×0.20), tls12 < 0.1 (×0.20), pas d'http10 (×0.15), ALPN présent (×0.20) | **Seuil** : `browser_confidence ≥ 0.55` + famille identifiée → `LEGITIMATE_BROWSER` **Propagation par cohorte** : si ≥ 70 % des sessions partageant un JA4 sont classées navigateur, les sessions `NORMAL`/`LOW` restantes avec le même JA4 sont aussi classées `LEGITIMATE_BROWSER`. **Inférence de famille** : pour les JA4 inconnus, correspondance structurelle avec les profils `_BROWSER_JA4_PROFILES` (Chromium, Firefox, Safari, Tor_Browser) — requiert `browser_confidence ≥ 0.45`. --- ## Scoring et normalisation ### Normalisation des scores (`normalize_scores`) Les scores bruts (négatifs = anomalous) sont mappés vers **[0, 1]** avec **1 = le plus anomalous** : ``` result[mask] = clip(−scores / (−s_min + 1e-9), 0, 1) ``` Les scores ≥ 0 sont mis à 0. ### Seuil adaptatif (`compute_adaptive_threshold`) ``` threshold = min(percentile(scores_négatifs, ANOMALY_PERCENTILE), ANOMALY_THRESHOLD) ``` Avec `ANOMALY_PERCENTILE=5` et `ANOMALY_THRESHOLD=-0.05`. ### Pénalité de récurrence Appliquée au `raw_anomaly_score` : ``` raw_anomaly_score −= log1p(recurrence_count) × RECURRENCE_WEIGHT ``` ### Niveaux de menace | Plage de score brut | Niveau | Interprétation | |---------------------|--------|----------------| | `< −0.30` | **CRITICAL** | Comportement extrêmement anomalous | | `< −0.15` | **HIGH** | Signal d'anomalie fort | | `< −0.05` | **MEDIUM** | Anomalie modérée | | `< 0` | **LOW** | Légèrement inhabituel | | `≥ 0` | **NORMAL** | Trafic normal | --- ## Clustering HDBSCAN des campagnes Lorsque `ENABLE_CLUSTERING=true`, les anomalies sont regroupées en campagnes par HDBSCAN : ```python hdbscan.HDBSCAN( min_cluster_size=CLUSTERING_MIN_SAMPLES, # défaut : 3 min_samples=max(2, CLUSTERING_MIN_SAMPLES − 1), # défaut : 2 cluster_selection_method='eom', ) ``` **Espace de clustering** : si un Autoencoder est disponible, le clustering s'effectue dans l'**espace latent 16-dim** de l'AE. Sinon, `StandardScaler` est appliqué sur les features brutes. **Fallback** si `hdbscan` n'est pas disponible : `DBSCAN(eps=0.5, min_samples=CLUSTERING_MIN_SAMPLES)`. Chaque anomalie reçoit un `campaign_id` (−1 = pas de cluster). --- ## Liste des features ### Features communes — modèle Applicatif (73 features) #### Comportement HTTP | Feature | Description | |---------|-------------| | `hits` | Nombre de requêtes dans la fenêtre | | `hit_velocity` | Requêtes par seconde | | `fuzzing_index` | Score de diversité chemins/paramètres | | `post_ratio` | Fraction de requêtes POST | | `port_exhaustion_ratio` | Fraction de ports source distincts / total | | `orphan_ratio` | Requêtes sans corrélation TLS | | `head_ratio` | Fraction de requêtes HEAD | | `http10_ratio` | Fraction de requêtes HTTP/1.0 | | `generic_accept_ratio` | Fraction d'en-têtes Accept courts | | `sec_fetch_absence_rate` | Fraction sans Sec-Fetch-Site | | `missing_accept_enc_ratio` | Fraction sans Accept-Encoding | | `http_scheme_ratio` | Fraction utilisant HTTP (pas HTTPS) | #### Gestion de connexion | Feature | Description | |---------|-------------| | `max_keepalives` | Max de requêtes sur une seule connexion Keep-Alive | | `tcp_shared_count` | Connexions TCP partagées entre sessions | | `multiplexing_efficiency` | Efficacité du multiplexage HTTP/2 | #### Empreinte navigateur | Feature | Description | |---------|-------------| | `header_count` | Nombre d'en-têtes HTTP envoyés | | `has_accept_language` | Présence de l'en-tête Accept-Language | | `has_cookie` | Présence de l'en-tête Cookie | | `has_referer` | Présence de l'en-tête Referer | | `modern_browser_score` | Score composite de conformité navigateur (0–100) | | `ua_ch_mismatch` | Incohérence User-Agent vs Client Hints | | `ip_id_zero_ratio` | Paquets IP avec ID=0 (pile minimaliste/headless) | | `header_order_shared_count` | IPs partageant le même ordre d'en-têtes | | `header_order_confidence` | Entropie normalisée de l'ordre des en-têtes | | `distinct_header_orders` | Ordres d'en-têtes distincts par IP | | `is_fake_navigation` | Sec-Fetch-Mode=navigate avec destination non-document | #### Comportement de navigation | Feature | Description | |---------|-------------| | `request_size_variance` | Variance de la taille des requêtes | | `mss_mobile_mismatch` | Incohérence TCP MSS vs profil mobile | | `asset_ratio` | Fraction de requêtes de ressources statiques | | `direct_access_ratio` | Accès directs (sans referer) | | `is_ua_rotating` | Rotation de User-Agent détectée | | `distinct_ja4_count` | Empreintes JA4 distinctes par IP | | `anomalous_payload_ratio` | Fraction de charges utiles anomalous | #### Concentration et rareté | Feature | Description | |---------|-------------| | `src_port_density` | Entropie des ports source | | `ja4_asn_concentration` | Concentration JA4 au sein de l'ASN | | `ja4_country_concentration` | Concentration JA4 par pays | | `is_rare_ja4` | Empreinte JA4 rare (< 100 hits totaux) | #### Temporel et diversité | Feature | Description | |---------|-------------| | `temporal_entropy` | Entropie de la distribution temporelle | | `path_diversity_ratio` | Diversité des chemins URL | | `url_depth_variance` | Variance de la profondeur des URL | #### Anubis | Feature | Description | |---------|-------------| | `anubis_is_flagged` | Signal de suspicion Anubis (bot détecté, action ni ALLOW/DENY/vide) | #### Navigateur multifactoriel — 6 axes | Feature | Description | |---------|-------------| | `is_known_browser` | JA4 correspond à un navigateur connu | | `browser_consistency_score` | Score composite de cohérence navigateur | | `browser_confidence` | Confiance globale de l'identification navigateur (seuil 0.55) | | `axis_ja4_known` | Axe 1 : JA4 reconnu dans `dict_browser_ja4` | | `axis_ja4_struct` | Axe 2 : structure JA4 cohérente (chiffrement, extensions TLS) | | `axis_http_modern` | Axe 3 : comportement HTTP moderne (Sec-Fetch-*, Accept-Language) | | `axis_nav_behavior` | Axe 4 : comportement de navigation (asset_ratio, referer, cascade) | | `axis_tls_coherence` | Axe 5 : cohérence TLS/TCP (ALPN, SNI, MSS mobile) | | `axis_h2_coherence` | Axe 6 : cohérence HTTP/2 (h2_fingerprint ↔ famille navigateur déclarée) | #### Thèse §5 — Features avancées | Feature | Description | |---------|-------------| | `seq_emb_0`..`seq_emb_31` | Embeddings séquentiels via Transformer (§5.2, remplace path_transition_entropy + cadence_cv) | | `burst_ratio` | Fraction de requêtes en rafale (§5.3) | | `pause_ratio` | Fraction de pauses longues (§5.3) | | `lag1_autocorrelation` | Autocorrélation lag-1 des inter-arrivées (§5.3) | | `benford_deviation` | Déviation par rapport à la loi de Benford | | `root_to_first_asset_delay` | Délai HTML → premier asset (§5.4, détection headless) | | `asset_load_stddev` | Écart-type des intervalles de chargement d'assets (§5.4) | | `host_diversity` | Diversité des hôtes ciblés (§5.8) | | `host_sweep_speed` | Vitesse de balayage des hôtes (§5.8) | | `host_coverage_uniformity` | Uniformité de couverture des hôtes (§5.8) | | `cross_domain_path_similarity` | Similarité Jaccard inter-hôte des chemins — scanner latéral (§5.8) | #### Features HTTP/2 | Feature | Description | |---------|-------------| | `h2_settings_known` | Fingerprint HTTP/2 reconnu dans `dict_browser_h2` (0/1) | | `h2_pseudo_order_match` | Ordre des pseudo-headers cohérent avec le navigateur déclaré (0/1) | | `h2_ja4_coherence` | Cohérence HTTP/2 ↔ JA4 (même famille) (0/1) | | `h2_settings_rare` | Fingerprint HTTP/2 avec <100 occurrences globales (0/1) | #### Score de cohérence cross-layer | Feature | Description | |---------|-------------| | `fingerprint_coherence_score` | Score composite (0.0–1.0) combinant : JA4↔UA, H2↔JA4, TCP↔UA, langue↔ASN, Sec-CH-UA↔UA | #### Features TCP fenêtre | Feature | Description | |---------|-------------| | `true_window_size` | Taille réelle de la fenêtre TCP | | `window_mss_ratio` | Ratio fenêtre TCP / MSS | #### Features de détection avancées | Feature | Description | |---------|-------------| | `has_xff` | Présence de l'en-tête X-Forwarded-For | | `unusual_content_type_ratio` | Fraction de Content-Type inhabituels | | `non_standard_port_ratio` | Fraction de ports non standards | | `login_post_concentration` | Concentration de POST sur les pages de login | | `sec_ch_mobile_mismatch` | Incohérence Sec-CH-UA-Mobile | ### Features supplémentaires — modèle Complet (+12 features TCP/TLS) | Feature | Description | |---------|-------------| | `tcp_jitter_variance` | Variance du jitter TCP inter-paquets | | `alpn_http_mismatch` | Incohérence ALPN vs protocole HTTP réel | | `is_alpn_missing` | ALPN absent dans le ClientHello | | `sni_host_mismatch` | Incohérence TLS SNI vs en-tête Host HTTP | | `ja3_diversity_ratio` | Ratio de diversité JA3 par IP | | `syn_timing_cv` | Coefficient de variation du timing SYN | | `tls12_ratio` | Fraction de connexions TLS 1.2 | | `ip_df_variance` | Variance du flag Don't-Fragment IP | | `avg_ttl` | TTL IP moyen (empreinte OS) | | `ttl_std` | Écart-type du TTL | | `no_window_scale_ratio` | Fraction sans TCP Window Scale | | `ja4_drift_ratio` | Dérive JA4 intra-session (§5.5) | --- ## Pipeline de détection ``` 1. Requête view_ai_features_1h → DataFrame 2. Enrichissement optionnel view_thesis_features_1h (features thèse §5) 3. Prétraitement : preprocess_df() (nettoyage, browser axes 6, imputation) 4. Chargement du feedback SOC → reclassification 5. Chargement de la carte de récurrence (view_ip_recurrence) 6. Séparation par correlated = 1 / correlated = 0 7. Pour chaque modèle (Complet, Applicatif) : a. Validation des features (exclure constantes/manquantes) b. Séparation des bots connus → journalisation KNOWN_BOT c. Filtrage de la baseline humaine (asn_label = 'human', fingerprint_coherence_score ≥ seuil) d. Chargement ou entraînement EIF + AE e. Scoring du trafic inconnu (EIF + AE) f. Chargement ou entraînement XGBoost (si labels disponibles) g. MetaLearner : pondération apprise (logistique) sur historique SOC, sinon fallback poids fixes h. Combinaison des scores via MetaLearner ou formule fixe i. Normalisation [0, 1] j. Seuil adaptatif (percentile_5 des scores négatifs, minimum -0.05) k. Pénalité de récurrence l. ExIFFI (importance par profondeur d'isolation EIF) + erreur AE par feature m. SHAP top-5 TreeExplainer n. HDBSCAN clustering → campaign_id o. Détection de dérive (KS test + KL divergence) p. Alerte drift adversarial (dérive simultanée multiple features → direction commune) 8. Analyse de flotte (fleet.py) : graphe bipartite JA4×ASN → communautés Louvain → fleet_score 9. Scoring dynamique H2 (browser_matcher_dynamic.py) : profils auto-appris vs sessions entrantes 10. Mode multi-fenêtre (si activé) : idem sur view_ai_features_24h 11. Insertion → ml_all_scores (toutes les sessions scorées) 12. Déduplication intra-cycle (garder raw_anomaly_score le plus bas par IP) 13. Déduplication inter-cycle (TTL, skip si détecté récemment sauf aggravation ≥ 0.05) 14. Insertion → ml_detected_anomalies (anomalies filtrées) 15. Insertion → fleet_detections (flottes détectées avec fleet_score) 16. Enregistrement → ml_performance_metrics (métriques de cycle + alertes) ``` --- ## Détection de dérive (KS + KL divergence) Par feature, deux tests comparent la distribution courante avec la distribution d'entraînement : **Test KS (Kolmogorov-Smirnov)** : - Distribution reconstruite par interpolation à partir d'un digest quantile 9 points (p5, p10, p25, p50, p75, p90, p95) - Feature driftée si `p_value < 0.05` **Divergence KL (Kullback-Leibler)** : - Histogramme discrétisé (20 bins) de la distribution courante vs baseline - Feature driftée si `KL > seuil` (0.5 par défaut) - Détection de **drift adversarial** : si ≥30% des features dérivent simultanément dans la même direction → alerte `ADVERSARIAL_DRIFT` **Règle de décision** : une feature est en drift si KS **ou** KL dépasse son seuil. - Dérive globale = fraction de features driftées - Si `drift > DRIFT_THRESHOLD` (0.30) → réentraînement automatique **Fallback** (sans `scipy`) : méthode Z-score — feature driftée si `|moyenne_courante − moyenne_entraînement| / std_entraînement > 2.0`. --- ## MetaLearner Remplace la pondération linéaire fixe `(1-XGB_W)×((1-AE_W)×eif + AE_W×ae) + XGB_W×xgb` par une régression logistique apprise (`scoring.MetaLearner`) : ``` P(bot) = logistic(w1×eif + w2×ae + w3×xgb + w4×volume + w5×correlated + bias) ``` - **Entraînement** : sur l'historique `ml_all_scores` JOIN `soc_feedback` (labels SOC + KNOWN_BOT + ANUBIS_DENY + LEGITIMATE_BROWSER) - **Seuil** : activé seulement si ≥1000 labels disponibles — sinon fallback aux poids fixes - **Transparence** : poids appris journalisés dans `ml_performance_metrics` pour audit SOC - **Validation** : comparaison MetaLearner vs poids fixes sur split temporel (données postérieures à l'entraînement) --- ## ExIFFI et explicabilité augmentée En complément de SHAP, le module expose deux méthodes d'importance de features spécifiques aux modèles utilisés : **ExIFFI** (`compute_exiffi_importance`) : - Importance calculée par la profondeur moyenne d'isolation par feature dans l'EIF - Une feature avec profondeur d'isolation faible contribue fortement à l'anomalie - Corrèle avec SHAP mais capte des aspects complémentaires de la structure EIF **Erreur AE par feature** (`compute_ae_feature_errors`) : - Reconstruction PyTorch feature par feature : `err_i = (x_i - x̂_i)²` - Pour chaque anomalie, les features avec la plus grande erreur de reconstruction sont identifiées - Expose quelles dimensions l'autoencoder ne parvient pas à reconstruire Les deux méthodes sont disponibles dans le champ `shap_features` des résultats, en complément des valeurs SHAP TreeExplainer. --- ## Détection de flottes (fleet.py) Détecte les **botnets coordonnés** utilisant des JA4 et ASN rotatifs via analyse de graphe bipartite : 1. **Construction du graphe** : nœuds JA4 ∪ ASN, arêtes IP observées dans le cycle 2. **Projection** : projection du graphe bipartite sur les nœuds JA4 3. **Communautés** : algorithme de Louvain (NetworkX) sur le graphe projeté 4. **Score de flotte** : `fleet_score = taille_communauté × densité_arêtes / log(nb_ASN)` 5. **Enrichissement** : les IPs membres reçoivent un malus proportionnel au fleet_score Résultats stockés dans `fleet_detections` (TTL 7 jours). Exposés dans le dashboard via la page `/fleet`. --- ## Métriques de performance (metrics.py) Enregistre par cycle dans `ml_performance_metrics` : | Métrique | Description | |----------|-------------| | `anomaly_rate` | Taux d'anomalies détectées (cible : 0.5%–10%) | | `known_bot_rate` | Fraction KNOWN_BOT dans le cycle | | `legit_browser_rate` | Fraction LEGITIMATE_BROWSER | | `drift_rate` | Fraction de features en dérive | | `corr_rate` | Taux de sessions corrélées (cible : ≥50%) | | `cycle_latency_s` | Durée totale d'inférence (cible : <300s) | | `alert_flags` | Alertes émises (CALIBRATION_HIGH/LOW, DRIFT, CORRELATION, LATENCY) | **Seuils d'alerte** : - `anomaly_rate > 10%` → `CALIBRATION_HIGH` - `anomaly_rate < 0.5%` → `CALIBRATION_LOW` - `drift_rate > 30%` → `DRIFT_ALERT` - `corr_rate < 50%` → `CORRELATION_ALERT` - `cycle_latency_s > 300` → `LATENCY_ALERT` --- ## Enrichissement Anubis La vue `view_ai_features_1h` enrichit chaque IP via les dictionnaires Anubis selon une cascade de priorité : 1. **UA + IP combinés** (même `rule_id`) — confiance maximale 2. **UA seul** (pas de condition IP) 3. **IP seul** (pas de condition UA) 4. **Correspondance ASN** 5. **Correspondance pays** --- ## Tables de sortie ### ml_detected_anomalies Détections d'anomalies au-dessus du seuil de menace. Engine : `ReplacingMergeTree(detected_at)`, ORDER BY `(src_ip, model_name)`, TTL 7 jours. Colonnes clés : `detected_at`, `src_ip`, `ja4`, `host`, `bot_name`, `anomaly_score`, `raw_anomaly_score`, `threat_level`, `model_name`, `recurrence`, `campaign_id`, `reason`, `anubis_bot_name`, `anubis_bot_action`, `anubis_bot_category`, plus toutes les features ML. ### ml_all_scores Toutes les classifications (sans filtre de seuil) pour l'observabilité. Engine : `ReplacingMergeTree(detected_at)`, ORDER BY `(window_start, src_ip, ja4, host, model_name)`, TTL 7 jours. --- ## Format du journal de décisions Le fichier `decisions.jsonl` contient des entrées JSONL structurées : ```json {"event": "CYCLE_START", "cycle_id": "20260309T143000", "total": 5000, "human": 1500, "known_bot": 200, "correlated": 3000} {"event": "ANOMALY", "src_ip": "203.0.113.42", "score": -0.25, "threat_level": "HIGH", "reason": "hit_velocity=45.2, fuzzing_index=0.8, ...", "campaign_id": 3} {"event": "KNOWN_BOT", "src_ip": "198.51.100.10", "bot_name": "AhrefsBot"} {"event": "CYCLE_END", "cycle_id": "20260309T143000", "anomalies": 15, "known_bots": 200, "duration_sec": 12.5} ``` Rotation des logs : 50 Mo max × `LOG_BACKUP_COUNT` sauvegardes (défaut : 7). --- ## Point de santé - **URL** : `GET http://localhost:8080/` - **Réponses** : `200 OK` (corps `OK`) ou `503 Service Unavailable` (corps `DEGRADED`) - Exécuté dans un thread daemon au démarrage - État mis à `False` après `MAX_CONSECUTIVE_FAILURES` (3) échecs consécutifs ClickHouse, remis à `True` dès le premier succès --- ## Persistance des modèles | Fichier | Description | |---------|-------------| | `model__.joblib` | EIF sérialisé (joblib) | | `model__.meta.json` | Métadonnées (features, seuils, statistiques d'entraînement, digest quantile) | | `model_.current` | Pointeur vers la version active | | `training_history.jsonl` | Historique d'entraînement | Rotation automatique : seules les `MODEL_HISTORY_COUNT` dernières versions (défaut : 10) sont conservées. --- ## Déploiement Docker ```bash # Construction de l'image make build-bot-detector # Exécution avec docker-compose cd services/bot-detector docker-compose up -d # Tests make test-bot-detector ``` ### Volumes | Chemin hôte | Chemin conteneur | Description | |-------------|-----------------|-------------| | `./bot_detector_logs` | `/var/log/bot_detector` | Journaux de décisions (JSONL) | | `./bot_detector_models` | `/var/lib/bot_detector` | Modèles ML persistés | | `./reputation/data/user_files/bot_ip.csv` | `/data/bot_ip.csv` (ro) | Liste d'IPs de bots connus | | `./reputation/data/user_files/bot_ja4.csv` | `/data/bot_ja4.csv` (ro) | Liste de JA4 de bots connus | | `./reputation/data/user_files/asn_reputation.csv` | `/data/asn_reputation.csv` (ro) | Labels de réputation ASN |