Replace TrafficAutoEncoder (MSE reconstruction scoring) with TrafficNormalizingFlow (RealNVP via FrEIA, 4 affine coupling blocks, anomaly score = -log p(x)) for mathematically rigorous density estimation. Add SessionTransformer module producing 32-dimensional sequence embeddings from raw HTTP request sequences (path, method, timing) via a lightweight TransformerEncoder, replacing path_transition_entropy and cadence_cv features. Update thesis documentation sections 2.4.2b and 3.8 accordingly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
32 KiB
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 :
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+BatchNorm1dsur les couches cachées,Sigmoiden 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 :
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_probsqui alimententcleanlab.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 :
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_scoresJOINsoc_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_metricspour 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 :
- Construction du graphe : nœuds JA4 ∪ ASN, arêtes IP observées dans le cycle
- Projection : projection du graphe bipartite sur les nœuds JA4
- Communautés : algorithme de Louvain (NetworkX) sur le graphe projeté
- Score de flotte :
fleet_score = taille_communauté × densité_arêtes / log(nb_ASN) - 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_HIGHanomaly_rate < 0.5%→CALIBRATION_LOWdrift_rate > 30%→DRIFT_ALERTcorr_rate < 50%→CORRELATION_ALERTcycle_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é :
- UA + IP combinés (même
rule_id) — confiance maximale - UA seul (pas de condition IP)
- IP seul (pas de condition UA)
- Correspondance ASN
- 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 :
{"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(corpsOK) ou503 Service Unavailable(corpsDEGRADED) - Exécuté dans un thread daemon au démarrage
- État mis à
FalseaprèsMAX_CONSECUTIVE_FAILURES(3) échecs consécutifs ClickHouse, remis àTruedès le premier succès
Persistance des modèles
| Fichier | Description |
|---|---|
model_<name>_<version>.joblib |
EIF sérialisé (joblib) |
model_<name>_<version>.meta.json |
Métadonnées (features, seuils, statistiques d'entraînement, digest quantile) |
model_<name>.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
# 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 |