Replace the LogisticRegression meta-learner with a PyTorch MetaFusionMLP (Linear(3,16)->BN->ReLU->Dropout->Linear(16,1)->Sigmoid) for non-linear fusion of EIF, NF, and XGBoost scores. Replace KS-test + quantile digest drift detection with ADWIN (adaptive sliding window, Hoeffding bound). Replace weekly XGBoost batch retraining with River HoeffdingAdaptiveTree for incremental online learning (learn_one per cycle). Update all thesis documentation sections (2.4.2c, 2.4.3, 3.8, discussion, conclusion). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
47 KiB
Bot Detector IA — Documentation Technique
Architecture modulaire (16 modules) | Dernière mise à jour : 2026-04-13
Table des matières
- Vue d'ensemble
- Installation et configuration
- Modules
- Features ML
- Classification des menaces
- Tests
- 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 :
cycle.pyrécupère les features agrégées sur 1h (et 24h siENABLE_MULTIWINDOW=true)preprocessing.pynettoie les colonnes et enrichit via la détection navigateurpipeline.pyorchestre 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)
- Complet (~63 features,
models.pyentraîne/charge les trois voix de l'ensemble (EIF, AE, XGBoost)scoring.pynormalise, explique (SHAP), clusterise (HDBSCAN) et détecte la dérive- Les résultats sont insérés dans
ml_detected_anomaliesetml_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 ClickHousepandas,numpy— manipulation de donnéesscikit-learn— IsolationForest (fallback), StandardScaler, DBSCANstructlog— journalisation JSONjoblib— 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
# 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_ALLOWetLEGITIMATE_BROWSERsont attribués en amont parpipeline.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 :
- Chargement des profils en mémoire depuis
ja4_processing.auto_browser_profiles - Pour chaque session : rejet rapide (pseudo_order incompatible ou tolérance dépassée)
- Similarité pondérée :
h2_window_update(0.40),pseudo_order(0.40),h2_initial_window_size(0.10),h2_has_priority(0.10) - 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 :
_fetch_profiling_data()— Litview_h2_profiling_raw, déduplique par IP, limite 2M lignes_cluster_sessions()— HDBSCAN (min_cluster_size=1000) sur variables mixtes (StandardScaler + brut)_compute_centroids()— Moyenne + 3σ (tolérance) pour continues, mode pour catégorielles_label_family()— Analyse des UAs →Auto_Chrome,Auto_Firefox,Auto_Safari,Auto_Unknown_merge_profiles()— Fusion des clusters redondants (même famille + pseudo_order + WU < 5%)_persist_profiles()— INSERT INTOauto_browser_profiles(ReplacingMergeTree)_update_last_seen()— Rafraîchit les profils actifs (IPs vues dans les dernières 24h)_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, etSTRUCTURAL_EXCLUDED_FEATURES(dans config.py) exclut des features structurellement non pertinentes par modèle.
preprocess_df(df) — Pipeline de nettoyage :
- Nettoyage des noms de colonnes : supprime les préfixes de table (
c.split('.')[-1]) - Remplissage des colonnes texte :
fillna('')poursrc_ip,ja4,host,bot_name,anubis_bot_name,anubis_bot_action, etc. - Identification navigateur : calcule les 5 axes browser via
_compute_browser_axes(), infère la famille navigateur, ajoutebrowser_confidence,is_known_browser,browser_consistency_scoreet les 5 colonnesaxis_* - Signal Anubis : calcule
anubis_is_flagged(bot nommé par Anubis, action ni ALLOW ni DENY) - Imputation intelligente :
- Features binaires (
has_cookie,is_ua_rotating, etc.) →fillna(-1)(sentinelle pour donnée manquante) - Features numériques → remplacement des
±infparNaN, puisfillna(median)
- Features binaires (
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.
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 :
- Entraînement d'un XGBoost rapide (
n_estimators=80, max_depth=4) en 3-fold CV - Extraction des
pred_probsout-of-fold viacross_val_predict - Appel à
cleanlab.filter.find_label_issues(labels=y, pred_probs=pred_probs) - Exclusion des indices identifiés comme bruités du jeu d'entraînement
- Recalcul du
scale_pos_weightsur 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
Nonesi 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).
ADWINDriftMonitor(features, delta=0.002)
Détection de dérive conceptuelle par ADWIN (fenêtre glissante adaptative). Maintient un détecteur ADWIN par feature, mis à jour à chaque cycle avec la moyenne de la feature sur le trafic baseline.
Si la fraction de features en dérive dépasse DRIFT_THRESHOLD (0.30), le
modèle EIF/NF est ré-entraîné. Si > 50% des features dérivent, une alerte
ADVERSARIAL_DRIFT est générée.
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) :
-
Triage initial : Sépare le DataFrame en trois groupes :
known_bots: IPs avec unbot_namerenseigné (dictionnaires de réputation)anubis_allow: IPs avecanubis_bot_action='ALLOW'unknown_traffic: tout le reste- Baseline humaine :
unknown_trafficavecasn_label='isp'
-
Validation des features :
validate_features()exclut les features invalides. Retour anticipé si ratio <MIN_VALID_FEATURE_RATIO. -
Vérification de la baseline : Minimum 500 sessions humaines requis. Sinon
SKIPPED_LOW_DATA. -
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. -
EIF : scoring :
decision_function()sur tout le trafic inconnu. Pour isotree :score_equiv = 0.5 - isotree_score. -
Autoencoder : entraînement et scoring : Erreur de reconstruction MSE normalisée.
-
Combinaison EIF + AE :
combined = (1 - α) × eif_norm + α × ae_normoù
α = AE_WEIGHT = 0.30. Soit : 70% EIF + 30% AE. -
XGBoost : prédiction supervisée :
predict_proba()→ probabilité de malveillance. -
Fusion EIF+AE+XGB :
final = (1 - β) × combined + β × xgb_proboù
β = XGB_WEIGHT = 0.20. Le score final se décompose en : 56% EIF + 24% AE + 20% XGBoost. -
Seuil adaptatif :
compute_adaptive_threshold()sur les scores bruts EIF. -
Pénalité de récurrence :
raw_anomaly_score -= log1p(recurrence_count) × RECURRENCE_WEIGHT -
Classification des menaces :
score_to_threat_level()pour le score brut- Override
ANUBIS_DENYpour les IPs Anubis deny - Classification
LEGITIMATE_BROWSERsibrowser_confidence ≥ 0.55+ famille identifiée + threat_level ∈ {NORMAL, LOW} + action ≠ DENY
-
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
HIGHsont escaladées enCRITICAL
- SHAP : explication top-5 features → champ
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 :
- Connexion ClickHouse via
get_client() - Requête
{DB}.view_ai_features_1h→ DataFrame principal - Enrichissement avec les features thèse §5 :
{DB}.view_thesis_features_1h(jointure sursrc_ip) preprocess_df(df)— nettoyage, imputation, détection navigateur- Chargement de la carte de récurrence :
{DB}.view_ip_recurrence - Chargement du feedback SOC :
{DB}.audit_logs(siENABLE_FEEDBACK=true) - Application du feedback : FP →
asn_label='isp', TP →asn_label='soc_confirmed_bot' - Exécution du modèle Complet (
correlated=1,FEATURES_COMPLET) - Exécution du modèle Applicatif (
correlated=0,FEATURES) - Multi-fenêtres (si
ENABLE_MULTIWINDOW=true) :- Requête
{DB}.{MULTIWINDOW_VIEW}(défautview_ai_features_24h) - Exécution des modèles
Complet_24hetApplicatif_24h - Fusion OR : une IP est flaggée si anomalie dans ≥ 1 fenêtre (score le plus bas conservé)
- Requête
- Insertion
ml_all_scores: Toutes les sessions scorées - Déduplication intra-cycle :
drop_duplicates(subset=['src_ip'], keep='first')en gardant le score le plus bas - Déduplication inter-cycles (
_filter_recent_detections) : Interrogeml_detected_anomaliespour les IPs insérées dans les dernièresDEDUP_TTL_MINminutes. Réinsertion uniquement si le score s'est dégradé de ≥ 0.05 - 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 :
- Import de toute la configuration via
from .config import * - Affichage d'un bandeau de démarrage avec toutes les valeurs de configuration
- Journalisation de l'événement
SERVICE_START - 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 |
seq_emb_0..seq_emb_31 |
Embeddings séquentiels via SessionTransformer (§5.2, remplace path_transition_entropy + cadence_cv) | 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 |
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()sitorchouxgboostnon 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