Files
ja4-platform/docs/services/bot-detector.md
toto c96c41fb45 docs: réécriture complète de la documentation des services en français
- bot-detector.md : architecture 11 modules, 77/65 features,
  ensemble triple voix (EIF+AE+XGBoost), browser 5 axes, HDBSCAN,
  toutes les variables d'environnement vérifiées depuis le code source
- dashboard.md : corrigé stack (Jinja2+htmx, pas React+Vite),
  14 pages + 35 API routes + health, dual-database, IPv4/IPv6
- python-ja4common.md : ajouté CLICKHOUSE_DB_PROCESSING/LOGS,
  schéma dual-database, note dashboard n'utilise pas ja4_common

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

604 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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), enrichi par l'explicabilité
SHAP, le clustering HDBSCAN et la détection multifactorielle des navigateurs.
---
## Architecture des modules
Le service est découpé en **11 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
│ └─ browser.py Identification multifactorielle des navigateurs (5 axes)
├─ pipeline.py Orchestration : filtrage → entraînement → scoring → fusion
│ ├─ models.py EIF, TrafficAutoEncoder (PyTorch), XGBoost
│ └─ scoring.py Normalisation, seuil adaptatif, SHAP, HDBSCAN, dérive
└─ (insère dans ml_all_scores + ml_detected_anomalies)
```
| 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` | 170 | Détection multifactorielle des navigateurs sur 5 axes pondérés |
| `scoring.py` | 279 | Normalisation, seuil adaptatif, SHAP top-3, HDBSCAN, détection de dérive |
| `models.py` | 478 | `TrafficAutoEncoder`, entraînement/chargement EIF, XGBoost, élagage de features |
| `preprocessing.py` | 117 | `preprocess_df()` — nettoyage, typage, imputation, listes `FEATURES` / `FEATURES_COMPLET` |
| `pipeline.py` | 378 | `run_semi_supervised_logic()` — orchestration complète d'un modèle |
| `cycle.py` | 371 | `fetch_and_analyze()` — boucle principale, feedback SOC, multiwindow |
| `__main__.py` | 41 | Point d'entrée, bannière de démarrage, boucle `while True` |
| `__init__.py` | 1 | Docstring du package |
---
## 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 (01)
└──────────────────────┘ │
× (1 AE_WEIGHT)
┌──────────────────────┐ │
│ TrafficAutoEncoder │ ├──→ combined_norm
│ (PyTorch) │──→ ae_norm (01)
└──────────────────────┘ × AE_WEIGHT
× (1 XGB_WEIGHT)
┌──────────────────────┐ │
│ XGBoost │ ├──→ anomaly_score
│ (supervisé, labels │──→ xgb_prob (01)
│ SOC) │ × 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` | 77 (`FEATURES_COMPLET`) | HTTP + TCP + TLS (L3→L7) |
| **Applicatif** | `correlated = 0` | 65 (`FEATURES`) | HTTP seul (L7 pur) |
En mode multi-fenêtre (`ENABLE_MULTIWINDOW=true`), deux variantes supplémentaires sont exécutées sur la vue 24h : `Complet_24h` et `Applicatif_24h`.
### Extended Isolation Forest (EIF)
Modèle principal non supervisé. Utilise `isotree.IsolationForest` :
```python
isotree.IsolationForest(
ntrees=300, # N_ESTIMATORS
ndim=min(3, n_features),
sample_size='auto',
missing_action='impute',
random_seed=42,
nthreads=-1,
)
```
**Fallback** si `isotree` n'est pas disponible : `sklearn.ensemble.IsolationForest(n_estimators=300, contamination=CONTAMINATION)`.
**Calibration** : le score isotree brut (∈ [0, 1], >0.5 = anomalous) est converti en convention sklearn : `sklearn_equiv = 0.5 isotree_score`.
### TrafficAutoEncoder (PyTorch)
Architecture symétrique encodeur-décodeur :
```
Encodeur : n_features → dim1 → dim2 → 16 (latent)
Décodeur : 16 → dim2 → dim1 → n_features
dim1 = min(64, max(n_features, latent_dim + 4))
dim2 = min(32, max(dim1 // 2, latent_dim + 2))
```
- Activations : `ReLU` + `BatchNorm1d` sur les couches cachées, `Sigmoid` en sortie du décodeur
- Optimiseur : `Adam(lr=1e-3, weight_decay=1e-5)`
- Perte : `MSELoss`
- Entraînement : 50 époques, batch_size=256
- Score : erreur de reconstruction MSE par échantillon
- Normalisation des entrées : min-max [0, 1] par feature
### XGBoost (supervisé)
Entraîné sur les labels issus du feedback SOC (table `soc_feedback`) :
```python
xgb.XGBClassifier(
n_estimators=200,
max_depth=6,
learning_rate=0.1,
scale_pos_weight=auto, # max(1, n_neg / n_pos)
eval_metric='logloss',
tree_method='hist',
random_state=42,
n_jobs=-1,
)
```
- Labels positifs (bot) : `HIGH`, `CRITICAL`, `ANUBIS_DENY`, `KNOWN_BOT`
- Labels négatifs (légitime) : `NORMAL`, `LEGITIMATE_BROWSER`
- Activation requiert ≥ `XGB_MIN_LABELS` (100) labels
- Réentraînement tous les `XGB_RETRAIN_INTERVAL_HOURS` (168h = 7 jours)
---
## 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 1025 (×0.20), nb extensions 1025 (×0.20) |
| **3 — HTTP moderne** | `axis_http_modern` | 0.25 | modern_browser_score ≥ 50 (×0.35), Accept-Language (×0.20), Sec-Fetch < 0.3 (×0.25), generic_accept < 0.3 (×0.10), pas de ua_ch_mismatch (×0.10) |
| **4 — Comportement navigation** | `axis_nav_behavior` | 0.15 | has_cookie (×0.25), has_referer (×0.25), asset_ratio > 0.15 (×0.25), direct_access < 0.5 (×0.25) |
| **5 — Cohérence TLS/TCP** | `axis_tls_coherence` | 0.20 | Pas d'alpn_mismatch (×0.25), window_scale OK (×0.20), tls12 < 0.1 (×0.20), pas d'http10 (×0.15), ALPN présent (×0.20) |
**Seuil** : `browser_confidence ≥ 0.55` + famille identifiée `LEGITIMATE_BROWSER`
**Propagation par cohorte** : si 70 % des sessions partageant un JA4 sont classées navigateur, les sessions `NORMAL`/`LOW` restantes avec le même JA4 sont aussi classées `LEGITIMATE_BROWSER`.
**Inférence de famille** : pour les JA4 inconnus, correspondance structurelle avec les profils `_BROWSER_JA4_PROFILES` (Chromium, Firefox, Safari, Tor_Browser) requiert `browser_confidence ≥ 0.45`.
---
## Scoring et normalisation
### Normalisation des scores (`normalize_scores`)
Les scores bruts (négatifs = anomalous) sont mappés vers **[0, 1]** avec **1 = le plus anomalous** :
```
result[mask] = clip(scores / (s_min + 1e-9), 0, 1)
```
Les scores 0 sont mis à 0.
### Seuil adaptatif (`compute_adaptive_threshold`)
```
threshold = min(percentile(scores_négatifs, ANOMALY_PERCENTILE), ANOMALY_THRESHOLD)
```
Avec `ANOMALY_PERCENTILE=5` et `ANOMALY_THRESHOLD=-0.05`.
### Pénalité de récurrence
Appliquée au `raw_anomaly_score` :
```
raw_anomaly_score = log1p(recurrence_count) × RECURRENCE_WEIGHT
```
### Niveaux de menace
| Plage de score brut | Niveau | Interprétation |
|---------------------|--------|----------------|
| `< 0.30` | **CRITICAL** | Comportement extrêmement anomalous |
| `< 0.15` | **HIGH** | Signal d'anomalie fort |
| `< 0.05` | **MEDIUM** | Anomalie modérée |
| `< 0` | **LOW** | Légèrement inhabituel |
| `≥ 0` | **NORMAL** | Trafic normal |
---
## Clustering HDBSCAN des campagnes
Lorsque `ENABLE_CLUSTERING=true`, les anomalies sont regroupées en campagnes par HDBSCAN :
```python
hdbscan.HDBSCAN(
min_cluster_size=CLUSTERING_MIN_SAMPLES, # défaut : 3
min_samples=max(2, CLUSTERING_MIN_SAMPLES 1), # défaut : 2
cluster_selection_method='eom',
)
```
**Espace de clustering** : si un Autoencoder est disponible, le clustering s'effectue dans l'**espace latent 16-dim** de l'AE. Sinon, `StandardScaler` est appliqué sur les features brutes.
**Fallback** si `hdbscan` n'est pas disponible : `DBSCAN(eps=0.5, min_samples=CLUSTERING_MIN_SAMPLES)`.
Chaque anomalie reçoit un `campaign_id` (1 = pas de cluster).
---
## Liste des features
### Features communes — modèle Applicatif (65 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 (0100) |
| `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
| 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 |
| `axis_ja4_known` | Score de l'axe 1 (JA4 connu) |
| `axis_ja4_struct` | Score de l'axe 2 (structure JA4) |
| `axis_http_modern` | Score de l'axe 3 (HTTP moderne) |
| `axis_nav_behavior` | Score de l'axe 4 (comportement navigation) |
| `axis_tls_coherence` | Score de l'axe 5 (cohérence TLS/TCP) |
#### Thèse §5 — Features avancées
| Feature | Description |
|---------|-------------|
| `path_transition_entropy` | Entropie des transitions de chemins |
| `cadence_cv` | Coefficient de variation de la cadence de requêtes |
| `burst_ratio` | Fraction de requêtes en rafale |
| `pause_ratio` | Fraction de pauses longues |
| `lag1_autocorrelation` | Autocorrélation lag-1 des inter-arrivées |
| `benford_deviation` | Déviation par rapport à la loi de Benford |
| `host_diversity` | Diversité des hôtes ciblés |
| `host_sweep_speed` | Vitesse de balayage des hôtes |
| `host_coverage_uniformity` | Uniformité de couverture des hôtes |
#### 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, 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')
d. Chargement ou entraînement EIF + AE
e. Scoring du trafic inconnu (EIF + AE)
f. Chargement ou entraînement XGBoost (si labels disponibles)
g. Combinaison des scores (formule triple voix)
h. Normalisation [0, 1]
i. Seuil adaptatif
j. Pénalité de récurrence
k. SHAP (top-3 features)
l. HDBSCAN clustering → campaign_id
m. Détection de dérive (KS test)
8. Mode multi-fenêtre (si activé) : idem sur view_ai_features_24h
9. Insertion → ml_all_scores (toutes les sessions scorées)
10. Déduplication intra-cycle (garder raw_anomaly_score le plus bas par IP)
11. Déduplication inter-cycle (TTL, skip si détecté récemment sauf aggravation ≥ 0.05)
12. Insertion → ml_detected_anomalies (anomalies filtrées)
```
---
## Détection de dérive (Kolmogorov-Smirnov)
Par feature, un **test KS bilatéral** compare la distribution courante avec la distribution d'entraînement (reconstruite par interpolation à partir d'un digest quantile p10/p25/p50/p75/p90) :
- Feature driftée si `p_value < 0.05`
- 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`.
---
## Explicabilité SHAP
Lorsque `ENABLE_SHAP=true` et que la librairie `shap` est disponible :
- Calcul des valeurs SHAP via `TreeExplainer` sur le modèle EIF
- Les **3 features les plus contributives** sont stockées dans le champ `reason`
- Format : `"feature1=valeur (±shap), feature2=valeur (±shap), feature3=valeur (±shap)"`
---
## Enrichissement Anubis
La vue `view_ai_features_1h` enrichit chaque IP via les dictionnaires Anubis selon une cascade de priorité :
1. **UA + IP combinés** (même `rule_id`) confiance maximale
2. **UA seul** (pas de condition IP)
3. **IP seul** (pas de condition UA)
4. **Correspondance ASN**
5. **Correspondance pays**
---
## Tables de sortie
### ml_detected_anomalies
Détections d'anomalies au-dessus du seuil de menace. Engine : `ReplacingMergeTree(detected_at)`, ORDER BY `(src_ip, model_name)`, TTL 7 jours.
Colonnes clés : `detected_at`, `src_ip`, `ja4`, `host`, `bot_name`, `anomaly_score`, `raw_anomaly_score`, `threat_level`, `model_name`, `recurrence`, `campaign_id`, `reason`, `anubis_bot_name`, `anubis_bot_action`, `anubis_bot_category`, plus toutes les features ML.
### ml_all_scores
Toutes les classifications (sans filtre de seuil) pour l'observabilité. Engine : `ReplacingMergeTree(detected_at)`, ORDER BY `(window_start, src_ip, ja4, host, model_name)`, TTL 7 jours.
---
## Format du journal de décisions
Le fichier `decisions.jsonl` contient des entrées JSONL structurées :
```json
{"event": "CYCLE_START", "cycle_id": "20260309T143000", "total": 5000, "human": 1500, "known_bot": 200, "correlated": 3000}
{"event": "ANOMALY", "src_ip": "203.0.113.42", "score": -0.25, "threat_level": "HIGH", "reason": "hit_velocity=45.2, fuzzing_index=0.8, ...", "campaign_id": 3}
{"event": "KNOWN_BOT", "src_ip": "198.51.100.10", "bot_name": "AhrefsBot"}
{"event": "CYCLE_END", "cycle_id": "20260309T143000", "anomalies": 15, "known_bots": 200, "duration_sec": 12.5}
```
Rotation des logs : 50 Mo max × `LOG_BACKUP_COUNT` sauvegardes (défaut : 7).
---
## Point de santé
- **URL** : `GET http://localhost:8080/`
- **Réponses** : `200 OK` (corps `OK`) ou `503 Service Unavailable` (corps `DEGRADED`)
- Exécuté dans un thread daemon au démarrage
- État mis à `False` après `MAX_CONSECUTIVE_FAILURES` (3) échecs consécutifs ClickHouse, remis à `True` dès le premier succès
---
## Persistance des modèles
| Fichier | Description |
|---------|-------------|
| `model_<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
```bash
# Construction de l'image
make build-bot-detector
# Exécution avec docker-compose
cd services/bot-detector
docker-compose up -d
# Tests
make test-bot-detector
```
### Volumes
| Chemin hôte | Chemin conteneur | Description |
|-------------|-----------------|-------------|
| `./bot_detector_logs` | `/var/log/bot_detector` | Journaux de décisions (JSONL) |
| `./bot_detector_models` | `/var/lib/bot_detector` | Modèles ML persistés |
| `./reputation/data/user_files/bot_ip.csv` | `/data/bot_ip.csv` (ro) | Liste d'IPs de bots connus |
| `./reputation/data/user_files/bot_ja4.csv` | `/data/bot_ja4.csv` (ro) | Liste de JA4 de bots connus |
| `./reputation/data/user_files/asn_reputation.csv` | `/data/asn_reputation.csv` (ro) | Labels de réputation ASN |