# Bot Detector IA — Documentation Technique > Architecture modulaire (16 modules) | Dernière mise à jour : 2026-04-13 --- ## Table des matières 1. [Vue d'ensemble](#1-vue-densemble) 2. [Installation et configuration](#2-installation-et-configuration) 3. [Modules](#3-modules) 4. [Features ML](#4-features-ml) 5. [Classification des menaces](#5-classification-des-menaces) 6. [Tests](#6-tests) 7. [Diagnostic](#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 ├── browser_matcher.py (498) # Scoring H2 statique à 7 dimensions pondérées ├── browser_signatures.py (166) # Signatures statiques Chrome/Firefox/Safari ├── browser_matcher_dynamic.py (387) # Scoring H2 dynamique temps réel (profils auto-appris) ├── profile_builder.py (614) # Profiling HDBSCAN hors-ligne, centroïdes, lifecycle ├── fleet.py (XXX) # Détection de flottes par graphes bipartis NetworkX ├── scoring.py (588) # 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 (0–20) | | `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 ```bash # 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** : ```python 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.4b `browser_matcher.py` — Scoring H2 statique **Rôle** : Scoring à 7 dimensions pondérées des sessions HTTP/2 contre des signatures de navigateurs connues (Chrome, Firefox, Safari). **Fonctions exportées** : | Fonction | Description | |----------|-------------| | `run_browser_matcher(df)` | Score un batch de sessions, retourne `browser_match_chrome/firefox/safari/max` | | `log_dual_mode_comparison(df)` | Compare les scores statique vs confiance browser | **Dimensions de scoring** : | Dimension | Poids | Signal | |-----------|-------|--------| | D1 — SETTINGS H2 | 0.30 | Correspondance exacte des paramètres SETTINGS | | D2 — WINDOW_UPDATE | 0.15 | Valeur de WINDOW_UPDATE ± tolérance | | D3 — Pseudo-order | 0.15 | Ordre des pseudo-headers H2 | | D4 — PRIORITY frames | 0.10 | Présence de frames PRIORITY | | D5 — HTTP headers | 0.15 | Cohérence des headers HTTP | | D6 — TLS structure | 0.10 | Famille TLS (JA4) | | D7 — JA4 dict | 0.05 | Lookup dans le dictionnaire JA4 navigateurs | **Dépendance** : `browser_signatures.py` (signatures statiques), `config.py` (`BROWSER_CONFIDENCE_THRESHOLD`). --- ### 3.4c `browser_matcher_dynamic.py` — Scoring H2 dynamique temps réel **Rôle** : Scoring des sessions HTTP/2 contre les profils auto-appris (centroïdes HDBSCAN). Remplace le dictionnaire statique pour l'adaptation automatique aux nouvelles versions de navigateurs. **Fonctions exportées** : | Fonction | Description | |----------|-------------| | `get_dynamic_matcher()` | Singleton du chargeur/scorer | | `load_dynamic_profiles(client, force)` | Charge les profils depuis `auto_browser_profiles` (refresh 24h) | | `score_session_dynamic(session)` | Score une session → `(famille, score)` | | `score_sessions_batch_dynamic(df)` | Score un batch (ajoute `dynamic_family`, `dynamic_score`) | **Pipeline de scoring** : 1. Chargement des profils en mémoire depuis `ja4_processing.auto_browser_profiles` 2. Pour chaque session : rejet rapide (pseudo_order incompatible ou tolérance dépassée) 3. Similarité pondérée : `h2_window_update` (0.40), `pseudo_order` (0.40), `h2_initial_window_size` (0.10), `h2_has_priority` (0.10) 4. Confiance volumétrique : `min(1.0, log10(count_ips + 1) / 4)` --- ### 3.4d `profile_builder.py` — Profiling HDBSCAN hors-ligne **Rôle** : Pipeline quotidien (cron) qui clusterise les sessions H2 similaires, calcule les centroïdes, et gère le cycle de vie des profils dynamiques. **Fonction exportée** : | Fonction | Description | |----------|-------------| | `run_profile_builder(client)` | Pipeline complet : extraction → HDBSCAN → centroïdes → fusion → persistance → lifecycle | **Pipeline interne** : 1. `_fetch_profiling_data()` — Lit `view_h2_profiling_raw`, déduplique par IP, limite 2M lignes 2. `_cluster_sessions()` — HDBSCAN (`min_cluster_size=1000`) sur variables mixtes (StandardScaler + brut) 3. `_compute_centroids()` — Moyenne + 3σ (tolérance) pour continues, mode pour catégorielles 4. `_label_family()` — Analyse des UAs → `Auto_Chrome`, `Auto_Firefox`, `Auto_Safari`, `Auto_Unknown` 5. `_merge_profiles()` — Fusion des clusters redondants (même famille + pseudo_order + WU < 5%) 6. `_persist_profiles()` — INSERT INTO `auto_browser_profiles` (ReplacingMergeTree) 7. `_update_last_seen()` — Rafraîchit les profils actifs (IPs vues dans les dernières 24h) 8. `_purge_stale_profiles()` — Supprime les profils > 14 jours **CLI** : `python -m bot_detector.profile_builder` --- ### 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`) ```python # 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`. ```python XGBClassifier( n_estimators=200, max_depth=6, learning_rate=0.1, scale_pos_weight=, # 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. **Filtrage Cleanlab des labels bruités** (`cleanlab>=2.6`, optionnel) : Avant l'entraînement XGBoost, un pipeline de *confident learning* filtre les labels SOC probablement erronés : 1. Entraînement d'un XGBoost rapide (`n_estimators=80, max_depth=4`) en 3-fold CV 2. Extraction des `pred_probs` out-of-fold via `cross_val_predict` 3. Appel à `cleanlab.filter.find_label_issues(labels=y, pred_probs=pred_probs)` 4. Exclusion des indices identifiés comme bruités du jeu d'entraînement 5. Recalcul du `scale_pos_weight` sur les données nettoyées Le taux de labels filtrés est loggué (`[XGB][name] Cleanlab : N/M labels bruyants supprimés (X.X%)`). En cas d'échec (erreur, dépendance manquante), le pipeline retombe sur les données brutes sans interruption. **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 : ```python 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** : ```python 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 ``` où `α = 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 ``` où `β = 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** : ```bash 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 ```bash # 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 ```sql -- 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 ```bash # 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 ```bash # 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 ```