feat(e2e): add distributed E2E test framework with parametric traffic generation

Add run-e2e-test.sh with CLI parameters (--hits, --http-ratio, --dns, --tls,
--src-ips, --keep-analysis, --up) for configurable traffic generation. Traffic
runs from VM endpoints with multiple source IPs (alias IPs on eth0) to produce
distinct sessions for the ML pipeline. Fix curl TLS flags (--tlsv1.2 instead
of --tls-v1-2), skip redundant local verification in distributed mode, and
fix dashboard is_available() cache that never retried after ClickHouse recovery.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-04-15 00:09:32 +02:00
parent 7894d39f1c
commit f88b739992
40 changed files with 2154 additions and 337 deletions

View File

@ -100,12 +100,13 @@ INSERT (Native TCP :9000)
- **Applicatif** (L7 seulement, ~73 features, `correlated=0`) — trafic HTTP non corrélé
- **Ensemble triple voix** :
- **Extended Isolation Forest** (isotree) — scoreur non supervisé principal
- **Autoencoder** (PyTorch, architecture n→64→32→16→32→64→n) — erreur de reconstruction
- **NFEnsemble** (PyTorch, Deep Ensemble M=5 TrafficNormalizingFlow/RealNVP) — NLL et incertitude épistémique
- **XGBoost** — supervisé, entraîné sur les labels SOC (`soc_feedback`)
- **Score final** : `final = meta_learner.predict(eif_norm, ae_norm, xgb_prob, volume, correlated)` avec fallback sur pondération linéaire fixe `(1-β) × ((1-α) × eif_norm + α × ae_norm) + β × xgb_prob` (α=0.30, β=0.20)
- **MetaLearner** (régression logistique) entraîné automatiquement sur les labels accumulés (seuil: 1000 labels)
- **Seuil adaptatif** par percentile, détection de dérive conceptuelle (KS + KL divergence)
- **fleet_detector** (NetworkX) — graphe bipartite JA4×ASN, `fleet_score`, table `fleet_detections`
- **MetaLearner** (MLP) entraîné automatiquement sur les labels accumulés (seuil: 1000 labels)
- **Seuil adaptatif** par percentile, détection de dérive conceptuelle (ADWIN + KS + KL divergence)
- **Détection adversariale** : incertitude inter-modèles NFEnsemble (`nf_uncertainty > NF_UNCERTAINTY_THRESHOLD`)
- **fleet_detector** (PyTorch Geometric GraphSAGE) — graphe bipartite JA4×ASN, `fleet_score`, table `fleet_detections`
- **HDBSCAN** — regroupement en campagnes d'attaque
- **Détection de navigateur** — 6 axes multifactoriels (confiance ≥ 0.55 → `LEGITIMATE_BROWSER`)
- **ExIFFI** — importance de features native à l'EIF (alternative à SHAP)
@ -211,7 +212,7 @@ view_ip_recurrence ───┤ │ Pré- │ │
│ │
│ ┌────────────┐ │ Pour chaque branche :
│ │ Ensemble │ │ ├── Extended Isolation Forest (EIF)
│ │ triple │──▶│ ├── Autoencoder (PyTorch)
│ │ triple │──▶│ ├── NFEnsemble (M=5 NF, PyTorch)
│ │ voix │ │ └── XGBoost (supervisé)
│ └────────────┘ │
│ │ Score = MetaLearner(eif, ae, xgb) ou
@ -264,9 +265,9 @@ Les deux empreintes sont générées par **ja4ebpf** (espace utilisateur Go) à
| Capture applicative (L7) | eBPF uprobe SSL_read + kprobe tcp_recvmsg |
| Corrélation en mémoire | Go 1.24.6 (256-shard manager, goroutines) |
| Détection ML — EIF | Python 3.11 + isotree |
| Détection ML — Autoencoder | Python 3.11 + PyTorch |
| Détection ML — NFEnsemble | Python 3.11 + PyTorch |
| Détection ML — Supervisé | Python 3.11 + XGBoost |
| Détection ML — Ensemble | Python 3.11 + MetaLearner (régression logistique) |
| Détection ML — Ensemble | Python 3.11 + MetaLearner (MLP) |
| Clustering de campagnes | HDBSCAN + NetworkX (fleet detection) |
| Explicabilité | SHAP + ExIFFI |
| Backend dashboard | FastAPI + Jinja2 (Python 3.11) |

View File

@ -70,15 +70,16 @@ Table d'ingestion brute — cible directe des INSERTs du correlator.
### http_logs
Table de logs HTTP parsés et enrichis — alimentée par la vue matérialisée
`mv_http_logs`.
`mv_http_logs`. La MV utilise `nullIf` pour `src_ip` et `dst_ip` afin de
convertir les chaînes vides en `0.0.0.0` au lieu de provoquer une erreur de parse.
| Colonne | Type | Description |
|---------|------|-------------|
| `time` | DateTime | Horodatage de la requête |
| `log_date` | Date DEFAULT `toDate(time)` | Clé de partition |
| `src_ip` | IPv4 | IP source du client |
| `src_ip` | IPv4 | IP source du client (vide → `0.0.0.0` via `nullIf`) |
| `src_port` | UInt16 | Port source |
| `dst_ip` | IPv4 | IP destination du serveur |
| `dst_ip` | IPv4 | IP destination du serveur (vide → `0.0.0.0` via `nullIf`) |
| `dst_port` | UInt16 | Port destination |
| `src_asn` | UInt32 | ASN source (enrichi via dict_iplocate_asn) |
| `src_country_code` | LowCardinality(String) | Code pays |

View File

@ -288,7 +288,10 @@ Variables d'environnement clés :
| `ANOMALY_THRESHOLD` | `-0.05` | Seuil de détection d'anomalies (fallback) |
| `CYCLE_INTERVAL_SEC` | `300` | Intervalle entre cycles de détection (secondes) |
| `RETRAIN_INTERVAL_HOURS` | `24` | Intervalle de réentraînement des modèles |
| `AE_WEIGHT` | `0.30` | Poids de l'Autoencoder dans l'ensemble (α) |
| `AE_WEIGHT` | `0.30` | Poids du NFEnsemble dans l'ensemble (α) |
| `NF_UNCERTAINTY_THRESHOLD` | `1.0` | Seuil d'incertitude inter-modèles pour détection adversariale |
| `MIN_HUMAN_BASELINE` | `500` | Nombre minimum de sessions humaines pour entraîner l'IF |
| `BASELINE_ACCEPT_UNKNOWN` | `false` | Mode test : fallback ASN `unknown` si baseline ISP insuffisante |
| `XGB_WEIGHT` | `0.20` | Poids de XGBoost dans l'ensemble (β) |
| `ENABLE_MULTIWINDOW` | `false` | Active les variantes 24h (Complet/Applicatif) |
| `HEALTH_PORT` | `8080` | Port du endpoint /health |
@ -462,7 +465,7 @@ Télécharge et génère tous les fichiers CSV de référence (bot IPs, JA4, ASN
|
+-----------------+ SELECT view_ai_features
| bot-detector |<-- view_thesis_features
| (EIF+AE+XGB) |
| (EIF+NF+XGB) |
| |--> INSERT ml_all_scores, ml_detected_anomalies
+-----------------+
+-----------------+

View File

@ -36,6 +36,15 @@ make build-dashboard # Image FastAPI + Jinja2
make test-all
```
### Tests E2E distribués (full stack : capture + ML + dashboard)
```bash
make e2e-up # Créer les 4 VMs (centos8/rocky9/rocky10/analysis)
make e2e-down # Détruire les VMs E2E
make test-e2e # Test E2E complet (capture + ML + dashboard)
make test-e2e-quick # Test E2E rapide (trafic réduit, 1 cycle bot-detector)
```
### Tests par service
| Service | Commande | Détails |
@ -122,7 +131,7 @@ uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000
| Librairie | Usage |
|-----------|-------|
| `isotree` | Extended Isolation Forest (scoreur principal non supervisé) |
| `torch` | Autoencoder (PyTorch, architecture n->64->32->16->32->64->n) |
| `torch` | NFEnsemble / TrafficNormalizingFlow (PyTorch, Deep Ensemble M=5) |
| `xgboost` | Modèle supervisé (entraîné sur les labels SOC) |
| `hdbscan` | Clustering de campagnes d'attaque |
| `shap` | Explicabilité des scores d'anomalie |
@ -139,8 +148,8 @@ services/bot-detector/bot_detector/
├── log.py # Configuration du logging
├── infra.py # Connexion ClickHouse, health check
├── preprocessing.py # Feature engineering, filtrage, normalisation
├── models.py # EIF, Autoencoder, XGBoost (entraînement + scoring)
├── scoring.py # Ensemble triple voix, seuils adaptatifs
├── models.py # EIF, NFEnsemble/TrafficNormalizingFlow, XGBoost (entraînement + scoring)
├── scoring.py # Ensemble triple voix, seuils adaptatifs, ADWIN drift
├── browser.py # Détection de navigateur 5 axes multifactoriels
├── pipeline.py # Orchestration du cycle de détection
├── cycle.py # Boucle principale (cycle de 5 minutes)

View File

@ -3,9 +3,9 @@
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
(Extended Isolation Forest + NFEnsemble + XGBoost) piloté par un **méta-learner
MLP**, enrichi par l'explicabilité **ExIFFI** et **SHAP**,
le clustering HDBSCAN, la détection de flottes coordonnées (PyTorch Geometric GraphSAGE) et
la surveillance de performance par cycle.
---
@ -23,8 +23,8 @@ __main__.py Point d'entrée (python -m bot_detector)
├─ 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
│ ├─ models.py EIF, NFEnsemble/TrafficNormalizingFlow (PyTorch), XGBoost
│ └─ scoring.py Normalisation, MetaLearner MLP, seuil adaptatif, ExIFFI, SHAP, HDBSCAN, dérive ADWIN+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)
@ -44,11 +44,11 @@ __main__.py Point d'entrée (python -m bot_detector)
| `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 |
| `scoring.py` | 564 | `MetaLearner` MLP, normalisation, seuil adaptatif, ExIFFI, SHAP top-5, HDBSCAN, dérive ADWIN+KS+KL |
| `models.py` | 484 | `NFEnsemble`/`TrafficNormalizingFlow`, 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 |
| `fleet.py` | 174 | `build_fleet_graph()`, `detect_fleet_communities()`, `enrich_with_fleet_score()`PyTorch Geometric GraphSAGE + 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` |
@ -85,15 +85,18 @@ Toute la configuration est lue via `os.getenv()` dans `config.py`. Aucun fichier
| `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 |
| `MIN_HUMAN_BASELINE` | int | `500` | Nombre minimum de sessions humaines pour entraîner l'IF |
| `BASELINE_ACCEPT_UNKNOWN` | bool | `false` | Mode test : utiliser ASN `unknown` comme fallback si baseline ISP insuffisante |
### Autoencoder
### NFEnsemble (Deep Ensemble M=5 Normalizing Flows)
| 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_WEIGHT` | float | `0.30` | Poids du NFEnsemble dans le score combiné (plage ]0, 1[) |
| `AE_EPOCHS` | int | `50` | Nombre d'époques d'entraînement par membre |
| `AE_LATENT_DIM` | int | `16` | Dimension de l'espace latent (coupling layers RealNVP) |
| `AE_LEARNING_RATE` | float | `1e-3` | Taux d'apprentissage Adam |
| `NF_UNCERTAINTY_THRESHOLD` | float | `1.0` | Seuil de variance inter-modèles pour la détection adversariale |
### XGBoost
@ -154,8 +157,8 @@ Le bot-detector utilise trois modèles en parallèle, combinés par une pondéra
└──────────────────────┘ │
× (1 AE_WEIGHT)
┌──────────────────────┐ │
TrafficAutoEncoder │ ├──→ combined_norm
│ (PyTorch) │──→ ae_norm (01)
NFEnsemble (M=5 NF) │ ├──→ combined_norm
│ (PyTorch RealNVP) │──→ ae_norm (01)
└──────────────────────┘ × AE_WEIGHT
× (1 XGB_WEIGHT)
┌──────────────────────┐ │
@ -208,24 +211,38 @@ isotree.IsolationForest(
**Calibration** : le score isotree brut (∈ [0, 1], >0.5 = anomalous) est converti en convention sklearn : `sklearn_equiv = 0.5 isotree_score`.
### TrafficAutoEncoder (PyTorch)
### NFEnsemble — Deep Ensemble M=5 Normalizing Flows (PyTorch)
Architecture symétrique encodeur-décodeur :
Le NFEnsemble est un **deep ensemble** de M=5 `TrafficNormalizingFlow` indépendants, chacun
basé sur l'architecture RealNVP (Dinh et al., 2017) avec couches de coupling affines,
permutations fixes et batch normalization.
**TrafficNormalizingFlow** (membre individuel) :
```
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))
Couche 1 : Permutation fixe → RealNVP coupling → BatchNorm
Couche 2 : Permutation fixe → RealNVP coupling → BatchNorm
...
Sortie : log-probabilité exacte par la règle de changement de variable
```
- 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
- **Coupling layers** : transformations affines conditionnées (scale + shift)
- **Permutations** : permutations fixes alternées entre les couches
- **BatchNorm** : normalisation par batch entre les couches de coupling
- **Optimiseur** : `Adam(lr=1e-3, weight_decay=1e-5)`
- **Perte** : Negative Log-Likelihood (NLL) — `-log p(x)` via la règle de changement de variable
- **Entraînement** : 50 époques, batch_size=256, échantillonnage bootstrap (avec remise) par membre
- **Score** : `-log p(x)` — plus élevé = plus anomalous
- **Normalisation des entrées** : min-max [0, 1] par feature
**NFEnsemble** (M=5, Lakshminarayanan et al., 2017) :
- **Incertitude** : la variance inter-modèles des scores `-log p(x)` quantifie l'incertitude épistémique.
- Faible incertitude → dérive organique (les modèles s'accordent)
- Forte incertitude → dérive adversariale probable (les modèles divergent)
- **Seuil d'incertitude** : `NF_UNCERTAINTY_THRESHOLD` (défaut 1.0) — au-delà, `is_adversarial_drift = True`
- **Score final** : moyenne des `-log p(x)` sur les M modèles (rétro-compatible avec le pipeline)
- **Sérialisation** : `state_dict()` contient `ensemble_size`, `n_features`, et les `state_dict` de chaque membre
### XGBoost (supervisé)
@ -324,7 +341,7 @@ hdbscan.HDBSCAN(
)
```
**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.
**Espace de clustering** : si un NFEnsemble est disponible, le clustering s'effectue dans l'**espace latent moyen** du Deep Ensemble. Sinon, `StandardScaler` est appliqué sur les features brutes.
**Fallback** si `hdbscan` n'est pas disponible : `DBSCAN(eps=0.5, min_samples=CLUSTERING_MIN_SAMPLES)`.
@ -506,20 +523,20 @@ Chaque anomalie reçoit un `campaign_id` (1 = pas de cluster).
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)
d. Chargement ou entraînement EIF + NFEnsemble
e. Scoring du trafic inconnu (EIF + NFEnsemble, incertitude inter-modèles)
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
l. ExIFFI (importance par profondeur d'isolation EIF) + erreur NF 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
o. Détection de dérive (ADWIN + KS test + KL divergence)
p. Alerte drift adversarial (dérive simultanée multiple features → direction commune, ou incertitude NF > NF_UNCERTAINTY_THRESHOLD)
8. Analyse de flotte (fleet.py) : graphe bipartite JA4×ASN → communautés GraphSAGE → 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)
@ -532,9 +549,15 @@ Chaque anomalie reçoit un `campaign_id` (1 = pas de cluster).
---
## Détection de dérive (KS + KL divergence)
## Détection de dérive (ADWIN + KS + KL divergence)
Par feature, deux tests comparent la distribution courante avec la distribution d'entraînement :
Par feature, trois mécanismes comparent la distribution courante avec la distribution d'entraînement :
**ADWIN (Adaptive Windowing)** (River) :
- Détection en ligne par fenêtre adaptative chaque feature est surveillée par un détecteur ADWIN indépendant
- Propriété `drift_detected` (bool) vrai quand un changement de distribution est détecté dans la fenêtre
- Pas de seuil manuel ADWIN ajuste automatiquement la taille de fenêtre
- Remplace la dérive KS+KL pour les features continues en temps réel
**Test KS (Kolmogorov-Smirnov)** :
- Distribution reconstruite par interpolation à partir d'un digest quantile 9 points (p5, p10, p25, p50, p75, p90, p95)
@ -543,9 +566,9 @@ Par feature, deux tests comparent la distribution courante avec la distribution
**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`
- Détection de **drift adversarial** : si 30% des features dérivent simultanément dans la même direction alerte `ADVERSARIAL_DRIFT`. Également déclenché quand `nf_uncertainty > NF_UNCERTAINTY_THRESHOLD` alerte `ADVERSARIAL_DRIFT_NF`
**Règle de décision** : une feature est en drift si KS **ou** KL dépasse son seuil.
**Règle de décision** : une feature est en drift si ADWIN détecte un changement, ou 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
@ -556,12 +579,13 @@ Par feature, deux tests comparent la distribution courante avec la distribution
## 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`) :
Remplace la pondération linéaire fixe `(1-XGB_W)×((1-AE_W)×eif + AE_W×ae) + XGB_W×xgb` par un MLP appris (`scoring.MetaLearner`) :
```
P(bot) = logistic(w1×eif + w2×ae + w3×xgb + w4×volume + w5×correlated + bias)
P(bot) = MLP(w1×eif + w2×ae + w3×xgb + w4×volume + w5×correlated + bias)
```
- **Architecture** : MLP (Multi-Layer Perceptron) avec couches cachées remplace la régression logistique
- **Entraînement** : sur l'historique `ml_all_scores` JOIN `soc_feedback` (labels SOC + KNOWN_BOT + ANUBIS_DENY + LEGITIMATE_BROWSER)
- **Seuil** : activé seulement si 1000 labels disponibles sinon fallback aux poids fixes
- **Transparence** : poids appris journalisés dans `ml_performance_metrics` pour audit SOC
@ -578,10 +602,10 @@ En complément de SHAP, le module expose deux méthodes d'importance de features
- 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)²`
**Erreur NF par feature** (`compute_ae_feature_errors`) :
- Score de reconstruction NF feature par feature : `err_i = (x_i - x̂_i)²` dans l'espace latent
- Pour chaque anomalie, les features avec la plus grande erreur de reconstruction sont identifiées
- Expose quelles dimensions l'autoencoder ne parvient pas à reconstruire
- Expose quelles dimensions le Normalizing Flow 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.
@ -592,8 +616,8 @@ Les deux méthodes sont disponibles dans le champ `shap_features` des résultats
Détecte les **botnets coordonnés** utilisant des JA4 et ASN rotatifs via analyse de graphe bipartite :
1. **Construction du graphe** : nœuds JA4 ASN, arêtes IP observées dans le cycle
2. **Projection** : projection du graphe bipartite sur les nœuds JA4
3. **Communautés** : algorithme de Louvain (NetworkX) sur le graphe projeté
2. **Embedding** : PyTorch Geometric GraphSAGE pour apprendre des embeddings de nœuds
3. **Communautés** : clustering dans l'espace d'embedding GraphSAGE
4. **Score de flotte** : `fleet_score = taille_communauté × densité_arêtes / log(nb_ASN)`
5. **Enrichissement** : les IPs membres reçoivent un malus proportionnel au fleet_score
@ -613,7 +637,7 @@ Enregistre par cycle dans `ml_performance_metrics` :
| `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) |
| `alert_flags` | Alertes émises (CALIBRATION_HIGH/LOW, DRIFT, CORRELATION, LATENCY, ADVERSARIAL_DRIFT_NF) |
**Seuils d'alerte** :
- `anomaly_rate > 10%` `CALIBRATION_HIGH`
@ -628,11 +652,8 @@ Enregistre par cycle dans `ml_performance_metrics` :
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**
1. **IP/CIDR** correspondance exacte d'adresse ou de sous-réseau
2. **ASN** correspondance par numéro d'ASN
---

View File

@ -68,12 +68,14 @@ Le programme `bpf/tc_capture.c` est attaché à l'interface réseau via **TC (Tr
- Envoyé dans le RingBuffer `rb_tcp_syn` (16 MB)
**ClientHello TLS** : détection du type 0x16 (Handshake) et sous-type 0x01 (ClientHello).
- `bpf_skb_load_bytes()` pour capturer 512 octets du payload
- `bpf_skb_load_bytes()` avec tailles en cascade (512 → 256 → 128) pour capturer SNI et extensions
- La taille réellement copiée est stockée dans `payload_len`
- Envoyé dans le RingBuffer `rb_tls_hello` (16 MB)
**HTTP en clair (port 80/8080)** : pour les connexions non chiffrées.
- SYN/FIN/RST exclus (uniquement les segments porteurs de données)
- Jusqu'à 4096 octets via `bpf_skb_load_bytes()`
- `bpf_skb_load_bytes()` avec tailles en cascade (256 → 128 → 64)
- La taille réellement copiée est stockée dans `payload_len`
- Envoyé dans le RingBuffer `rb_http_plain` (32 MB)
### Uprobe SSL_read — Couche L7
@ -124,6 +126,7 @@ Buffer reçu (SSL data ou HTTP plain)
| Champ | Description |
|-------|-------------|
| `src_ip`, `src_port` | Clé de corrélation |
| `dst_ip`, `dst_port` | Destination IP et port (extrait du SYN) |
| `ttl` | Time To Live initial |
| `df_bit` | Don't Fragment bit |
| `ip_id` | IP Identification (0 = Linux/VPN/spoofé) |
@ -138,7 +141,7 @@ Buffer reçu (SSL data ou HTTP plain)
| Champ | Description |
|-------|-------------|
| `tls_version` | Version TLS |
| `tls_version` | Version TLS la plus haute annoncée (extrait des SupportedVersions) |
| `ciphers` | Liste suites cryptographiques |
| `extensions` | Liste extensions TLS |
| `elliptic_curves` | Courbes elliptiques supportées |
@ -167,7 +170,7 @@ Buffer reçu (SSL data ou HTTP plain)
| Champ | Description |
|-------|-------------|
| `h2_header_table_size` | SETTINGS ID 1 (-1 si absent) |
| `h2_header_table_size` | SETTINGS ID 1 (`nil` si absent du preface, omis dans le JSON) |
| `h2_enable_push` | SETTINGS ID 2 |
| `h2_max_concurrent_streams` | SETTINGS ID 3 |
| `h2_initial_window_size` | SETTINGS ID 4 |
@ -254,7 +257,8 @@ services/ja4ebpf/
│ ├── dispatcher/
│ │ └── dispatcher.go # Routeur Magic Bytes (ProtoHTTP1/2/Unknown)
│ ├── correlation/
│ │ ── manager.go # Gestionnaire sessions 256-shard
│ │ ── manager.go # Gestionnaire sessions 256-shard
│ │ └── session.go # Structs L3L4, TLSInfo, SessionState
│ └── writer/
│ └── writer.go # Writer ClickHouse (batch + retry)
├── packaging/

189
docs/testing-e2e.md Normal file
View File

@ -0,0 +1,189 @@
# Test E2E distribué — Stack de test complète ja4-platform
## Objectif
Valider le pipeline complet de bout en bout :
```
trafic simulé → ja4ebpf (capture eBPF) → ClickHouse (stockage + agrégation MV)
→ bot-detector (ML) → dashboard (visualisation)
```
Les tests unitaires et d'intégration existants testent la capture eBPF isolément.
Le test E2E distribué valide la **chaine complète** sur une architecture multi-VMs.
## Architecture
```
HOST (orchestrateur — run-e2e-test.sh)
├── centos8 (el8) ── nginx + ja4ebpf ──eth1──┐
├── rocky9 (el9) ── nginx + ja4ebpf ──eth1──┤ réseau privé ja4-e2e
├── rocky10 (el10) ── nginx + ja4ebpf ──eth1──┤ 192.168.42.0/24
│ │
│ analysis ────┘ 192.168.42.10 (fixe)
│ ├── Docker ClickHouse :9000/:8123
│ ├── Docker bot-detector :8080
│ └── Docker dashboard :8000
└── Trafic curl/httpx → endpoints :80/:443
```
### VMs
| VM | Rôle | Box | IP | Services |
|----|------|-----|-----|----------|
| centos8 | Endpoint el8 | centos/8 | DHCP eth0 | nginx, ja4ebpf |
| rocky9 | Endpoint el9 | generic/rocky9 | DHCP eth0 | nginx, ja4ebpf |
| rocky10 | Endpoint el10 | almalinux/10 | DHCP eth0 | nginx, ja4ebpf |
| analysis | Serveur central | generic/rocky9 | 192.168.42.10 (eth1 fixe) | Docker: ClickHouse, bot-detector, dashboard |
### Réseau
- **eth0** (NAT libvirt) : SSH depuis le host, réception du trafic de test
- **eth1** (réseau privé `ja4-e2e`) : communication inter-VMs
- Les endpoints utilisent eth1 pour envoyer les logs ja4ebpf vers `192.168.42.10:9000`
- Le host accède au dashboard et ClickHouse via l'IP eth0 de la VM analysis (routée via libvirt NAT)
## Pipeline de données
```
1. HOST → endpoints curl/httpx génère du trafic HTTP/HTTPS/H2
2. ja4ebpf Capture eBPF : TLS ClientHello (JA4), TCP SYN (L3/L4),
HTTP via uprobe SSL_read (L7)
3. → ClickHouse :9000 ja4ebpf écrit dans ja4_logs.http_logs_raw (batch 100 lignes, flush 1s)
4. MV mv_http_logs Materialized View : http_logs_raw → http_logs (parsed, corrélé)
5. MV mv_agg_host_ip_ja4_1h Agrégation horaire par (host, src_ip, ja4) → agg_host_ip_ja4_1h
6. view_ai_features_1h Vue qui joint les features pour le ML
7. bot-detector Cycle ML toutes les 30s (config de test) :
- Lit view_ai_features_1h
- Pipeline : NFEnsemble (M=5 NF) → ADWIN (River) → MLP fusion
- Écrit ml_all_scores + ml_detected_anomalies
8. dashboard API FastAPI sur :8000, requête ClickHouse
```
## Principe clé : DB vierge avant chaque test
Avant chaque exécution E2E, la Phase 1 fait un `docker compose down -v` qui supprime tous les volumes Docker (y compris les données ClickHouse). Cela garantit que :
- ClickHouse démarre avec un schéma vierge
- Les données observées sont exclusivement celles générées par le test
- Les vérifications de la Phase 5 sont déterministes
## Stack Docker (VM analysis)
Définie dans `tests/vm/analysis/docker-compose.yml` :
### ClickHouse
- Image : `clickhouse/clickhouse-server:24.8`
- Ports : `0.0.0.0:9000` (native, ja4ebpf), `0.0.0.0:8123` (HTTP, API)
- Schéma : 12 fichiers SQL de `shared/clickhouse/*.sql`, exécutés via `clickhouse-init.sh`
- Credentials : `user=default, password=""` (patché par clickhouse-init.sh)
- Dictionnaires : CSV stubs de `tests/integration/platform/csv-stubs/`
### bot-detector
- Build : `services/bot-detector/bot_detector/Dockerfile`
- Port : `0.0.0.0:8080` (health check)
- Configuration accélérée pour les tests :
- `CYCLE_INTERVAL_SEC: 30` (vs 300 en prod)
- `MIN_VALID_FEATURE_RATIO: 0.10` (vs 0.50 en prod)
- SHAP, clustering, multi-fenêtres désactivés
### dashboard
- Build : `services/dashboard/Dockerfile`
- Port : `0.0.0.0:8000`
- Routes de vérification : `/health`, `/api/overview`, `/api/detections`
## Phases du test (run-e2e-test.sh)
| Phase | Description | Durée |
|-------|-------------|-------|
| 0 | Setup : démarrage VMs, rsync, découverte IPs | ~2 min (si VMs existantes) |
| 1 | Stack analysis : purge volumes (DB vierge), `docker compose up -d --build`, attente healthy | ~3 min |
| 2 | Endpoints : nginx + ja4ebpf (DSN → analysis:9000), en parallèle | ~1 min |
| 3 | Trafic : 500 req/VM × 3 VMs, HTTP/HTTPS/H2, méthodes variées | ~5 min |
| 4 | Attente : flush ja4ebpf 15s, poll ml_all_scores (max 120s) | ~2 min |
| 5 | Vérifications : 15+ checks sur 4 layers | ~1 min |
## Vérifications (Phase 5)
### Layer 1 — Données brutes
- `ja4_logs.http_logs_raw` : lignes > 0
- `uniqExact(host)` : >= 2 hôtes distincts (multi-source)
### Layer 2 — Pipeline ClickHouse (MVs)
- `ja4_logs.http_logs` : JA4 fingerprints capturés
- `ja4_logs.http_logs` : méthodes HTTP capturées (L7 via uprobe SSL_read)
- `ja4_processing.agg_host_ip_ja4_1h` : agrégation horaire peuplée
- `ja4_processing.view_ai_features_1h` : features ML disponibles
### Layer 3 — ML bot-detector
- `ja4_processing.ml_all_scores` : classifications produites
- `ja4_processing.ml_detected_anomalies` : anomalies détectées (optionnel)
- Health check `:8080`
### Layer 4 — Dashboard
- `/health` : OK
- `/api/overview` : données non-vides
- `/api/detections` : accessible
## Utilisation
```bash
# Créer les 4 VMs
make e2e-up
# Test complet (500 req/VM, ~15 min)
make test-e2e
# Test rapide (100 req/VM)
make test-e2e-quick
# Garder les VMs après le test (pour debug)
KEEP_RUNNING=true make test-e2e
# Détruire les VMs
make e2e-down
```
## Accès manuel (debug)
```bash
# ClickHouse — vérifier les données
curl "http://192.168.42.10:8123/?query=SELECT+count()+FROM+ja4_logs.http_logs"
# Dashboard
curl http://192.168.42.10:8000/health
curl http://192.168.42.10:8000/api/overview | python3 -m json.tool
# bot-detector
curl http://192.168.42.10:8080/
# SSH dans la VM analysis
cd tests/vm && vagrant ssh analysis
# Logs des conteneurs
vagrant ssh analysis -- "docker logs bot_detector_ai --tail 50"
vagrant ssh analysis -- "docker logs ja4-dashboard --tail 50"
```
## Fichiers
| Fichier | Rôle |
|---------|------|
| `tests/vm/Vagrantfile` | Définition des 4 VMs + réseau ja4-e2e |
| `tests/vm/provision-analysis.sh` | Provisionneur VM analysis (Docker, firewall) |
| `tests/vm/analysis/docker-compose.yml` | Stack centralisée CH + bot-detector + dashboard |
| `tests/vm/run-e2e-test.sh` | Orchestrateur E2E 5 phases |
| `tests/vm/run-tests-vm.sh` | Script endpoint (modifié pour CH_HOST) |
| `Makefile` | Cibles e2e-up, e2e-down, test-e2e, test-e2e-quick |
## Dépannage
| Problème | Diagnostic |
|----------|------------|
| ClickHouse inaccessible | `vagrant ssh analysis -- "docker ps"` ; vérifier le port binding |
| ja4ebpf n'écrit pas | `vagrant ssh rocky9 -- "cat /tmp/ja4ebpf.log \| tail 20"` |
| Pas de JA4 | Le hook TC nécessite CAP_BPF ; vérifier `dmesg \| grep bpf` |
| bot-detector ne démarre pas | `vagrant ssh analysis -- "docker logs bot_detector_ai"` |
| Pas de données ML | Volume insuffisant pour les fenêtres d'agrégation horaire |
| Dashboard vide | Le bot-detector doit avoir complété au moins 1 cycle (30s) |

View File

@ -11,7 +11,7 @@
## Résumé
Ce document présente une architecture opérationnelle de détection et classification du trafic HTTP malveillant, s'inscrivant dans la continuité des approches de génération 3 (fingerprinting multi-protocole et ML comportemental). Le système exploite 96 features organisées en 8 familles couvrant les couches réseau L3 à L7, corrélant des signaux TCP, TLS et HTTP en un vecteur unifié par session. La détection repose sur un ensemble triple-voix combinant un Extended Isolation Forest (EIF), un Normalizing Flow (NF) et XGBoost, fusionnés par un méta-modèle MLP (Multi-Layer Perceptron) non-linéaire calibré sur les étiquettes accumulées. L'explicabilité est assurée par l'importance des features par profondeur d'isolation (EIF) et SHAP TreeExplainer (XGBoost). Le clustering de campagnes est réalisé par HDBSCAN dans l'espace latent 16 dimensions de l'autoencodeur, et la détection de flottes coordonnées par graphes bipartis via NetworkX. Le fingerprinting HTTP/2 passif — extraction des trames SETTINGS, WINDOW_UPDATE et de l'ordre des pseudo-headers côté serveur — exploite un signal déjà utilisé par des solutions industrielles (Akamai, Cloudflare, F5), ici implémenté via eBPF. L'infrastructure repose sur 16 modules Python (4 800 lignes), une base ClickHouse à double schéma (ja4_logs bruts TTL 2 h, ja4_processing agrégés TTL 7 j), des cycles d'analyse de 300 secondes, et traite en production plus de 3 millions de logs, environ 34 000 sessions par cycle, avec approximativement 777 anomalies détectées par cycle (≈ 2,3 % — chiffre opérationnel brut, non validé comme taux de détection). Le système intègre un moteur de profiling dynamique automatique des navigateurs (HDBSCAN sur les vecteurs H2 observés, centroïdes auto-appris, scoring temps réel par distance normalisée) qui s'adapte aux évolutions des piles HTTP/2 sans intervention manuelle.
Ce document présente une architecture opérationnelle de détection et classification du trafic HTTP malveillant, s'inscrivant dans la continuité des approches de génération 3 (fingerprinting multi-protocole et ML comportemental). Le système exploite 96 features organisées en 8 familles couvrant les couches réseau L3 à L7, corrélant des signaux TCP, TLS et HTTP en un vecteur unifié par session. La détection repose sur un ensemble triple-voix combinant un Extended Isolation Forest (EIF), un Normalizing Flow (NF) et un Hoeffding Adaptive Tree (HAT, River), fusionnés par un MLP non-linéaire calibré sur les étiquettes accumulées. L'explicabilité est assurée par l'importance des features par profondeur d'isolation (EIF) et SHAP TreeExplainer (HAT). Le clustering de campagnes est réalisé par HDBSCAN dans l'espace latent du Normalizing Flow, et la détection de flottes coordonnées par GraphSAGE (PyTorch Geometric). Le fingerprinting HTTP/2 passif — extraction des trames SETTINGS, WINDOW_UPDATE et de l'ordre des pseudo-headers côté serveur — exploite un signal déjà utilisé par des solutions industrielles (Akamai, Cloudflare, F5), ici implémenté via eBPF. L'infrastructure repose sur 16 modules Python (4 800 lignes), une base ClickHouse à double schéma (ja4_logs bruts TTL 2 h, ja4_processing agrégés TTL 7 j), des cycles d'analyse de 300 secondes, et traite en production plus de 3 millions de logs, environ 34 000 sessions par cycle, avec approximativement 777 anomalies détectées par cycle (≈ 2,3 % — chiffre opérationnel brut, non validé comme taux de détection). Le système intègre un moteur de profiling dynamique automatique des navigateurs (HDBSCAN sur les vecteurs H2 observés, centroïdes auto-appris, scoring temps réel par distance normalisée) qui s'adapte aux évolutions des piles HTTP/2 sans intervention manuelle.
**Mots-clés** : fingerprinting réseau, JA4+, HTTP/2 fingerprinting, détection de bots, Extended Isolation Forest, autoencodeurs, ensemble hybride, corrélation TCP/TLS/HTTP, WAF, classification de trafic, apprentissage semi-supervisé, clustering HDBSCAN

View File

@ -43,9 +43,9 @@ Ce document décrit une architecture opérationnelle s'inscrivant dans la contin
1. **Corrélation TCP/TLS/HTTP** en temps réel via ja4ebpf (clé : `src_ip:src_port`, 256 shards, timeout orphelin 500 ms)
2. **Fingerprinting HTTP/2 passif** : extraction des trames SETTINGS, WINDOW_UPDATE, PRIORITY et de l'ordre des pseudo-headers directement depuis le stream TCP — approche déjà exploitée par des solutions industrielles (Akamai, Cloudflare, F5), ici implémentée via eBPF
3. **Architecture EIF bifurquée** : modèle complet (≈ 45 features L3→L7) et modèle applicatif (≈ 35 features L7 uniquement), évitant le biais de zérotage sur le trafic non corrélé — choix pragmatique de gestion des données manquantes
4. **Ensemble triple-voix avec fusion par MLP non-linéaire** : combinaison EIF + NF + XGBoost avec méta-modèle MLP apprenant les interactions non-linéaires entre les trois voix
4. **Ensemble triple-voix avec fusion par MLP non-linéaire** : combinaison EIF + NF + HAT (River) avec fusion MLP apprenant les interactions non-linéaires entre les trois voix
5. **HDBSCAN dans l'espace latent AE** : clustering de campagnes par similarité de comportement compressé en 16 dimensions
6. **Détection de dérive adversariale** : distinction entre dérive organique (mises à jour navigateur) et manipulation directionnelle coordonnée
6. **Détection de dérive adversariale** : distinction entre dérive organique (mises à jour navigateur) et manipulation adversariale via incertitude épistémique de Deep Ensembles (NFEnsemble M=5)
7. **8 features comportementales avancées** : application de statistiques standard (déviation de Benford, entropie de transition markovienne, autocorrélation lag-1, délai root-to-first-asset, diversité de hosts, uniformité de couverture cross-host) au domaine de la détection de bots
8. **Graphes bipartis NetworkX** pour la détection de flottes

View File

@ -58,7 +58,7 @@ La deuxième couche de défense statique repose sur des dictionnaires de réputa
[Anubis](https://github.com/TecharoHQ/anubis) est un système de règles communautaire en YAML permettant de définir des actions granulaires par bot identifié. Les quatre actions disponibles sont :
- **ALLOW** : autorisation explicite (bots légitimes : Googlebot, Bingbot, bots de recherche académique)
- **DENY** : blocage avec retour 403 Forbidden signal de vérité terrain fort pour l'entraînement XGBoost
- **DENY** : blocage avec retour 403 Forbidden signal de vérité terrain fort pour l'entraînement HAT (River)
- **WEIGH** : ajout d'un score de pondération sans blocage signal auxiliaire dans le vecteur de features
- **CHALLENGE** : redirection vers un challenge (PoW ou CAPTCHA)
@ -69,7 +69,7 @@ La deuxième couche de défense statique repose sur des dictionnaires de réputa
**Priorité de correspondance** : `COALESCE(IP match, ASN match)` une correspondance CIDR précise sur l'IP prend la priorité sur la correspondance ASN plus générale. Cela reflète le principe que l'information la plus spécifique est la plus fiable.
**Valeur pour le pipeline ML** :
- Les sessions `DENY` fournissent des étiquettes de bot à haute confiance pour l'entraînement supervisé de XGBoost, sans nécessiter d'annotation manuelle.
- Les sessions `DENY` fournissent des étiquettes de bot à haute confiance pour l'entraînement incrémental du HAT (River), sans nécessiter d'annotation manuelle.
- Les sessions `WEIGH` contribuent une feature binaire `anubis_is_flagged` dans la famille F7, enrichissant le vecteur de features sans déclencher de blocage.
---
@ -292,7 +292,7 @@ XGBoost ([Chen & Guestrin, 2016](https://arxiv.org/abs/1603.02754)) est un algor
**Limites des approches supervisées** :
- **Concept drift** : un modèle entraîné sur des bots de 2024 peut être aveugle aux nouvelles techniques de 2025
- **Rareté des étiquettes** : annoter manuellement des millions de sessions HTTP est coûteux et sujet à erreur
- **Bruit des étiquettes** : les labels fournis par les analystes SOC contiennent des erreurs systématiques (faux positifs mal corrigés, biais de confirmation). Ces étiquettes bruitées empoisonnent le modèle supervisé un problème bien documenté par [Northcutt et al., 2021 (Cleanlab)](https://arxiv.org/abs/1911.00068) qui montre que les jeux de données réels contiennent 8 à 20 % de labels incorrects. Pour mitiger ce risque, notre pipeline intègre un filtre Cleanlab avant l'entraînement XGBoost (détail §3.8).
- **Bruit des étiquettes** : les labels fournis par les analystes SOC contiennent des erreurs systématiques (faux positifs mal corrigés, biais de confirmation). Ces étiquettes bruitées empoisonnent le modèle supervisé un problème bien documenté par [Northcutt et al., 2021 (Cleanlab)](https://arxiv.org/abs/1911.00068) qui montre que les jeux de données réels contiennent 8 à 20 % de labels incorrects. Pour mitiger ce risque, notre pipeline intègre un filtre Cleanlab avant l'apprentissage incrémental du HAT (détail §3.8).
- **Biais de jeu de données** : les modèles entraînés sur des données de laboratoire (CICIDS2017, NSL-KDD) généralisent mal au trafic en production, comme documenté dans la littérature sur les benchmarks de détection d'intrusions
- **Attaque par évasion adversariale** : un attaquant ayant accès ou connaissance du modèle peut crafting des sessions qui maximisent le score de légitimité
@ -413,20 +413,19 @@ Le système de détection combine trois « voix » complémentaires :
┌──────────────┼──────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌────▼──────┐
│ EIF │ │ NF │ │ XGBoost
│ (semi- │ │ (Normal- │ │(supervisé)
│supervisé) │ │ izing │ │
│ │ │ Flow) │ │
│ EIF │ │ NF │ │ HAT
│ (semi- │ │ (Normal- │ │ (River,
│supervisé) │ │ izing │ │ supervisé
│ │ │ Flow) │ │ online)
└─────┬─────┘ └─────┬─────┘ └────┬──────┘
│ │ │
eif_norm nf_norm xgb_prob
eif_norm nf_norm hat_prob
│ │ │
└──────────────┼──────────────┘
┌────────▼────────┐
Meta-Model
Stacking MLP
│ (non-linéaire) │
Fusion MLP
non-linéaire
└────────┬────────┘
┌────────▼────────┐
@ -437,7 +436,7 @@ Le système de détection combine trois « voix » complémentaires :
**Limites de la fusion linéaire**
Une fusion linéaire — combinaison convexe pondérée ou régression logistique — ne peut capturer que des frontières de décision linéaires dans l'espace des scores intermédiaires. Or les signaux EIF, NF et XGBoost peuvent exhiber des interactions non-linéaires impossibles à modéliser par une combinaison linéaire :
Une fusion linéaire — combinaison convexe pondérée ou régression logistique — ne peut capturer que des frontières de décision linéaires dans l'espace des scores intermédiaires. Or les signaux EIF, NF et HAT peuvent exhiber des interactions non-linéaires impossibles à modéliser par une combinaison linéaire :
```
Problème XOR des scores :
@ -450,18 +449,18 @@ Problème XOR des scores :
NF bas
```
Exemple concret : un bot utilisant un outilHeadless avec un JA4 fingerprint légitime (NF bas) mais un comportement de navigation atypique (EIF élevé). Le XGBoost peut compenser, mais la fusion linéaire ne peut apprendre la relation *« EIF élevé ET XGB élevé MAIS NF bas = bot »* — elle ne fait que sommer les contributions indépendantes.
Exemple concret : un bot utilisant un outilHeadless avec un JA4 fingerprint légitime (NF bas) mais un comportement de navigation atypique (EIF élevé). Le HAT peut compenser, mais la fusion linéaire ne peut apprendre la relation *« EIF élevé ET HAT élevé MAIS NF bas = bot »* — elle ne fait que sommer les contributions indépendantes.
**Stacking OOF (Out-of-Fold) et MLP méta-modèle**
Pour résoudre cette limitation, le système utilise un méta-modèle non-linéaire de type MLP (*Multi-Layer Perceptron*) entraîné via stacking Out-of-Fold :
1. **Prédictions OOF** : les modèles de base (EIF, NF, XGBoost) produisent des prédictions sur des plis de validation croisée temporelle, garantissant que le méta-modèle n'a jamais vu les données d'entraînement des modèles de base — évitant le surapprentissage (*information leakage*).
1. **Prédictions OOF** : les modèles de base (EIF, NF, HAT) produisent des prédictions sur des plis de validation croisée temporelle, garantissant que le méta-modèle n'a jamais vu les données d'entraînement des modèles de base — évitant le surapprentissage (*information leakage*).
2. **Méta-modèle MLP** : un réseau de neurones à 2 couches apprend la fonction de fusion optimale :
```
MetaFusionMLP : [eif, nf, xgb] → Linear(3,16) → BatchNorm → ReLU → Dropout(0.2)
MetaFusionMLP : [eif, nf, hat] → Linear(3,16) → BatchNorm → ReLU → Dropout(0.2)
→ Linear(16,1) → Sigmoid → P(bot)
```
@ -470,7 +469,7 @@ MetaFusionMLP : [eif, nf, xgb] → Linear(3,16) → BatchNorm → ReLU → Dropo
- **Early stopping** (patience = 5 epochs) : arrête l'entraînement dès que la loss de validation ne s'améliore plus, évitant le surapprentissage.
- **Weight decay** ($\lambda = 10^{-4}$) : pénalité L2 sur les poids du MLP pour une régularisation supplémentaire.
Le MLP apprend des frontières de décision non-linéaires dans l'espace 3D `[eif_norm, nf_norm, xgb_prob]`, capable de résoudre les patterns XOR et les interactions conditionnelles entre les trois voix. Le système de détection peut ainsi combiner automatiquement les signaux de manière optimale en fonction du type de trafic observé en production.
Le MLP apprend des frontières de décision non-linéaires dans l'espace 3D `[eif_norm, nf_norm, hat_prob]`, capable de résoudre les patterns XOR et les interactions conditionnelles entre les trois voix. Le système de détection peut ainsi combiner automatiquement les signaux de manière optimale en fonction du type de trafic observé en production.
**Calendrier de retraining** :
- HAT (supervisé) : apprentissage incrémental à chaque cycle (300s) sur les étiquettes accumulées, après filtrage Cleanlab des labels SOC bruyants (voir ci-dessous)
@ -479,18 +478,22 @@ Le MLP apprend des frontières de décision non-linéaires dans l'espace 3D `[ei
**Filtrage des labels SOC bruyants (Cleanlab)** :
Avant chaque entraînement XGBoost, les labels fournis par les analystes SOC sont filtrés via [Cleanlab](https://cleanlab.ai/) ([Northcutt et al., 2021](https://arxiv.org/abs/1911.00068)). Ce framework de *confident learning* identifie les exemples dont l'étiquette est probablement erronée en comparant les prédictions out-of-fold d'un modèle aux labels observés.
Avant chaque cycle d'apprentissage incrémental du HAT, les labels fournis par les analystes SOC sont filtrés via [Cleanlab](https://cleanlab.ai/) ([Northcutt et al., 2021](https://arxiv.org/abs/1911.00068)). Ce framework de *confident learning* identifie les exemples dont l'étiquette est probablement erronée en comparant les prédictions out-of-fold d'un modèle aux labels observés.
```python
# 1. Obtenir pred_probs via cross-validation (3 folds)
quick_model = XGBClassifier(n_estimators=80, max_depth=4)
pred_probs = cross_val_predict(quick_model, X, y, cv=3, method='predict_proba')
# 1. Obtenir pred_probs via cross-validation (3 folds) sur les labels accumulés
from river import tree
quick_model = tree.HoeffdingAdaptiveTreeClassifier()
# Les labels accumulés ce cycle sont filtrés avant injection dans le HAT
pred_probs = cross_val_predict(quick_model, X_accumulated, y_accumulated,
cv=3, method='predict_proba')
# 2. Identifier les labels douteux
issues = find_label_issues(labels=y, pred_probs=pred_probs)
# 3. Exclure les exemples bruités avant l'entraînement final
X_clean, y_clean = X[~noisy_mask], y[~noisy_mask]
# 3. N'injecter que les labels propres via learn_one()
for x_clean, y_clean in clean_samples:
hat_model.learn_one(x_clean, y_clean)
```
Ce mécanisme protège le modèle contre l'empoisonnement par des faux positifs mal corrigés ou des biais de confirmation des analystes. Le taux de labels filtrés est loggé pour surveillance. En cas d'échec de Cleanlab (erreur mémoire, dépendance manquante), le pipeline revient aux données brutes sans interruption.
@ -577,7 +580,20 @@ Le passage au stream mining élimine trois problématiques majeures du batch tra
**Validation gate** : conservée — si le taux d'anomalie sur le jeu de validation dépasse 20% après retraining EIF/NF, le nouveau modèle est rejeté et le modèle précédent conservé.
#### 2.4.4 Modélisation des phases d'attaque
**Quantification d'incertitude par Deep Ensembles**
La détection adversariale par ADWIN reposait sur l'heuristique suivante : si plus de 50% des features driftent simultanément, le drift est qualifié d'adversarial. Cette heuristique est non fondée — un pic de légitime trafic (ex. mise à jour navigateur majeure) peut déclencher un drift massif sur de nombreuses features sans pour autant être adversarial. À l'inverse, une attaque furtive ne touchant que quelques features ne serait jamais détectée.
Cette heuristique est remplacée par une mesure d'incertitude épistémique via **Deep Ensembles** ([Lakshminarayanan et al., 2017](https://arxiv.org/abs/1612.01474)) : le Normalizing Flow unique est remplacé par un ensemble de $M=5$ modèles indépendants, chacun entraîné sur un échantillon bootstrap (avec remise) de la baseline humaine. L'incertitude est mesurée par la variance inter-modèles :
$$\sigma^2(x) = \frac{1}{M} \sum_{m=1}^{M} \left( -\log p_m(x) - \overline{-\log p(x)} \right)^2$$
La logique de détection repose sur l'intuition suivante :
- **Dérive organique** (changement naturel du trafic) : les 5 modèles s'accordent sur la nouveauté → variance faible. Tous les manifolds ont capturé les mêmes structures dans la baseline, donc un nouveau pattern légitime est traité de manière cohérente.
- **Dérive adversariale** (évasion délibérée) : les 5 modèles ne s'accordent pas → variance qui explose. Un échantillon adversarial tombe dans une région de l'espace où chaque manifold a appris une frontière légèrement différente (diversité induite par le bootstrap), produisant des scores de vraisemblance très dispersés.
Le seuil `NF_UNCERTAINTY_THRESHOLD` (défaut : 1.0) est appliqué sur $\sigma^2(x)$ : tout échantillon au-dessus est tagué `is_adversarial_drift = True`. Cette approche est fondée statistiquement (variance sur un ensemble) et ne dépend pas d'un seuil arbitraire sur le nombre de features en drift.
La modélisation des phases d'attaque (Reconnaissance → Mouvement latéral → Intrusion → Exfiltration) par des modèles d'état-espace ou des processus de Markov cachés constitue une piste de recherche. L'enrichissement du clustering HDBSCAN avec ce signal de phase permettrait de distinguer des campagnes en phase de reconnaissance de campagnes en phase d'exploitation active.

View File

@ -69,8 +69,8 @@
│ │ 3b. dynamic H2 profiling scoring │ │
│ │ 4. EIF bifurqué (complet/appli) │ │
│ │ 5. NF log-likelihood scoring │ │
│ │ 6. XGBoost probabilité │ │
│ │ 7. Meta-Model MLP fusion │ │
│ │ 6. HAT probabilité (River online)│ │
│ │ 7. Fusion MLP non-linéaire │ │
│ │ 8. HDBSCAN clustering (NF latent) │ │
│ │ 9. Écriture résultats ClickHouse │ │
│ └──────────────────────────────────┘ │
@ -240,7 +240,7 @@ Session entrante
├── asn_label == 'human' ?
│ ── OUI → baseline EIF training (sans étiquette bot)
└── Sinon → Triple-voix : EIF + NF + XGBoost + Meta-Model Stacking (MLP non-linéaire)
└── Sinon → Triple-voix : EIF + NF + HAT (River) + Fusion MLP non-linéaire
```
#### Seuil adaptatif
@ -258,7 +258,7 @@ La valeur `percentile_5` du historique des scores négatifs (anomalies confirmé
| EIF Complet | ≈ 45 features L3→L7 | Données L3/L4 disponibles | eif_score_full |
| EIF Applicatif | ≈ 35 features L7 | L3/L4 absentes (CDN/proxy) | eif_score_app |
| NF | Même dimensionnalité que EIF actif | Toutes sessions | nf_log_likelihood |
| XGBoost | Ensemble complet 96 features | Toutes sessions | xgb_probability |
| HAT (River) | Ensemble complet 96 features | Toutes sessions | hat_probability |
#### Niveaux de sévérité

View File

@ -21,6 +21,7 @@ La détection de bots s'inscrit dans une dynamique de course aux armements où c
| asset_ratio | Playwright/Puppeteer chargeant toutes ressources | Détectable via resource dependency tree (§5.4) |
| IP reputation | Proxies résidentiels (Bright Data, Oxylabs) | Contournement partiel mais coût élevé par requête |
| Comportement navigation | Scripts imitant les patterns de clic humain | Détectable via cadence fingerprint et entropy de séquence |
| Deep Ensembles (NF M=5) | Perturbation continue des features | L'évasion par perturbation continue est difficile car l'attaquant doit tromper 5 manifolds différents simultanément |
#### Architecture multi-couches comme contre-mesure structurelle
@ -71,7 +72,7 @@ Cependant, des proxies résidentiels persistants apparaissant dans **chaque cycl
| 2 | Signaux orthogonaux 5.2, §5.3) résistants à contamination | Détecte bots résistants à l'EIF par des axes indépendants |
| 3 | Validation : `anomaly_rate > 20%` rejet du modèle | Détecte les cycles d'entraînement pathologiques |
| 4 | Feedback SOC : FP reclassification "human" ; TP exclusion baseline | Correction manuelle des erreurs systématiques |
| 5 | Triple ensemble : XGBoost corrige les erreurs systématiques EIF | Supervisé corrige les biais de l'non-supervisé |
| 5 | Triple ensemble : HAT (River) corrige les erreurs systématiques EIF | Supervisé online corrige les biais de l'non-supervisé |
#### Impact du feedback SOC
@ -109,14 +110,14 @@ Le fingerprinting réseau opère sans déchiffrement TLS (les métadonnées TLS
| Composant | Temps d'exécution | Conditions |
|-----------|------------------|------------|
| EIF training | < 2 secondes | ~34 000 sessions, 96 features |
| AE inference | ~50 ms | Batch de 34 000 sessions |
| XGBoost inference | ~30 ms | Batch de 34 000 sessions |
| NF (Normalizing Flow) inference | ~50 ms | Batch de 34 000 sessions |
| HAT (River) inference | ~30 ms | Batch de 34 000 sessions |
| HDBSCAN (anomalies) | ~100 ms | ~34 000 sessions, espace latent AE |
| HDBSCAN (profiling) | ~25 s | Quotidien, ~200k sessions H2 dédupliquées, min_cluster=1000 |
| Dynamic matcher scoring | < 1 ms | Par session, lookup en mémoire contre ~510 profils |
| GraphSAGE (fleet.py) | ~80 ms | Graphe d'IPs, 2 couches SAGEConv, GPU/CPU |
| Fusion MLP | < 10 ms | MLP 2 couches, négligeable |
| **Cycle complet** | **~300 secondes** | EIF + AE + XGBoost + HDBSCAN + GraphSAGE |
| **Cycle complet** | **~300 secondes** | EIF + NF + HAT + HDBSCAN + GraphSAGE |
La durée du cycle (300 s = 5 minutes) est contrainte principalement par la **fenêtre d'agrégation ClickHouse** (1 heure glissante avec recalcul toutes les 5 minutes), non par les temps d'exécution ML.
@ -133,7 +134,7 @@ La durée du cycle (300 s = 5 minutes) est contrainte principalement par la **fe
- À 34 000 sessions/cycle : ~100 ms acceptable
- À 500 000 sessions/cycle (scaling ×15) : ~2 s encore tolérable
**Fusion MLP** : O(n × d) inférence avec d = 3 features d'entrée (scores EIF, NF, XGBoost), MLP 2 couches (16 neurones). Temps négligeable quelle que soit la taille.
**Fusion MLP** : O(n × d) inférence avec d = 3 features d'entrée (scores EIF, NF, HAT), MLP 2 couches (16 neurones). Temps négligeable quelle que soit la taille.
**Limite architecturale principale** : le modèle supervisé (Hoeffding Adaptive Tree) s'améliore incrémentalement à chaque cycle via `learn_one()`, mais nécessite un flux continu de labels fiables. À faible volume de labels (< 500 sessions étiquetées), le HAT converge lentement. Ce problème est partiellement atténué par le filtrage Cleanlab qui élimine les labels douteux (détail §3.8), mais la qualité du feedback SOC reste le goulot d'étranglement principal.
@ -176,7 +177,7 @@ Ce document présente un système opérationnel déployé en production, mais so
**Le chiffre de "777 anomalies par cycle (≈ 2,3 %)"** est un compteur opérationnel brut : il mesure le nombre de sessions dépassant le seuil d'anomalie configuré, mais ne distingue pas les vrais positifs des faux positifs. En l'absence de ground truth systématique, ce chiffre ne constitue pas un indicateur de performance de détection.
**Conséquence** : les choix architecturaux (EIF bifurqué, ensemble triple-voix, poids de la fusion LR) sont motivés par des arguments qualitatifs et l'expérience opérationnelle, mais ne sont pas validés par une évaluation quantitative contrôlée. La priorité immédiate pour les travaux futurs est l'établissement d'un protocole d'évaluation sur un dataset labellisé, avec comparaison contre des baselines (Isolation Forest seul, XGBoost seul, LOF, One-Class SVM).
**Conséquence** : les choix architecturaux (EIF bifurqué, ensemble triple-voix, poids de la fusion MLP) sont motivés par des arguments qualitatifs et l'expérience opérationnelle, mais ne sont pas validés par une évaluation quantitative contrôlée. La priorité immédiate pour les travaux futurs est l'établissement d'un protocole d'évaluation sur un dataset labellisé, avec comparaison contre des baselines (Isolation Forest seul, HAT seul, LOF, One-Class SVM).
### 6.7 Travaux futurs et roadmap

View File

@ -19,13 +19,13 @@ Un système de détection à couverture complète couvrant cinq couches réseau
Un pipeline ML combinant :
- **Isolation Forest Étendu (EIF)** ([Hariri et al., 2021](https://ieeexplore.ieee.org/document/8888179)) : modèle non-supervisé fondé sur l'isolation aléatoire d'instances anormales dans des espaces de features basse-dimension
- **Autoencodeur variationnel (AE)** ([Mirsky et al., NDSS 2018](https://www.ndss-symposium.org/ndss-paper/kitsune-an-ensemble-of-autoencoders-for-online-network-intrusion-detection/)) : détection d'anomalies par reconstruction, capturant les corrélations entre features
- **XGBoost supervisé** : correction des erreurs systématiques des modèles non-supervisés via labels SOC accumulés
- **Fusion par MLP méta-modèle** : fusion non-linéaire des trois scores en un score final calibré
- **Normalizing Flow (RealNVP)** : détection d'anomalies par vraisemblance, capturant les corrélations jointes entre features via Deep Ensemble (M=5)
- **HAT supervisé (Hoeffding Adaptive Tree, River)** : correction des erreurs systématiques des modèles non-supervisés via labels SOC accumulés, apprentissage incrémental par `learn_one()`
- **Fusion MLP non-linéaire** : fusion non-linéaire des trois scores en un score final calibré
Le pipeline intègre un mécanisme de **détection de dérive conceptuelle** (basé sur le percentile 5 des scores négatifs) distinguant la dérive organique (évolution naturelle du trafic) de la dérive adversariale (manipulation intentionnelle de la distribution).
Le pipeline intègre un mécanisme de **détection de dérive conceptuelle** via ADWIN (Adaptive Windowing) distinguant la dérive organique (évolution naturelle du trafic) de la dérive adversariale (variance épistémique élevée du NFEnsemble).
L'**explainabilité** est assurée par l'importance des features par profondeur d'isolation (approche de type ExIFFI) pour l'EIF et SHAP ([Lundberg & Lee, 2017](https://shap.readthedocs.io/)) pour XGBoost, permettant l'audit des décisions de blocage par l'équipe SOC.
L'**explainabilité** est assurée par l'importance des features par profondeur d'isolation (approche de type ExIFFI) pour l'EIF et SHAP ([Lundberg & Lee, 2017](https://shap.readthedocs.io/)) pour le HAT, permettant l'audit des décisions de blocage par l'équipe SOC.
#### Composant 3 : Fingerprinting HTTP/2 passif structuré (browser_matcher)
@ -76,7 +76,7 @@ Architecture de données fondée sur ClickHouse avec **AggregatingMergeTree view
### Perspective
Le système atteint ses objectifs opérationnels actuels. La capture HTTP/2 passive est intégrée avec 12 colonnes individuelles dans `ja4_logs.http_logs`, et le module `browser_matcher` est opérationnel avec ses 7 dimensions de scoring statique. Le moteur de profiling dynamique automatique (§3.9.6) complète le système statique en apprenant les signatures H2 à partir du trafic réel, éliminant la dépendance aux signatures codées en dur. Les axes d'amélioration prioritaires sont le monitoring de la convergence des clusters dynamiques, l'extension DNS Shadow Analysis pour la couverture DNS (`[todo]``[partiel]`), et le passage à l'apprentissage en ligne pour XGBoost. À plus long terme, le support HTTP/3 (QUIC) deviendra nécessaire à mesure que la proportion de trafic HTTP/3 augmente dans la baseline.
Le système atteint ses objectifs opérationnels actuels. La capture HTTP/2 passive est intégrée avec 12 colonnes individuelles dans `ja4_logs.http_logs`, et le module `browser_matcher` est opérationnel avec ses 7 dimensions de scoring statique. Le moteur de profiling dynamique automatique (§3.9.6) complète le système statique en apprenant les signatures H2 à partir du trafic réel, éliminant la dépendance aux signatures codées en dur. Les axes d'amélioration prioritaires sont le monitoring de la convergence des clusters dynamiques et l'extension DNS Shadow Analysis pour la couverture DNS (`[todo]``[partiel]`). Le passage à l'apprentissage en ligne via HAT (River) est effectif depuis la section 6.7. À plus long terme, le support HTTP/3 (QUIC) deviendra nécessaire à mesure que la proportion de trafic HTTP/3 augmente dans la baseline.
La modélisation des phases d'attaque séquentielles par des modèles d'état-espace constitue une piste de recherche prometteuse, qui permettrait de modéliser explicitement les phases d'attaque séquentielles — comblant la lacune actuelle entre la détection de sessions individuelles et la détection de campagnes d'attaque coordonnées multi-phases.
@ -236,8 +236,8 @@ arXiv preprint arXiv:1210.0921.
[35] **HDBSCAN Python library** — Implémentation performante de l'algorithme HDBSCAN.
[https://hdbscan.readthedocs.io/en/latest/](https://hdbscan.readthedocs.io/en/latest/)
[36] **XGBoost** — Bibliothèque de gradient boosting optimisée.
[https://xgboost.readthedocs.io/en/stable/](https://xgboost.readthedocs.io/en/stable/)
[36] **River** — Bibliothèque d'apprentissage incrémental et stream mining.
[https://riverml.xyz/](https://riverml.xyz/)
---