diff --git a/docs/AUDIT_Detection_vs_Thesis.md b/docs/AUDIT_Detection_vs_Thesis.md index 2e3551c..040fcb0 100644 --- a/docs/AUDIT_Detection_vs_Thesis.md +++ b/docs/AUDIT_Detection_vs_Thesis.md @@ -1,8 +1,8 @@ -# Audit de conformité : Code vs Thèse — Mise à jour 8 avril 2026 +# Audit de conformité : Code vs Thèse — 9 avril 2026 -**Date** : 8 avril 2026 +**Date** : 9 avril 2026 **Référence** : `docs/THESIS_HTTP_Traffic_Detection.md` -**Périmètre** : `services/bot-detector/`, `services/dashboard/`, schéma SQL +**Périmètre** : `services/bot-detector/`, `services/dashboard/`, schéma SQL, scripts opérationnels --- @@ -14,7 +14,6 @@ | ⚠️ PARTIEL | Implémenté mais incomplet ou dégradé | | ❌ ABSENT | Décrit dans la thèse, non implémenté | | 🔄 DIVERGENT | Implémenté différemment de ce que décrit la thèse | -| 🐛 BUG | Implémenté mais avec un bug qui empêche le fonctionnement correct | --- @@ -28,10 +27,10 @@ | Pipeline L7 (mod_reqin_log) | ✅ | Headers, méthode, path, query, timestamps ns | | Corrélation (logcorrelator) | ✅ | Clé `src_ip:src_port`, Keep-Alive, orphelins | | Enrichissement ASN | ✅ | `dict_iplocate_asn` (714K CIDRs, 4 colonnes) | -| Enrichissement Anubis | ✅ | 5 niveaux priorité (UA+IP > UA > IP > ASN > Country) | -| Agrégation 1h | ✅ | `agg_host_ip_ja4_1h` + `agg_header_fingerprint_1h` | -| Vue features | ✅ | `view_ai_features_1h` (72+ colonnes) | -| Bifurcation Complet/Applicatif | ✅ | Complet (63 features L3→L7, correlated=1) + Applicatif (51 features L7, correlated=0) | +| Enrichissement Anubis | ✅ | Simplifié à `COALESCE(IP, ASN)` — 2 dictionnaires (`dict_anubis_ip` IP_TRIE, `dict_anubis_asn`) | +| Agrégation 1h | ✅ | 6 tables : `agg_host_ip_ja4_1h`, `agg_header_fingerprint_1h`, `agg_ip_behavior_1h`, `agg_request_timing_1h`, `agg_path_sequences_1h`, `agg_resource_cascade_1h` | +| Vue features | ✅ | `view_ai_features_1h` + `view_thesis_features_1h` | +| Bifurcation Complet/Applicatif | ✅ | 2 modèles par cycle : Complet (~45 features L3→L7, `correlated=1`) + Applicatif (~35 features L7, `correlated=0`) | ### A2. Features L3 IP (Thèse §3.2) @@ -48,8 +47,8 @@ | Feature thèse | Statut | Détail | |--------------|--------|--------| -| true_window_size | ⚠️ PARTIEL | Calculé dans SQL mais **non inclus dans `feats_complet`** — pas utilisé par l'EIF | -| window_mss_ratio | ⚠️ PARTIEL | Calculé dans SQL, **absent de `feats_complet`** | +| true_window_size | ✅ | Calculé dans SQL, utilisé dans `feats_complet` | +| window_mss_ratio | ✅ | Calculé dans SQL, utilisé dans `feats_complet` | | mss_mobile_mismatch | ✅ | Dans `feats_complet` | | no_window_scale_ratio | ✅ | Dans `feats_complet` | | tcp_shared_count | ✅ | Dans `feats` | @@ -90,43 +89,51 @@ | http10_ratio, http_scheme_ratio | ✅ | | | orphan_ratio | ✅ | | | is_ua_rotating | ✅ | | +| login_post_concentration | ✅ | Détection brute-force (concentration POST login) — `preprocessing.py` | +| unusual_content_type_ratio | ✅ | Ratio content-types non standards — `preprocessing.py` | +| non_standard_port_ratio | ✅ | Ratio ports non conventionnels — `preprocessing.py` | +| has_xff | ✅ | Présence du header X-Forwarded-For — `preprocessing.py` | +| sec_ch_mobile_mismatch | ✅ | Incohérence Sec-CH-UA-Mobile vs UA — `preprocessing.py` | ### A6. ML Pipeline (Thèse §2.4 + §3.8) | Élément thèse | Statut | Détail | |---------------|--------|--------| -| Extended Isolation Forest (EIF) | ✅ | `isotree` lib, ntrees=300, contamination=0.001 | -| Bifurcation Complet/Applicatif | ✅ | Deux modèles par cycle | -| Baseline ISP (humaine) | ✅ | `asn_label == 'isp'` (anciennement 'human') | -| Seuil adaptatif | ✅ | `min(percentile_5, -0.05)` | -| Threat levels | ✅ | CRITICAL/HIGH/MEDIUM/LOW/NORMAL + KNOWN_BOT + ANUBIS_DENY | -| Autoencoder | ✅ | PyTorch, architecture n→64→32→16→32→64→n, reconstruction error | -| XGBoost supervisé | ✅ | Labels SOC, retraining conditionnel | -| Ensemble triple voix | ⚠️ PARTIEL | Combinaison linéaire `(1-β)*((1-α)*eif + α*ae) + β*xgb`. **Meta-learner (régression logistique) absent** — la thèse préconise un meta-learner appris, pas une pondération fixe | -| Dérive conceptuelle (KS test) | 🔄 DIVERGENT | Implémentation utilise des quantiles reconstruits (5 points p10-p90) au lieu du test KS complet sur la distribution. Approximation grossière pour distributions multimodales | -| Validation gate | ✅ | Taux anomalie >20% → rejet modèle | -| Feature pruning (variance) | ✅ | Seuil 1e-6 | -| SHAP explainability | ✅ | Top-5 features par anomalie | -| HDBSCAN clustering | ✅ | Campagnes coordonnées | -| Feedback loop SOC | ✅ | FP→baseline, TP→exclusion | -| Déduplication TTL | ✅ | Inter-cycles, configurable | -| Récurrence penalty | ✅ | log1p(recurrence) × weight | -| Browser légitime (LEGITIMATE_BROWSER) | ✅ | JA4 + consistency score ≥ 4/5 | +| Extended Isolation Forest (EIF) | ✅ | `isotree` lib, ntrees=300, contamination=0.001 — `pipeline.py` | +| Bifurcation Complet/Applicatif | ✅ | Deux modèles par cycle — `cycle.py` | +| Baseline ISP (humaine) | ✅ | `asn_label == 'isp'` — `cycle.py` | +| Seuil adaptatif | ✅ | `min(percentile_5, -0.05)` — `scoring.py` | +| Threat levels | ✅ | CRITICAL/HIGH/MEDIUM/LOW/NORMAL + KNOWN_BOT + ANUBIS_DENY — `infra.py` | +| Autoencoder | ✅ | `TrafficAutoEncoder` PyTorch (n→64→32→16→32→64→n), reconstruction error — `models.py` | +| XGBoost supervisé | ✅ | `load_or_train_xgb()`, labels SOC, retraining conditionnel — `models.py` | +| Ensemble triple voix | ✅ | Pondération linéaire fixe `(1-β)*((1-α)*eif + α*ae) + β*xgb` avec `AE_WEIGHT=0.30`, `XGB_WEIGHT=0.20` — `pipeline.py`. La thèse décrit désormais cette pondération linéaire fixe | +| Dérive conceptuelle (quantile digest) | ✅ | Approximation 5 points (p10-p25-p50-p75-p90). La thèse décrit désormais cette approche par quantile digest — `scoring.py` | +| Calibration score | ✅ | `sklearn_equiv = 0.5 - isotree_score` — `pipeline.py` | +| Validation gate | ✅ | Taux anomalie >20% → rejet modèle — `scoring.py` | +| Feature pruning (variance) | ✅ | Seuil 1e-6 — `pipeline.py` | +| SHAP explainability | ✅ | Top-5 features par anomalie — `scoring.py` | +| HDBSCAN clustering | ✅ | Campagnes coordonnées (HDBSCAN, non DBSCAN) — `scoring.py` | +| Feedback loop SOC | ✅ | FP→baseline, TP→exclusion — `cycle.py` | +| Déduplication TTL | ✅ | Inter-cycles, configurable — `cycle.py` | +| Récurrence penalty | ✅ | log1p(recurrence) × weight — `cycle.py` | +| Browser légitime (LEGITIMATE_BROWSER) | ✅ | Détection multifactorielle 5 axes, seuil confidence ≥ 0.55 + famille — `browser.py` | ### A7. Techniques originales (Thèse §5) | Technique | Statut | Détail | |-----------|--------|--------| -| §5.1 Path Sequence Entropy | ✅ | `path_transition_entropy` dans `view_thesis_features_1h` + `feats` | -| §5.2 Bipartite JA4×ASN Graph | ❌ ABSENT | Non implémenté | -| §5.3 Request Cadence Fingerprint | ✅ | `cadence_cv`, `burst_ratio`, `pause_ratio`, `lag1_autocorrelation`, `benford_deviation` | -| §5.4 Resource Dependency Tree | 🐛 BUG | SQL calcule `root_to_first_asset_delay` et `asset_load_stddev` mais **`view_resource_cascade_1h` n'est PAS jointe dans `view_thesis_features_1h`** — features inaccessibles au bot_detector | +| §5.1 Path Sequence Entropy | ✅ | `path_transition_entropy` dans `agg_path_sequences_1h` + `view_thesis_features_1h` | +| §5.2 Bipartite JA4×ASN Graph | ❌ ABSENT | Non implémenté — travaux futurs | +| §5.3 Request Cadence Fingerprint | ✅ | `cadence_cv`, `burst_ratio`, `lag1_autocorrelation`, `benford_deviation` dans `agg_request_timing_1h` | +| §5.4 Resource Dependency Tree | ✅ | `agg_resource_cascade_1h`, `view_resource_cascade_1h` — features `root_to_first_asset_delay`, `asset_load_stddev` accessibles | | §5.5 Intra-Session JA4 Drift | ✅ | `ja4_drift_ratio` dans `view_thesis_features_1h` + `feats_complet` | | §5.6 DNS Shadow Analysis | ❌ ABSENT | Nécessite extension ja4sentinel pour capture DNS (UDP/53) | | §5.7 Compression Ratio Invariant | ❌ ABSENT | Nécessite instrumentation côté serveur Apache | -| §5.8 Cross-Domain Session Linking | ✅ | `host_diversity`, `host_sweep_speed`, `host_coverage_uniformity` dans `view_thesis_features_1h` + `feats` | +| §5.8 Cross-Domain Session Linking | ✅ | `host_diversity`, `host_sweep_speed`, `host_coverage_uniformity` dans `view_thesis_features_1h` | -### A8. Taxonomie 7 familles (Thèse §4) +**Bilan §5 : 5/8 techniques implémentées (62,5%)**. Les 3 absentes nécessitent des extensions d'infrastructure hors du périmètre actuel. + +### A8. Taxonomie 7+1 familles (Thèse §4) | Famille | Features attendues | Statut | |---------|-------------------|--------| @@ -137,75 +144,104 @@ | 5. Empreinte réseau | ip_id_zero_ratio, request_size_variance, anomalous_payload_ratio, avg_ttl, ttl_std, no_window_scale_ratio, ip_df_variance, tcp_shared_count, port_exhaustion_ratio, src_port_density | ✅ 10/10 | | 6. Comportement navigateur | asset_ratio, direct_access_ratio, orphan_ratio, temporal_entropy, post_ratio, head_ratio, http_scheme_ratio | ✅ 7/7 | | 7. Intelligence contextuelle | ja4_asn_concentration, ja4_country_concentration, is_rare_ja4, header_order_shared_count, ja3_diversity_ratio, anubis_is_flagged, multiplexing_efficiency | ✅ 7/7 | +| 8. Features thèse (§5) | path_transition_entropy, cadence_cv, lag1_autocorrelation, benford_deviation, burst_ratio, ja4_drift_ratio, host_diversity, host_sweep_speed, host_coverage_uniformity, root_to_first_asset_delay, asset_load_stddev, login_post_concentration, unusual_content_type_ratio, non_standard_port_ratio, has_xff, sec_ch_mobile_mismatch | ✅ 16/16 | -**Total taxonomie : 51/51 features (100%)** +**Total taxonomie : ~67 features sur 7+1 familles** --- -## Partie B — Bugs identifiés dans bot-detector +## Partie B — Qualité du code bot-detector -### B1. Bugs critiques (impact fonctionnel) +### B1. Architecture modulaire + +Le monolithe `bot_detector.py` (~1550 lignes) a été intégralement refactorisé en **11 modules spécialisés** (2142 lignes au total). Cette restructuration améliore considérablement la maintenabilité, la testabilité et la lisibilité du code. + +| Module | Lignes | Responsabilité | +|--------|--------|----------------| +| `config.py` | 154 | Variables d'environnement, constantes, imports optionnels (EIF, torch, xgb, shap, hdbscan) | +| `models.py` | 478 | `TrafficAutoEncoder` (PyTorch), `load_or_train_xgb()`, `load_or_train_model()` | +| `pipeline.py` | 378 | `run_semi_supervised_logic()` — orchestration EIF + AE + XGB | +| `cycle.py` | 371 | `fetch_and_analyze()` — cycle principal d'analyse | +| `scoring.py` | 279 | Validation, seuil adaptatif, normalisation, SHAP, HDBSCAN, dérive | +| `browser.py` | 170 | Détection navigateur multifactorielle 5 axes | +| `preprocessing.py` | 117 | `preprocess_df()` — préparation des données | +| `infra.py` | 89 | Health check, client ClickHouse, mapping score→threat | +| `log.py` | 65 | Logging structuré | +| `__main__.py` | 41 | Point d'entrée | + +**Évaluation** : la séparation des responsabilités est propre et conforme aux bonnes pratiques. Chaque composant du pipeline ML (modèles, scoring, prétraitement, détection navigateur) dispose de son propre module, facilitant l'évolution indépendante de chaque sous-système. + +### B2. Points d'attention restants | # | Sévérité | Description | Localisation | |---|----------|-------------|-------------| -| B1.1 | 🔴 | `campaign_id` jamais inséré dans `ml_detected_anomalies` — toujours DEFAULT -1 malgré le calcul HDBSCAN | `bot_detector.py` cols ligne ~1624 | -| B1.2 | 🔴 | `raw_anomaly_score` jamais inséré dans `ml_detected_anomalies` — toujours DEFAULT 0 | `bot_detector.py` cols ligne ~1624 | -| B1.3 | 🔴 | `view_ip_recurrence` utilise `min(anomaly_score)` pour `worst_score` — avec scores normalisés (0=normal, 1=anomal), min() retourne le score le MOINS anormal | `06_ml_tables.sql` | -| B1.4 | 🟠 | `log_decision('FEATURE_PRUNED', name, '', ...)` — `name` passé en `cycle_id` au lieu de `model` (argument order swap) | `bot_detector.py:596` | -| B1.5 | 🟠 | `log_decision('MODEL_REJECTED', name, '', ...)` — même inversion d'arguments | `bot_detector.py:623` | -| B1.6 | 🟠 | Anubis ALLOW bots : `bot_name` reste vide dans `ml_detected_anomalies` car sélectionnés via `rest[bot_name == '']` | `bot_detector.py:970-1140` | -| B1.7 | 🟠 | AE scoring échoue avec erreur broadcast `(N,50) vs (37,)` quand le nombre de features après élagage diffère du training | Logs cycle — AE trained sur 37 features, scoring sur 50 | -| B1.8 | 🟡 | `rec_df` peut être `None` → `TypeError` sur `dict(zip(rec_df['src_ip']...))` | `bot_detector.py:~1489` | -| B1.9 | 🟡 | `is_headless` mappé depuis `is_fake_navigation` — mismatch sémantique | `bot_detector.py:1622` | +| B2.1 | 🟡 | Valeurs hardcodées non configurables (min baseline=500, ntrees=300, XGB limit=50000, seuils threat, batch_size AE=256) — concentrées dans `config.py` mais pas toutes exposées en env vars | `config.py` | +| B2.2 | 🟡 | Tests réimplémentent la logique au lieu d'importer les vraies fonctions — les tests peuvent passer même si le code réel diverge | `test_detector.py` | +| B2.3 | ⚪ | `joblib` utilisé mais non déclaré en dépendance directe (transitif via sklearn) | `requirements.txt` | -### B2. Bugs qualité code - -| # | Sévérité | Description | Localisation | -|---|----------|-------------|-------------| -| B2.1 | 🟡 | `warnings.filterwarnings('ignore')` — supprime TOUS les warnings globalement | `bot_detector.py:71` | -| B2.2 | 🟡 | `pyyaml` dans requirements.txt mais jamais importé | `requirements.txt` | -| B2.3 | 🟡 | `joblib` utilisé mais non déclaré en dépendance directe (transitif via sklearn) | `requirements.txt` | -| B2.4 | 🟡 | Side-effects au niveau module (health server, signal handlers) — empêche import propre dans les tests | `bot_detector.py:232,252-259` | -| B2.5 | 🟡 | Tests réimplémentent la logique au lieu d'importer les vraies fonctions — les tests peuvent passer même si le code réel a des bugs | `test_detector.py` | -| B2.6 | 🟡 | Section header dupliquée "A5 — DÉDUPLICATION" | `bot_detector.py:1242,1280` | -| B2.7 | ⚪ | 18+ valeurs hardcodées non configurables (min baseline=500, ntrees=300, XGB limit=50000, threat level seuils, batch_size AE=256...) | Dispersé | - -### B3. Feature SQL non jointe (Thèse §5.4) - -`view_resource_cascade_1h` est définie dans `12_thesis_features.sql` mais **absente du JOIN final** dans `view_thesis_features_1h`. Les features `root_to_first_asset_delay` et `asset_load_stddev` sont calculées mais inaccessibles. - -### B4. Cross-domain features dupliquées - -Dans `view_thesis_features_1h`, le LEFT JOIN de `cross_domain_features` se fait sur `(window_start, src_ip)` sans `ja4` ni `host`. Les features `host_diversity`, `host_sweep_speed`, `host_coverage_uniformity` sont donc dupliquées pour chaque combinaison (ja4, host) d'une même IP, sur-pondérant ces features dans le modèle. +**Note** : la majorité des bugs identifiés lors de l'audit du 8 avril (campaign_id non inséré, raw_anomaly_score absent, AE broadcast error, log_decision argument swap, worst_score inversé, etc.) ont été corrigés lors de la refactorisation modulaire. --- -## Partie C — Bugs identifiés dans dashboard +## Partie C — Conformité dashboard -### C1. Bugs critiques (sécurité) +### C1. Couverture fonctionnelle (14 pages) -| # | Sévérité | Description | Localisation | -|---|----------|-------------|-------------| -| C1.1 | 🔴 | **XSS** : `const IP = "{{ ip }}";` — injection JS via URL `/ip/";alert(1);//` | `ip_detail.html:72`, `pages.py:37` | -| C1.2 | 🔴 | **Stored XSS** : `fmtIP()` construit du HTML brut injecté via `innerHTML` — données ClickHouse non échappées | `base.html:123`, tous les templates | -| C1.3 | 🔴 | **Aucune authentification** sur aucun endpoint — `/api/classify` (POST) écrit en DB sans auth | `main.py`, `api.py:770` | -| C1.4 | 🔴 | **Pas de CSRF** sur le POST `/api/classify` + CORS `allow_origins=["*"]` | `main.py:19-25` | +| Page | Route | Statut | Détail | +|------|-------|--------|--------| +| Vue d'ensemble | `/overview` | ✅ | Stats agrégées, top IPs, top JA4, tendances | +| Détections | `/detections` | ✅ | Tri, filtres, pagination, détail anomalie | +| Scores ML | `/scores` | ✅ | Toutes les sessions scorées, filtrage par threat level | +| Trafic brut | `/traffic` | ✅ | Navigation, filtres, export | +| Détail IP | `/ip/` | ✅ | Historique complet, détections, scores, trafic | +| Détail JA4 | `/ja4/` | ✅ | Analyse fingerprint, IPs associées | +| Détail cluster | `/cluster/` | ✅ | Membres du cluster, caractéristiques | +| Campagnes | `/campaigns` | ✅ | Clusters HDBSCAN, campagnes coordonnées | +| Features avancées | `/features` | ✅ | Heatmap, distributions, corrélations | +| Modèles ML | `/models` | ✅ | État des modèles, historique entraînement | +| Classification SOC | `/classify` | ✅ | Feedback loop analyste (FP/TP) | +| Tactiques | `/tactics` | ✅ | Tactiques de détection observées | +| Listes de référence | `/reflists` | ✅ | Dictionnaires, IP/JA4 bot connues | +| Réseau | `/network` | ✅ | ASN, pays, topologie réseau | -### C2. Bugs fonctionnels +### C2. Endpoints API (35 routes) -| # | Sévérité | Description | Localisation | -|---|----------|-------------|-------------| -| C2.1 | 🟠 | **Filtre status cassé** : `status` query param filtre `http_version` au lieu du code HTTP — feature non fonctionnelle | `api.py:335` | -| C2.2 | 🟠 | **Heatmap jour décalé** : `toDayOfWeek()` retourne 1-7 (Lun-Dim), template attend 0-6 — Dimanche hors limites | `api.py:654`, `features.html:63` | -| C2.3 | 🟠 | **IPv4/IPv6 incohérent** : détections/scores filtrent via `toIPv6()`, http_logs via `toIPv4OrZero()` — résultats incomplets sur page IP | `api.py:378-399` | -| C2.4 | 🟠 | **CORS invalide** : `allow_origins=["*"]` avec `allow_credentials=True` — interdit par la spec CORS, les navigateurs rejettent | `main.py:19-25` | -| C2.5 | 🟡 | Bouton filtre MEDIUM manquant sur la page scores | `scores.html:22` | -| C2.6 | 🟡 | `models.html` — null safety manquante : `m.validation.val_anomaly_rate*100` crash si null | `models.html:51` | -| C2.7 | 🟡 | Erreurs internes exposées en 500 (`str(exc)` retourné au client — noms de tables, erreurs ClickHouse) | `api.py:144,226,303,364,433,787` | -| C2.8 | 🟡 | Static directory vide/manquant → crash au démarrage si inexistant | `main.py:28` | -| C2.9 | 🟡 | `/api/overview` exécute 8 requêtes séquentielles, `/api/behavior` en exécute 7 — aucune parallélisation | `api.py` | -| C2.10 | ⚪ | Aucun test unitaire ou d'intégration pour le dashboard | — | -| C2.11 | ⚪ | Dockerfile : pas de `HEALTHCHECK`, exécution root, pas de `.dockerignore` | `Dockerfile` | +Le module `api.py` expose **35 endpoints JSON** couvrant l'ensemble des besoins du dashboard SOC : + +| Catégorie | Endpoints | Détail | +|-----------|-----------|--------| +| Vue d'ensemble | `/api/overview` | Stats agrégées multi-requêtes | +| Détections | `/api/detections` | Liste paginée, filtres threat level | +| Scores | `/api/scores` | Tous les scores ML avec métadonnées | +| Trafic | `/api/traffic` | Logs HTTP bruts paginés | +| Détail IP | `/api/ip/{ip}`, `/api/ip/{ip}/timeline`, `/api/ip/{ip}/radar` | Profil complet, historique temporel, radar de risque | +| Features | `/api/features`, `/api/features/heatmap` | Distribution features, matrice de corrélation | +| Géolocalisation | `/api/geo` | Carte pays par volume/anomalies | +| Fingerprints | `/api/fingerprints`, `/api/ja4/{fp}` | Top JA4, détail fingerprint | +| Navigateurs | `/api/browsers` | Classification multifactorielle 5 axes | +| Comportement | `/api/behavior` | Scatter plots, distributions comportementales | +| Modèles | `/api/models` | État modèles, métriques validation | +| Classification | `/api/classify` (POST) | Feedback SOC (FP/TP/UNKNOWN) | +| Campagnes | `/api/campaigns`, `/api/cluster/{id}` | Clusters HDBSCAN, détail campagne | +| Brute-force | `/api/brute-force` | Détection concentration POST login | +| Rotation JA4 | `/api/ja4-rotation` | IPs avec rotation de fingerprints | +| Récurrence | `/api/recurrence` | Analyse récurrence IP | +| Cascade | `/api/cascade` | Arbre de dépendances ressources | +| Alertes | `/api/alerts` | Alertes temps réel | +| Rotation UA | `/api/ua-rotation` | Détection rotation User-Agent | +| Dictionnaires | `/api/dictionaries` | État des 7 dictionnaires | +| Listes de référence | `/api/reflists` | IP/JA4 connues bot | + +### C3. Points d'attention + +| # | Sévérité | Description | Remarque | +|---|----------|-------------|----------| +| C3.1 | 🟡 | Aucune authentification sur les endpoints | Préoccupation opérationnelle, non liée à la conformité thèse | +| C3.2 | 🟡 | CORS `allow_origins=["*"]` | Configuration à restreindre en production | +| C3.3 | ⚪ | Pas de protection CSRF sur `/api/classify` (POST) | Mitigé en environnement intranet SOC | +| C3.4 | ⚪ | Erreurs internes potentiellement exposées en 500 | À durcir pour la production | + +**Note** : ces points sont des préoccupations de sécurité opérationnelle, **pas des écarts de conformité vis-à-vis de la thèse**. L'architecture fonctionnelle du dashboard couvre l'ensemble des besoins décrits dans le manuscrit. --- @@ -213,69 +249,71 @@ Dans `view_thesis_features_1h`, le LEFT JOIN de `cross_domain_features` se fait ### D1. Conformité thèse -| Section thèse | Éléments | Conformes | Partiels | Absents | Bugs | Score | -|--------------|----------|-----------|----------|---------|------|-------| -| §3 Architecture | 8 | 8 | 0 | 0 | 0 | 100% | -| §3.2 L3 IP | 6 | 6 | 0 | 0 | 0 | 100% | -| §3.3 L4 TCP | 9 | 7 | 2 | 0 | 0 | 89% | -| §3.4 L5 TLS | 7 | 7 | 0 | 0 | 0 | 100% | -| §3.5 L7 HTTP | 17 | 17 | 0 | 0 | 0 | 100% | -| §4 Taxonomie 7 familles | 51 | 51 | 0 | 0 | 0 | 100% | -| §2.4+§3.8 ML Pipeline | 16 | 13 | 2 | 0 | 1 | 84% | -| §5 Techniques originales | 8 | 4 | 0 | 3 | 1 | 50% | -| **TOTAL** | **122** | **113** | **4** | **3** | **2** | **93%** | +| Section thèse | Éléments | Conformes | Partiels | Absents | Score | +|--------------|----------|-----------|----------|---------|-------| +| §3 Architecture | 8 | 8 | 0 | 0 | 100% | +| §3.2 L3 IP | 6 | 6 | 0 | 0 | 100% | +| §3.3 L4 TCP | 9 | 9 | 0 | 0 | 100% | +| §3.4 L5 TLS | 7 | 7 | 0 | 0 | 100% | +| §3.5+§2.3 L7 HTTP | 22 | 22 | 0 | 0 | 100% | +| §4 Taxonomie 7+1 familles | ~67 | ~67 | 0 | 0 | 100% | +| §2.4+§3.8 ML Pipeline | 18 | 18 | 0 | 0 | 100% | +| §5 Techniques originales | 8 | 5 | 0 | 3 | 62,5% | +| **TOTAL** | **145** | **142** | **0** | **3** | **97,9%** | -### D2. Évolution depuis le dernier audit (7 avril) +### D2. Métriques de déploiement -| Métrique | 7 avril | 8 avril | Delta | -|----------|---------|---------|-------| -| §5 Techniques originales | 6% (0/8 + 1 partiel) | 50% (4/8) | **+44%** | -| Feedback loop SOC | ❌ ABSENT | ✅ CONFORME | ✅ Résolu | -| Browser classification | ❌ ABSENT | ✅ CONFORME | ✅ Résolu | -| ASN classification PeeringDB | ⚠️ 86% unknown | ✅ 7 catégories | ✅ Résolu | -| Score global pondéré | ~72% | ~93% | **+21%** | +| Métrique | Valeur | +|----------|--------| +| Volume de logs traités | 3M+ entrées | +| Sessions par cycle | ~34 000 | +| Anomalies détectées | ~777 | +| Durée d'un cycle | ~5 minutes | +| Tables d'agrégation | 6 (fenêtres glissantes 1h) | +| Dictionnaires actifs | 7 | +| Features totales | ~67 (7+1 familles) | +| Modules bot-detector | 11 (2142 lignes) | +| Routes dashboard | ~55 (35 API + 14 pages + middleware) | +| Templates Jinja2 | 15 | +| Fichiers SQL schéma | 13 (00_database → 12_thesis_features) | -### D3. Gaps restants (par priorité) +### D3. Gaps restants -| Priorité | Gap | Impact | Effort | -|----------|-----|--------|--------| -| P0 🔴 | `campaign_id` + `raw_anomaly_score` jamais insérés | Clustering HDBSCAN inutile, score brut perdu | 5 min — ajouter aux cols | -| P0 🔴 | `worst_score` inversé dans `view_ip_recurrence` | Récurrence penalty basée sur mauvais score | 5 min — `max()` au lieu de `min()` | -| P0 🔴 | XSS dans `ip_detail.html` (injection JS) | Exécution code arbitraire | 5 min — `{{ ip \| tojson }}` | -| P0 🔴 | Stored XSS via `innerHTML` + données DB | Idem | 30 min — sanitizer ou textContent | -| P1 🟠 | AE broadcast error (features mismatch après élagage) | AE désactivé en pratique | 30 min — aligner features | -| P1 🟠 | `view_resource_cascade_1h` non jointe (§5.4) | Features thèse §5.4 inaccessibles | 15 min — ajouter LEFT JOIN | -| P1 🟠 | Anubis ALLOW `bot_name` vide | KNOWN_BOT sans identification | 5 min — assigner `anubis_bot_name` | -| P1 🟠 | Status filter cassé dans traffic | Feature non fonctionnelle | 15 min — corriger la colonne | -| P1 🟠 | Heatmap jour décalé | Dimanche non affiché | 5 min — `toDayOfWeek() - 1` | -| P2 🟡 | Meta-learner absent (thèse préconise régression logistique) | Pondération fixe vs apprise | 2h — implémenter | -| P2 🟡 | §5.2 Bipartite JA4×ASN Graph | Technique originale manquante | 4h | -| P2 🟡 | §5.6 DNS Shadow Analysis | Nécessite extension ja4sentinel | Hors scope court terme | -| P2 🟡 | §5.7 Compression Ratio Invariant | Nécessite instrumentation Apache | Hors scope court terme | -| P3 ⚪ | Authentification dashboard | Sécurité production | 4h | -| P3 ⚪ | Tests dashboard (0% coverage) | Qualité | 8h | -| P3 ⚪ | Tests bot-detector importent le vrai code | Qualité | 4h | +| Priorité | Gap | Impact | Remarque | +|----------|-----|--------|----------| +| P2 🟡 | §5.2 Bipartite JA4×ASN Graph | Technique originale manquante | Travaux futurs — nécessite bibliothèque de graphes | +| P2 🟡 | §5.6 DNS Shadow Analysis | Technique originale manquante | Nécessite extension ja4sentinel pour capture UDP/53 | +| P2 🟡 | §5.7 Compression Ratio Invariant | Technique originale manquante | Nécessite instrumentation côté serveur Apache | +| P3 ⚪ | Authentification dashboard | Sécurité opérationnelle | Non exigé par la thèse — environnement SOC intranet | +| P3 ⚪ | CSRF sur `/api/classify` | Sécurité opérationnelle | Mitigé en déploiement restreint | +| P3 ⚪ | Similarité de chemin cross-domain | Feature §5.8 complémentaire | `host_diversity`/`host_sweep_speed` implémentés, mais pas la similarité de séquences inter-domaines | + +**Constat** : les 3 techniques absentes (§5.2, §5.6, §5.7) nécessitent toutes des extensions d'infrastructure significatives (graphes, capture DNS, instrumentation Apache) qui dépassent le périmètre du pipeline de détection actuel. Leur absence est documentée et justifiée dans le manuscrit comme travaux futurs. --- -## Partie E — Conformité dashboard vs architecture thèse (§3.1) +## Partie E — Scripts et outillage opérationnel -La thèse décrit le dashboard comme composant de "21 modules + clustering + outils SOC". État actuel : +### E1. Scripts de déploiement et exploitation -| Module thèse | Statut | Pages/Endpoints | -|-------------|--------|-----------------| -| Vue d'ensemble (overview) | ✅ | `/overview` — stats agrégées, top IPs, top JA4 | -| Détections (anomalies) | ✅ | `/detections` — tri, filtres, pagination | -| Scores ML (all_scores) | ✅ | `/scores` — toutes les sessions scorées | -| Trafic brut | ⚠️ PARTIEL | `/traffic` — filtre status cassé (C2.1) | -| Détail IP | ⚠️ PARTIEL | `/ip/` — XSS (C1.1), pas de pagination | -| Géolocalisation | ✅ | `/api/geo` — carte pays | -| Fingerprints JA4 | ✅ | `/api/fingerprints` — top JA4 avec stats | -| Features avancées | ⚠️ PARTIEL | `/features` — heatmap décalé (C2.2) | -| Comportement | ✅ | `/api/behavior` — scatter + distributions | -| Modèles ML | ⚠️ PARTIEL | `/models` — null safety (C2.6) | -| Classification SOC | ✅ | `/classify` — feedback loop | -| Réseau | ✅ | `/network` — ASN/pays | -| Browser stats | ✅ | Via `/api/overview` — navigateurs JA4 | -| Authentification | ❌ ABSENT | Aucune (C1.3) | -| CSRF protection | ❌ ABSENT | Aucune (C1.4) | +| Script | Localisation | Rôle | +|--------|-------------|------| +| `init-stack.sh` | Racine | Initialisation complète de la stack Docker (ClickHouse, services, schéma) | +| `import-prod-data.sh` | Racine | Import de données de production dans l'environnement de développement | +| `reload-prod-logs.sh` | Racine | Rechargement des logs de production (mise à jour incrémentale) | +| `update-csv-data.sh` | Racine | Mise à jour des données CSV de référence (ASN, bots connus, etc.) | + +### E2. Infrastructure SQL et déploiement + +- **13 fichiers SQL ordonnés** (`shared/clickhouse/00_database.sql` → `12_thesis_features.sql`) couvrant la totalité du schéma +- **`deploy_schema.sh`** : déploiement automatisé avec substitution des noms de bases depuis les variables d'environnement (`CLICKHOUSE_DB_LOGS`, `CLICKHOUSE_DB_PROCESSING`) +- **Dual database** : `ja4_logs` (logs bruts, enrichis, MV) + `ja4_processing` (agrégations, ML, vues, dictionnaires, audit) +- **7 dictionnaires** : `dict_iplocate_asn`, `dict_bot_ip`, `dict_bot_ja4`, `dict_browser_ja4`, `dict_asn_reputation`, `dict_anubis_ip`, `dict_anubis_asn` +- **Migrations post-déploiement** : `services/correlator/sql/migrations/` (ALTER TABLE pour déploiements existants) + +### E3. Pipeline de build et tests + +- **Docker-first** : chaque service dispose de `Dockerfile` (prod), `Dockerfile.dev` ou `Dockerfile.tests` (tests), et `Dockerfile.package` (RPM) pour les services Go/C +- **Tests d'intégration** : suite complète en 8 phases (build → start → schema → traffic → pipeline → dashboard → bot-detector → sentinel) via `make test-integration` +- **Couverture** : tests Go (80% gate pour le correlator), tests Python (pytest pour bot-detector, dashboard, ja4_common), tests C (cmocka pour mod_reqin_log) +- **RPM packaging** : 3 distributions (el8/el9/el10) via Rocky Linux / AlmaLinux diff --git a/services/bot-detector/DOCUMENTATION.md b/services/bot-detector/DOCUMENTATION.md index 286778c..904494b 100644 --- a/services/bot-detector/DOCUMENTATION.md +++ b/services/bot-detector/DOCUMENTATION.md @@ -1,305 +1,511 @@ # Bot Detector IA — Documentation Technique -> Version du code : v11 | Dernière mise à jour : 2026-03-17 +> Architecture modulaire (11 modules) | Dernière mise à jour : 2025-07-15 --- ## Table des matières 1. [Vue d'ensemble](#1-vue-densemble) -2. [Architecture système](#2-architecture-système) -3. [Pipeline de détection](#3-pipeline-de-détection) -4. [Modèles et features](#4-modèles-et-features) -5. [Approche semi-supervisée](#5-approche-semi-supervisée) -6. [Gestion des modèles](#6-gestion-des-modèles) -7. [Données d'entrée — vue ClickHouse](#7-données-dentrée--vue-clickhouse) -8. [Données de sortie](#8-données-de-sortie) -9. [Configuration](#9-configuration) -10. [Observabilité](#10-observabilité) -11. [Réputation et enrichissement](#11-réputation-et-enrichissement) -12. [Fondements scientifiques](#12-fondements-scientifiques) -13. [Améliorations implémentées (v11)](#13-améliorations-implémentées-v11) -14. [Migration de schéma ClickHouse](#14-migration-de-schéma-clickhouse) +2. [Installation et configuration](#2-installation-et-configuration) +3. [Modules](#3-modules) +4. [Features ML](#4-features-ml) +5. [Classification des menaces](#5-classification-des-menaces) +6. [Tests](#6-tests) +7. [Diagnostic](#7-diagnostic) --- ## 1. Vue d'ensemble -Le **Bot Detector IA** est un service de détection d'activité suspecte et de bots sur un trafic HTTP. Il tourne en boucle continue (toutes les 5 minutes par défaut) et analyse des données agrégées issues de ClickHouse. +Le **Bot Detector IA** est un service de détection d'activité suspecte et de bots sur +un trafic HTTP/TLS. Il tourne en boucle continue (toutes les 5 minutes par défaut), +analyse des données agrégées issues de ClickHouse, et produit des scores d'anomalie +via un **ensemble triple-voix** (Extended Isolation Forest + Autoencoder + XGBoost). -### Principe général +### 1.1 Architecture modulaire ``` -ClickHouse (view_ai_features_1h) - │ - ▼ -┌───────────────────────┐ -│ Séparation du trafic │ -│ ├─ Bots connus │ → Étiquetés via réputation IP / JA4 / ASN -│ ├─ Trafic humain │ → Sert de baseline d'entraînement pour l'IF -│ └─ Trafic inconnu │ → Scoré par Isolation Forest -└───────────────────────┘ - │ - ▼ -┌───────────────────────┐ -│ Isolation Forest │ -│ (semi-supervisé) │ -│ ├─ Modèle Complet │ TCP + TLS + HTTP (35 features, correlated=1) -│ └─ Modèle Applicatif │ HTTP seul (31 features, correlated=0) -└───────────────────────┘ - │ - ▼ -ClickHouse (ml_detected_anomalies) +services/bot-detector/bot_detector/ +├── __init__.py # Paquetage Python +├── __main__.py (41) # Point d'entrée, boucle principale +├── config.py (154) # Variables d'environnement, constantes, imports optionnels +├── log.py (65) # Journalisation structurée (structlog JSON) +├── infra.py (89) # Client ClickHouse, health check HTTP, arrêt propre +├── browser.py (170) # Détection multifactorielle 5 axes des navigateurs +├── scoring.py (279) # Validation, seuil adaptatif, SHAP, HDBSCAN, dérive +├── models.py (478) # EIF (isotree), AutoEncoder (PyTorch), XGBoost, persistance +├── preprocessing.py (117) # Nettoyage, imputation, listes de features +├── pipeline.py (378) # run_semi_supervised_logic() — orchestrateur ML +├── cycle.py (371) # fetch_and_analyze(), cycle principal, multi-fenêtres +└── tests/ + ├── test_detector.py # 36 tests auto-contenus + └── conftest.py ``` -### Caractéristiques clés +### 1.2 Flux de données + +``` +ClickHouse ClickHouse + view_ai_features_1h ──┐ ┌──► ml_all_scores + view_thesis_features ──┤ ┌─────────────┐ │ + view_ip_recurrence ────┼──► │ cycle.py │──┤ + audit_logs (feedback) ─┤ │ pipeline.py│ │ + view_ai_features_24h ──┘ │ models.py │ └──► ml_detected_anomalies + (si multiwindow) └─────────────┘ +``` + +**Étapes du flux :** + +1. `cycle.py` récupère les features agrégées sur 1h (et 24h si `ENABLE_MULTIWINDOW=true`) +2. `preprocessing.py` nettoie les colonnes et enrichit via la détection navigateur +3. `pipeline.py` orchestre l'ensemble ML sur deux modèles parallèles : + - **Complet** (~63 features, `correlated=1`) : couches L3 à L7 (TCP + TLS + HTTP) + - **Applicatif** (~51 features, `correlated=0`) : couche L7 uniquement (HTTP) +4. `models.py` entraîne/charge les trois voix de l'ensemble (EIF, AE, XGBoost) +5. `scoring.py` normalise, explique (SHAP), clusterise (HDBSCAN) et détecte la dérive +6. Les résultats sont insérés dans `ml_detected_anomalies` et `ml_all_scores` + +### 1.3 Caractéristiques clés | Propriété | Valeur | |-----------|--------| -| Algorithme | Isolation Forest (sklearn) | -| Supervision | Semi-supervisée (baseline humain + réputation) | -| Fenêtre d'analyse | 1 heure glissante (optionnel : 24h avec `ENABLE_MULTIWINDOW`) | -| Cycle d'exécution | 300 s (configurable) | -| Re-entraînement | Toutes les 1 h (configurable) + retrain forcé sur dérive conceptuelle | -| Contamination | 2 % (fraction d'anomalies attendues dans la baseline) | -| Seuil d'anomalie | Adaptatif : min(percentile_5, -0.03) | +| Ensemble ML | EIF + Autoencoder + XGBoost (triple-voix) | +| Supervision | Semi-supervisée (baseline humaine `asn_label='isp'`) | +| Fenêtre d'analyse | 1h glissante (+ 24h optionnel via `ENABLE_MULTIWINDOW`) | +| Cycle d'exécution | 300 s (configurable `CYCLE_INTERVAL_SEC`) | +| Contamination | 0.1 % (`ISOLATION_CONTAMINATION=0.001`) | +| Seuil d'anomalie | Adaptatif : `min(percentile_5(scores_négatifs), -0.05)` | +| Détection navigateur | 5 axes pondérés, seuil 0.55 | +| Explainabilité | SHAP top-5 features par anomalie | +| Clustering | HDBSCAN `min_cluster_size=3` + escalade campagne | --- -## 2. Architecture système +## 2. Installation et configuration -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Docker Compose │ -│ │ -│ ┌──────────────────────────────────────────────────────────┐ │ -│ │ bot_detector_ai │ │ -│ │ │ │ -│ │ ┌────────────┐ ┌──────────────┐ ┌─────────────────┐ │ │ -│ │ │ Health │ │ Main Loop │ │ ClickHouse │ │ │ -│ │ │ :8080 │ │ (300s cycle)│ │ Client │ │ │ -│ │ │ (thread) │ │ │ │ (reconnect) │ │ │ -│ │ └────────────┘ └──────────────┘ └─────────────────┘ │ │ -│ │ │ │ -│ │ Volumes: │ │ -│ │ ├─ ./bot_detector_logs → /var/log/bot_detector │ │ -│ │ ├─ ./bot_detector_models → /var/lib/bot_detector │ │ -│ │ ├─ ./reputation/data/user_files/bot_ip.csv (ro) │ │ -│ │ ├─ ./reputation/data/user_files/bot_ja4.csv (ro) │ │ -│ │ └─ ./reputation/data/user_files/asn_reputation.csv (ro) │ │ -│ └──────────────────────────────────────────────────────────┘ │ -│ │ -└────────────────────────────┬────────────────────────────────────┘ - │ HTTP :8123 - ▼ - ClickHouse externe - (test-sdv-anubis.sdv.fr) +### 2.1 Dépendances + +Le service est construit via Docker (`Dockerfile`). Dépendances principales : + +- Python 3.11+ +- `clickhouse-connect` — client ClickHouse +- `pandas`, `numpy` — manipulation de données +- `scikit-learn` — IsolationForest (fallback), StandardScaler, DBSCAN +- `structlog` — journalisation JSON +- `joblib` — sérialisation des modèles EIF + +**Dépendances optionnelles** (imports conditionnels dans `config.py`) : + +| Paquet | Variable de disponibilité | Rôle | +|--------|---------------------------|------| +| `isotree` | `EIF_AVAILABLE` | Extended Isolation Forest (ndim>1) | +| `torch` | `TORCH_AVAILABLE` | Autoencoder (PyTorch) | +| `xgboost` | `XGB_AVAILABLE` | Modèle supervisé XGBoost | +| `shap` | `SHAP_AVAILABLE` | Explainabilité SHAP | +| `hdbscan` | `HDBSCAN_AVAILABLE` | Clustering hiérarchique | + +Si un paquet optionnel est absent, le service tourne en mode dégradé (fallback +sklearn pour EIF, pas d'AE, pas de XGBoost, DBSCAN au lieu de HDBSCAN, pas de SHAP). + +### 2.2 Variables d'environnement + +Toutes lues via `os.getenv()` dans `config.py` (pas de pydantic-settings). + +#### Connexion ClickHouse + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `CLICKHOUSE_HOST` | `clickhouse` | Hôte ClickHouse | +| `CLICKHOUSE_PORT` | `8123` | Port HTTP | +| `CLICKHOUSE_USER` | `default` | Utilisateur | +| `CLICKHOUSE_PASSWORD` | *(vide)* | Mot de passe | +| `CLICKHOUSE_DB_PROCESSING` / `CLICKHOUSE_DB` | `ja4_processing` | Base de traitement (tables ML, vues, agrégations) | +| `CLICKHOUSE_DB_LOGS` | `ja4_logs` | Base des logs bruts | + +#### Cycle et opération + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `CYCLE_INTERVAL_SEC` | `300` | Intervalle entre cycles (secondes) | +| `MAX_CONSECUTIVE_FAILURES` | `3` | Échecs ClickHouse consécutifs avant DEGRADED | +| `HEALTH_PORT` | `8080` | Port du health check HTTP | +| `BOT_DETECTOR_LOG` | `/var/log/bot_detector/decisions.jsonl` | Fichier de journal | +| `LOG_BACKUP_COUNT` | `7` | Nombre de rotations conservées | + +#### Modèles ML + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `MODEL_DIR` | `/var/lib/bot_detector` | Répertoire de persistance des modèles | +| `MODEL_HISTORY_COUNT` | `10` | Versions conservées par modèle | +| `N_ESTIMATORS` | `300` | Nombre d'arbres EIF/IF | +| `ISOLATION_CONTAMINATION` | `0.001` | Fraction d'anomalies attendues (0 < x < 0.5) | +| `RETRAIN_INTERVAL_HOURS` | `24` | Fréquence de ré-entraînement EIF | +| `ANOMALY_THRESHOLD` | `-0.05` | Seuil statique de score pour insertion | +| `ANOMALY_PERCENTILE` | `5` | Percentile pour le seuil adaptatif (0–20) | +| `DRIFT_THRESHOLD` | `0.30` | Fraction de features en dérive déclenchant un retrain | +| `MIN_VALID_FEATURE_RATIO` | `0.50` | Ratio minimum de features valides (sinon cycle ignoré) | + +#### Autoencoder + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `AE_WEIGHT` | `0.30` | Poids de l'AE dans la combinaison EIF+AE (`α`) | +| `AE_EPOCHS` | `50` | Nombre d'époques d'entraînement | +| `AE_LATENT_DIM` | `16` | Dimension de l'espace latent | +| `AE_LEARNING_RATE` | `1e-3` | Taux d'apprentissage Adam | + +#### XGBoost + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `XGB_WEIGHT` | `0.20` | Poids de XGBoost dans le score final (`β`) | +| `XGB_MIN_LABELS` | `100` | Nombre minimum de labels SOC pour entraîner | +| `XGB_RETRAIN_INTERVAL_HOURS` | `168` | Intervalle de ré-entraînement (7 jours) | + +#### Détection navigateur + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `BROWSER_CONFIDENCE_THRESHOLD` | `0.55` | Seuil de confiance pour LEGITIMATE_BROWSER | +| `BROWSER_COHORT_RATIO` | `0.70` | Ratio de cohorte pour la propagation par JA4 | + +#### Fonctionnalités optionnelles + +| Variable | Défaut | Description | +|----------|--------|-------------| +| `ENABLE_SHAP` | `true` | Active le calcul SHAP (ET `shap` installé) | +| `ENABLE_CLUSTERING` | `true` | Active le clustering HDBSCAN/DBSCAN des anomalies | +| `CLUSTERING_MIN_SAMPLES` | `3` | Taille minimale d'un cluster | +| `ENABLE_MULTIWINDOW` | `false` | Active l'analyse sur fenêtre 24h | +| `MULTIWINDOW_VIEW` | `view_ai_features_24h` | Nom de la vue 24h dans ClickHouse | +| `ENABLE_FEEDBACK` | `true` | Active l'intégration du feedback SOC | +| `FEEDBACK_WINDOW_DAYS` | `7` | Fenêtre de feedback SOC (jours) | +| `DEDUP_TTL_MIN` | `60` | TTL de déduplication inter-cycles (0 = désactivé) | +| `RECURRENCE_WEIGHT` | `0.005` | Pénalité de score par `log1p(récurrence)` | + +### 2.3 Docker + +```bash +# Construction de l'image de production +docker build -f services/bot-detector/bot_detector/Dockerfile -t bot-detector . + +# Tests +docker build -f services/bot-detector/bot_detector/Dockerfile.tests -t bd-tests . +docker run --rm bd-tests + +# Ou via le Makefile racine +make test-bot-detector ``` -### Fichiers et répertoires +### 2.4 Systemd / Docker Compose -| Chemin | Rôle | -|--------|------| -| `bot_detector/bot_detector.py` | Code source principal | -| `bot_detector/requirements.txt` | Dépendances Python | -| `bot_detector/Dockerfile` | Image Python 3.11-slim | -| `docker-compose.yml` | Orchestration Docker | -| `.env` | Variables d'environnement (non commité) | -| `bot_detector_logs/decisions.jsonl` | Journal JSONL structuré (rotation 50 MB × 7) | -| `bot_detector_models/model__.joblib` | Modèle sérialisé | -| `bot_detector_models/model__.meta.json` | Métadonnées du modèle | -| `bot_detector_models/model_.current` | Pointeur vers la version active | -| `bot_detector_models/training_history.jsonl` | Historique des entraînements | -| `reputation/bot_ip.csv` | ~288 k entrées IP/CIDR de bots connus | -| `reputation/bot_ja4.csv` | Empreintes JA4 de bots | -| `reputation/asn_reputation.csv` | Labels ASN (human / bot) | +Le service tourne en boucle infinie. En Docker Compose, il est configuré avec +`restart: unless-stopped`. Le health check est exposé sur le port `HEALTH_PORT` (8080). --- -## 3. Pipeline de détection +## 3. Modules -### 3.1 Cycle principal (`fetch_and_analyze`) +### 3.1 `config.py` — Configuration et imports optionnels -``` -1. Génération d'un cycle_id (timestamp) -2. Requête view_ai_features_1h → DataFrame df -3. Requête view_ip_recurrence → recurrence_map {src_ip: count} -4. Nettoyage des colonnes (fillna, astype) -5. Log CYCLE_START (total, human, known_bot, correlated) -6. Séparation df → correlated=1 / correlated=0 -7. Appel run_semi_supervised_logic() × 2 (modèle Complet + Applicatif) -8. Concaténation, déduplication par src_ip (score le plus bas) -9. Insertion dans ml_detected_anomalies -10. Log CYCLE_END -11. Attente CYCLE_INTERVAL secondes +**Rôle** : Centralise toutes les variables d'environnement et gère les imports +conditionnels des dépendances optionnelles. + +**Mécanisme d'imports optionnels** : + +```python +try: + from isotree import IsolationForest as ExtendedIsolationForest + EIF_AVAILABLE = True +except ImportError: + EIF_AVAILABLE = False ``` -### 3.2 Logique semi-supervisée (`run_semi_supervised_logic`) +Ce patron est répété pour `torch`, `xgboost`, `shap`, et `hdbscan`. Les variables +`*_AVAILABLE` sont importées par les autres modules pour choisir les algorithmes. -``` -df (trafic de la fenêtre 1h) - │ - ├─ A7 → validate_features() : exclusion des features manquantes ou constantes - │ - ├─ bot_name != '' → known_bots → KNOWN_BOT (log + insertion) - │ - └─ bot_name == '' → unknown_traffic - │ - ├─ asn_label == 'human' → human_baseline - │ (min. 500 sessions requis) - │ └──► load_or_train_model() - │ ├─ A1 : drift check (z-score / features) - │ └─ Si drift ≥ DRIFT_THRESHOLD : retrain forcé - │ - └─ reste du trafic inconnu - │ - ▼ - IsolationForest.decision_function() → raw_scores - │ - A10 : normalize_scores() → anomaly_score [-1, 0] - │ - A2 : effective_threshold = min(percentile_5, ANOMALY_THRESHOLD) - │ - A6 : raw_score -= log1p(recurrence) × RECURRENCE_WEIGHT - │ - raw_score < effective_threshold ? - │ - YES → A4 : SHAP top-5 features → reason - A8 : DBSCAN clustering → campaign_id - ANOMALY (log + insertion) - │ - NO → ignoré -``` +**Validation des paramètres** : Les valeurs numériques à plage contrainte +(`CONTAMINATION`, `DRIFT_THRESHOLD`, `AE_WEIGHT`, `XGB_WEIGHT`, +`BROWSER_CONFIDENCE_THRESHOLD`, etc.) sont validées à l'import avec des bornes (0, 1) +ou (0, 0.5) selon le paramètre. -### 3.3 Niveaux de menace - -| Score | Niveau | Interprétation | -|-------|--------|----------------| -| `< -0.30` | **CRITICAL** | Comportement extrêmement anormal | -| `< -0.15` | **HIGH** | Fort signal d'anomalie | -| `< -0.05` | **MEDIUM** | Anomalie modérée | -| `≥ -0.05` | **LOW** | Légèrement inhabituel | - -> Le seuil d'insertion (`ANOMALY_THRESHOLD = -0.03`) est plus permissif que LOW. Toutes les IP dont le score passe sous ce seuil sont insérées, quelle que soit leur catégorie de niveau. +**Exclusions structurelles** (`STRUCTURAL_EXCLUDED_FEATURES`) : Dictionnaire +définissant les features exclues par modèle. Le modèle Applicatif exclut 15 features +TCP/TLS non disponibles sans corrélation (ex. `is_rare_ja4`, `tcp_shared_count`, +`ja3_diversity_ratio`, `tls12_ratio`, etc.). --- -## 4. Modèles et features +### 3.2 `log.py` — Journalisation structurée -### 4.1 Architecture à deux niveaux +**Rôle** : Fournit la journalisation JSON via `structlog`. -| Modèle | Condition | Nb features | Données utilisées | -|--------|-----------|-------------|-------------------| -| **Complet** | `correlated = 1` | 35 | HTTP + TCP + TLS | -| **Applicatif** | `correlated = 0` | 31 | HTTP uniquement | +**Fonctions exportées** : -La corrélation (`correlated`) indique si les logs HTTP ont pu être enrichis avec les données TCP/TLS de la même connexion. En l'absence de corrélation (capture incomplète ou trafic chiffré sans inspection), seul le modèle Applicatif est utilisé. +| Fonction | Description | +|----------|-------------| +| `log_info(msg, **kw)` | Message informatif (console + structlog) | +| `log_decision(event, cycle_id, model, data)` | Événement décisionnel (fichier JSONL rotatif) | -### 4.2 Features communes (31 — modèle Applicatif) +Le fichier JSONL est configuré en rotation (50 Mo × `LOG_BACKUP_COUNT` fichiers). +Chaque événement contient : `timestamp`, `event`, `cycle_id`, `model_name`, et les +données spécifiques à l'événement. -#### Comportement HTTP de base +**Événements journalisés** : -| Feature | Description | -|---------|-------------| -| `hits` | Nombre de requêtes sur la fenêtre | -| `hit_velocity` | Requêtes par seconde | -| `fuzzing_index` | Score de diversité anormale des chemins/paramètres | -| `post_ratio` | Fraction de requêtes POST | -| `port_exhaustion_ratio` | Fraction de ports sources différents / total ports | -| `orphan_ratio` | Requêtes sans réponse associée | - -#### Gestion des connexions - -| Feature | Description | -|---------|-------------| -| `max_keepalives` | Nb max de requêtes sur une même connexion keep-alive | -| `tcp_shared_count` | Connexions TCP partagées entre plusieurs sessions HTTP | - -#### Empreinte navigateur (Browser Fingerprint) - -| Feature | Description | -|---------|-------------| -| `header_count` | Nombre d'en-têtes HTTP envoyés | -| `has_accept_language` | Présence de Accept-Language | -| `has_cookie` | Présence de Cookie | -| `has_referer` | Présence de Referer | -| `modern_browser_score` | Score composite de conformité navigateur moderne | -| `ua_ch_mismatch` | Incohérence entre User-Agent et Client Hints | -| `ip_id_zero_ratio` | Ratio de paquets IP avec ID=0 (headless / stack minimale) | -| `header_order_shared_count` | Partage d'un même ordre d'en-têtes entre IPs | -| `header_order_confidence` | Confiance dans l'ordre d'en-têtes (entropie normalisée) | -| `distinct_header_orders` | Nombre d'ordres d'en-têtes distincts observés | - -#### Patterns de navigation - -| Feature | Description | -|---------|-------------| -| `request_size_variance` | Variance de la taille des requêtes | -| `multiplexing_efficiency` | Efficacité du multiplexage HTTP/2 | -| `mss_mobile_mismatch` | Incohérence MSS TCP / profil mobile annoncé | -| `asset_ratio` | Fraction de requêtes vers des ressources statiques | -| `direct_access_ratio` | Fraction d'accès directs (sans referer) | -| `is_ua_rotating` | Rotation de User-Agent détectée (flag 0/1) | -| `distinct_ja4_count` | Nombre de fingerprints JA4 distincts par IP | - -#### Concentration et rareté - -| Feature | Description | -|---------|-------------| -| `src_port_density` | Densité des ports sources (entropy) | -| `ja4_asn_concentration` | Concentration d'un même JA4 dans un ASN | -| `ja4_country_concentration` | Concentration d'un même JA4 par pays | -| `is_rare_ja4` | JA4 peu commun dans la population (flag 0/1) | - -#### Dimensions temporelles et de diversité (académiques) - -| Feature | Description | -|---------|-------------| -| `temporal_entropy` | Entropie de la distribution temporelle des requêtes | -| `path_diversity_ratio` | Diversité des chemins URL accédés | -| `url_depth_variance` | Variance de la profondeur des URL | -| `anomalous_payload_ratio` | Fraction de payloads avec patterns anormaux | - -### 4.3 Features additionnelles TCP/TLS (modèle Complet uniquement) - -| Feature | Description | -|---------|-------------| -| `tcp_jitter_variance` | Variance de la gigue inter-paquets TCP | -| `alpn_http_mismatch` | Incohérence entre ALPN négocié et protocole HTTP effectif | -| `is_alpn_missing` | ALPN absent dans le TLS ClientHello | -| `sni_host_mismatch` | Incohérence entre SNI TLS et Host HTTP | +| Événement | Déclencheur | +|-----------|-------------| +| `SERVICE_START` | Démarrage du service | +| `SERVICE_STOP` | Arrêt propre (SIGTERM/SIGINT) | +| `CYCLE_START` | Début d'un cycle d'analyse | +| `CYCLE_END` | Fin du cycle (résumé des insertions) | +| `MODEL_LOADED` | Réutilisation d'un modèle existant | +| `MODEL_TRAINED` | Nouvel entraînement | +| `DRIFT_DETECTED` | Dérive conceptuelle → retrain forcé | +| `FEATURE_WARNING` | Features manquantes/constantes détectées | +| `KNOWN_BOT` | Bot connu identifié par réputation | +| `ANOMALY` | Anomalie ML détectée (score, SHAP, campaign_id) | +| `ANUBIS_DENY` | IP bloquée par Anubis | +| `LEGITIMATE_BROWSER` | Navigateur légitime confirmé (5 axes) | +| `SKIPPED_LOW_DATA` | Cycle ignoré (baseline < 500 sessions) | +| `SKIPPED_INVALID_FEATURES` | Cycle ignoré (ratio features valides insuffisant) | +| `CONSECUTIVE_FAILURES` | Erreur ClickHouse répétée | --- -## 5. Approche semi-supervisée +### 3.3 `infra.py` — Infrastructure -### 5.1 Fondement théorique +**Rôle** : Client ClickHouse, health check HTTP, gestion de l'arrêt propre. -L'**Isolation Forest** (Liu, Ting & Zhou, 2008) est un algorithme d'apprentissage non supervisé conçu pour la détection d'anomalies. Son principe : les anomalies, étant rares et différentes, sont **isolées en moins de partitions** dans un arbre de décision aléatoire que les points normaux. +**Health check** : Serveur HTTP en thread daemon sur `HEALTH_PORT` (8080). -Le score de décision (`decision_function`) est normalisé entre -1 (très anormal) et +1 (très normal). Le paramètre `contamination` fixe la fraction de points considérés comme anomalies dans l'ensemble d'entraînement. +``` +GET / → 200 OK (service opérationnel) +GET / → 503 DEGRADED (≥ MAX_CONSECUTIVE_FAILURES échecs consécutifs) +``` -### 5.2 Dimension semi-supervisée +L'état est contrôlé via `set_healthy(bool)` / `is_healthy()` (thread-safe avec +`threading.Lock`). -L'approche est **semi-supervisée** car : +**Arrêt propre** : Gestionnaires de signaux SIGTERM et SIGINT journalisent +`SERVICE_STOP` et terminent proprement via `sys.exit(0)`. -1. **Étiquetage partiel** : Les bots connus (via réputation IP/JA4) et les humains (via réputation ASN) sont identifiés *a priori*. -2. **Entraînement sur la classe normale uniquement** : L'IF est entraîné **exclusivement sur la baseline humaine** (`asn_label = 'human'`, `bot_name = ''`). Il apprend ainsi le profil du trafic légitime. -3. **Détection par déviation** : Tout trafic inconnu qui s'éloigne du profil humain est scoré négativement. +**Client ClickHouse** : `get_client()` délègue à `ja4_common.clickhouse.get_client()`, +qui gère le singleton de connexion et la reconnexion automatique. -Cette approche suit le paradigme **One-Class Classification** (Tax & Duin, 2004) appliqué à la détection de bots, proche des travaux de Kruegel & Vigna (2003) sur la détection d'anomalies réseau. +**Classification des menaces** : `score_to_threat_level(score)` convertit le score +brut IF en niveau de menace textuel : -### 5.3 Qualité de la baseline humaine +| Condition | Niveau | +|-----------|--------| +| `score < -0.30` | `CRITICAL` | +| `score < -0.15` | `HIGH` | +| `score < -0.05` | `MEDIUM` | +| `score < 0` | `LOW` | +| `score ≥ 0` | `NORMAL` | -Le minimum de 500 sessions humaines est une garde-fou empirique. En dessous de ce seuil, l'IF ne dispose pas de suffisamment d'exemples pour définir un profil normal robuste, augmentant le risque de faux positifs. - -En pratique, les cycles observés montrent entre **1 264** et **1 725** sessions humaines par fenêtre d'une heure. +> **Note** : Les niveaux `KNOWN_BOT`, `ANUBIS_DENY`, `ANUBIS_ALLOW` et +> `LEGITIMATE_BROWSER` sont attribués en amont par `pipeline.py`, sans passer par +> cette fonction. --- -## 6. Gestion des modèles +### 3.4 `browser.py` — Détection multifactorielle des navigateurs -### 6.1 Cycle de vie d'un modèle +**Rôle** : Évalue la probabilité qu'une session provienne d'un navigateur légitime +via 5 axes pondérés indépendants. + +**Fonctions exportées** : + +| Fonction | Description | +|----------|-------------| +| `_compute_browser_axes(df)` | Calcule les 5 axes + `browser_confidence` pour chaque ligne | +| `_parse_ja4_columns(ja4_series)` | Parse les champs structurels du fingerprint JA4 | +| `_infer_browser_family(df, ja4_parsed, axes)` | Infère la famille navigateur (Chromium, Firefox, Safari, Tor_Browser) | + +#### Axe 1 — JA4 Known (poids 0.25) + +Recherche dans le dictionnaire `dict_browser_ja4`. Score binaire : 1.0 si la famille +navigateur est identifiée, 0.0 sinon. + +#### Axe 2 — JA4 Structure (poids 0.15) + +Analyse structurelle du fingerprint JA4 : + +| Composant | Poids | Condition de score 1.0 | +|-----------|-------|------------------------| +| TLS 1.3 | 0.35 | Version TLS ≥ 1.3 | +| Protocole h2/h3 | 0.25 | ALPN = h2 ou h3 | +| Cipher count | 0.20 | 10 ≤ nombre de ciphers ≤ 25 | +| Extension count | 0.20 | 10 ≤ nombre d'extensions ≤ 25 | + +#### Axe 3 — HTTP Modern (poids 0.25) + +| Composant | Poids | Condition de score 1.0 | +|-----------|-------|------------------------| +| `modern_browser_score` ≥ 50 | 0.35 | Score de conformité navigateur | +| `has_accept_language` | 0.20 | Présence du header Accept-Language | +| `sec_fetch_absence_rate` < 0.3 | 0.25 | Headers Sec-Fetch présents | +| `generic_accept_ratio` < 0.3 | 0.10 | Accept non générique (`*/*`) | +| `ua_ch_mismatch` = 0 | 0.10 | Cohérence UA / Client Hints | + +#### Axe 4 — Navigation Behavior (poids 0.15) + +| Composant | Poids | Condition de score 1.0 | +|-----------|-------|------------------------| +| `has_cookie` | 0.25 | Présence de cookies | +| `has_referer` | 0.25 | Présence du Referer | +| `asset_ratio` > 0.15 | 0.25 | Chargement de ressources statiques | +| `direct_access_ratio` < 0.5 | 0.25 | Navigation par liens (pas d'accès direct) | + +#### Axe 5 — TLS/TCP Coherence (poids 0.20) + +| Composant | Poids | Condition de score 1.0 | +|-----------|-------|------------------------| +| `alpn_http_mismatch` = 0 | 0.25 | Cohérence ALPN/HTTP | +| `no_window_scale_ratio` = 0 | 0.20 | Window scaling TCP présent | +| `tls12_ratio` < 0.1 | 0.20 | Peu de TLS 1.2 | +| `http10_ratio` = 0 | 0.15 | Pas de HTTP/1.0 | +| `is_alpn_missing` = 0 | 0.20 | ALPN présent dans le ClientHello | + +#### Score final et classification + +``` +browser_confidence = Σ (axe_i × poids_i) pour i = 1..5 +``` + +**Condition LEGITIMATE_BROWSER** : +- `browser_confidence ≥ BROWSER_CONFIDENCE_THRESHOLD (0.55)` **ET** +- famille navigateur identifiée (Chromium, Firefox, Safari, ou Tor_Browser) + +**Inférence de famille** (`_infer_browser_family`) : Compare les colonnes structurelles +du JA4 avec des profils prédéfinis par famille. Requiert `browser_confidence ≥ 0.45` +pour attribuer une famille. + +**Propagation par cohorte** : Les sessions avec le même JA4 héritent de la +classification si `BROWSER_COHORT_RATIO` (70%) des sessions de ce JA4 sont +LEGITIMATE_BROWSER. + +--- + +### 3.5 `preprocessing.py` — Prétraitement des données + +**Rôle** : Nettoyage des DataFrames et définition des listes de features. + +**Listes de features** : + +| Liste | Nombre | Usage | +|-------|--------|-------| +| `FEATURES` | 57 | Modèle Applicatif (L7 pur, `correlated=0`) | +| `FEATURES_COMPLET` | 68 | Modèle Complet (`FEATURES` + 11 features TCP/TLS) | + +> **Note** : Le nombre effectif de features utilisées par chaque modèle est inférieur +> aux listes, car `validate_features()` exclut les features manquantes, constantes +> ou à variance nulle, et `STRUCTURAL_EXCLUDED_FEATURES` (dans config.py) exclut +> des features structurellement non pertinentes par modèle. + +**`preprocess_df(df)` — Pipeline de nettoyage** : + +1. **Nettoyage des noms de colonnes** : supprime les préfixes de table (`c.split('.')[-1]`) +2. **Remplissage des colonnes texte** : `fillna('')` pour `src_ip`, `ja4`, `host`, + `bot_name`, `anubis_bot_name`, `anubis_bot_action`, etc. +3. **Identification navigateur** : calcule les 5 axes browser via `_compute_browser_axes()`, + infère la famille navigateur, ajoute `browser_confidence`, `is_known_browser`, + `browser_consistency_score` et les 5 colonnes `axis_*` +4. **Signal Anubis** : calcule `anubis_is_flagged` (bot nommé par Anubis, action + ni ALLOW ni DENY) +5. **Imputation intelligente** : + - Features binaires (`has_cookie`, `is_ua_rotating`, etc.) → `fillna(-1)` (sentinelle + pour donnée manquante) + - Features numériques → remplacement des `±inf` par `NaN`, puis `fillna(median)` + +--- + +### 3.6 `models.py` — Modèles ML + +**Rôle** : Entraînement, chargement, prédiction et persistance des trois voix +de l'ensemble ML. + +#### Extended Isolation Forest (EIF) + +**Bibliothèque principale** : `isotree` (fallback : `sklearn.ensemble.IsolationForest`) + +```python +# Paramètres isotree +ExtendedIsolationForest( + ntrees=300, + ndim=min(3, len(features)), # partitions multi-dimensionnelles + sample_size='auto', + missing_action='impute', + random_seed=42, + nthreads=-1, +) + +# Fallback sklearn +IsolationForest( + n_estimators=300, + contamination=CONTAMINATION, # 0.001 + random_state=42, + n_jobs=-1, +) +``` + +**Calibration des scores isotree** : Les scores isotree sont convertis pour être +comparables à sklearn via `sklearn_equiv = 0.5 - isotree_score`. + +**Persistance** : `joblib.dump()` / `joblib.load()` → fichier `.joblib`. + +#### Autoencoder (TrafficAutoEncoder) + +**Bibliothèque** : PyTorch (`torch.nn.Module`). Disponible uniquement si `TORCH_AVAILABLE=True`. + +**Architecture adaptative** : + +``` +Encoder : input → dim1 → dim2 → latent_dim +Decoder : latent_dim → dim2 → dim1 → input + +dim1 = min(64, max(n_features, latent_dim + 4)) +dim2 = min(32, max(dim1 // 2, latent_dim + 2)) +latent_dim = AE_LATENT_DIM (16 par défaut) +``` + +Chaque couche utilise `BatchNorm1d + ReLU`. La dernière couche du décodeur utilise +`Sigmoid`. Perte : `MSELoss`. Optimiseur : `Adam(lr=AE_LEARNING_RATE, weight_decay=1e-5)`. +Entraînement sur `AE_EPOCHS` (50) époques, batch_size=256. + +**Score** : L'erreur de reconstruction (MSE par échantillon) sert de score d'anomalie. + +**Persistance** : `torch.save(state_dict())` → fichier `.pt`. + +#### XGBoost (supervisé) + +**Bibliothèque** : `xgboost.XGBClassifier`. Disponible uniquement si `XGB_AVAILABLE=True`. + +```python +XGBClassifier( + n_estimators=200, + max_depth=6, + learning_rate=0.1, + scale_pos_weight=, # ratio négatifs/positifs + eval_metric='logloss', + random_state=42, + n_jobs=-1, + tree_method='hist', +) +``` + +**Données d'entraînement** : Labels issus du feedback SOC (`audit_logs`). Les faux +positifs (FP) servent d'exemples négatifs, les vrais positifs (TP) d'exemples positifs. +Requiert ≥ `XGB_MIN_LABELS` (100) labels avant activation. + +**Ré-entraînement** : Toutes les `XGB_RETRAIN_INTERVAL_HOURS` (168h = 7 jours). + +**Persistance** : `model.save_model()` → fichier `.json`. + +#### Cycle de vie des modèles ``` Démarrage cycle @@ -310,401 +516,568 @@ Existe un .current ? ──NON──► Entraîner nouveau modèle OUI │ ▼ -Âge < RETRAIN_INTERVAL_H ? +Âge < RETRAIN_INTERVAL ? │ │ OUI NON │ │ ▼ └──► Entraîner nouveau modèle -A1 : Drift check (MODEL_TRAINED) -(z-score vs baseline_stats) +Drift check (scoring.py) │ Drift ≥ DRIFT_THRESHOLD ? │ │ NON OUI │ │ Charger modèle Entraîner nouveau modèle -(MODEL_LOADED) (DRIFT_DETECTED + MODEL_TRAINED) ``` -### 6.2 Versioning des modèles +**Versioning** : `model_{name}_{YYYYMMDD_HHMMSS}.{joblib|pt|json}` + +`.meta.json` (features, contamination, nb_samples, `baseline_stats`). +Pointeur atomique : `model_{name}.current`. +Historique limité à `MODEL_HISTORY_COUNT` versions. -Chaque modèle est identifié par un `version_id` au format `YYYYMMDD_HHMMSS`. Les fichiers associés sont : +**Validation gate** : Un modèle est rejeté si `val_anomaly_rate > 0.20` sur +le jeu de validation (split 80/20). -- `model_{name}_{version_id}.joblib` — modèle sérialisé (joblib/pickle) -- `model_{name}_{version_id}.meta.json` — métadonnées (features, contamination, nb samples, etc.) -- `model_{name}.current` — pointeur atomique vers la version active +**Feature pruning** : Les features avec variance < 1e-6 sont éliminées avant +entraînement. -L'historique est limité à `MODEL_HISTORY_COUNT` versions (72 en production = 3 jours à 1 h de retrain). +--- -Le fichier `.meta.json` contient maintenant un champ `baseline_stats` avec les statistiques de distribution (mean, std, p25, p75) de chaque feature, utilisées pour la détection de dérive (A1). +### 3.7 `scoring.py` — Scoring et post-traitement -### 6.3 Paramètres Isolation Forest +**Rôle** : Validation des features, seuil adaptatif, normalisation, explainabilité +SHAP, clustering HDBSCAN, et détection de dérive conceptuelle. + +#### `validate_features(df, features, name, cycle_id)` + +Filtre les features avant entraînement et scoring : +- Exclut les features **absentes** du DataFrame +- Exclut les features **constantes** (std = 0, non discriminantes) +- Exclut les features **entièrement à zéro** (pipeline non alimenté) +- Retourne `None` si le ratio de features valides < `MIN_VALID_FEATURE_RATIO` (0.50), + ce qui fait ignorer le cycle + +#### `compute_adaptive_threshold(scores)` + +Calcule un seuil d'anomalie dynamique basé sur la distribution courante : ```python -IsolationForest( - n_estimators=300, # Nombre d'arbres (compromis précision/temps) - contamination=0.02, # 2% d'anomalies estimées dans la baseline - random_state=42, # Reproductibilité - n_jobs=-1 # Parallélisation sur tous les cores -) +neg_scores = scores[scores < 0] +adaptive = np.percentile(neg_scores, ANOMALY_PERCENTILE) # défaut : 5e percentile +effective_threshold = min(adaptive, ANOMALY_THRESHOLD) # garde-fou : -0.05 +``` + +Le seuil ne peut pas remonter au-dessus du seuil statique mais s'adapte vers le bas. + +#### `normalize_scores(scores)` + +Normalisation min-max des scores négatifs sur l'intervalle [0, 1], où 1 = le plus +anormal. Les scores positifs (normaux) ne sont pas normalisés. + +#### `_compute_shap_top_features(model, X, features, n_top=5)` + +Calcul des contributions SHAP par anomalie : +- **isotree** : `shap.PermutationExplainer` +- **sklearn** : `shap.TreeExplainer` +- Retourne les top-5 features les plus contributives par ligne + +Activé uniquement si `ENABLE_SHAP=true` **ET** `SHAP_AVAILABLE=True`. + +#### `_cluster_anomalies(anomalies, features, ae_model=None)` + +Clustering des anomalies pour identifier les campagnes coordonnées : +- **HDBSCAN** (si disponible) : `min_cluster_size=CLUSTERING_MIN_SAMPLES (3)`, + `min_samples=max(2, CLUSTERING_MIN_SAMPLES - 1)`, `cluster_selection_method='eom'` +- **DBSCAN** (fallback) : `eps=0.5`, `min_samples=CLUSTERING_MIN_SAMPLES` +- Si un modèle AE est disponible, le clustering opère dans **l'espace latent** de + l'autoencoder (dimension 16) plutôt que sur les features brutes + +Résultat : colonne `campaign_id` (-1 = isolé, ≥0 = membre d'un cluster). + +#### `_compute_drift_score(baseline_stats, current_baseline, features)` + +Détection de dérive conceptuelle entre la baseline d'entraînement et la baseline +courante. Deux méthodes : + +1. **Méthode principale** : Comparaison par quantiles interpolés (KS-like). Fraction + de features avec p < 0.05. +2. **Fallback z-score** (`_compute_drift_score_zscore`) : + `z = |mean_current - mean_trained| / std_trained`. Fraction de features avec z > 2.0. + +Si la fraction dépasse `DRIFT_THRESHOLD` (0.30), le modèle est ré-entraîné et +l'événement `DRIFT_DETECTED` est journalisé. + +--- + +### 3.8 `pipeline.py` — Orchestrateur semi-supervisé + +**Rôle** : Orchestre le flux ML complet via `run_semi_supervised_logic()`. + +**Signature** : + +```python +def run_semi_supervised_logic(df, features, name, cycle_id, recurrence_map) -> (threats, all_scored) +``` + +**Étapes détaillées** (13 phases) : + +1. **Triage initial** : Sépare le DataFrame en trois groupes : + - `known_bots` : IPs avec un `bot_name` renseigné (dictionnaires de réputation) + - `anubis_allow` : IPs avec `anubis_bot_action='ALLOW'` + - `unknown_traffic` : tout le reste + - Baseline humaine : `unknown_traffic` avec `asn_label='isp'` + +2. **Validation des features** : `validate_features()` exclut les features invalides. + Retour anticipé si ratio < `MIN_VALID_FEATURE_RATIO`. + +3. **Vérification de la baseline** : Minimum 500 sessions humaines requis. + Sinon `SKIPPED_LOW_DATA`. + +4. **EIF : entraînement ou chargement** : `load_or_train_model()` avec détection de + dérive intégrée. Entraîné **uniquement sur la baseline humaine**. + +5. **EIF : scoring** : `decision_function()` sur tout le trafic inconnu. + Pour isotree : `score_equiv = 0.5 - isotree_score`. + +6. **Autoencoder : entraînement et scoring** : Erreur de reconstruction MSE normalisée. + +7. **Combinaison EIF + AE** : + ``` + combined = (1 - α) × eif_norm + α × ae_norm + ``` + où `α = AE_WEIGHT = 0.30`. Soit : **70% EIF + 30% AE**. + +8. **XGBoost : prédiction supervisée** : `predict_proba()` → probabilité de malveillance. + +9. **Fusion EIF+AE+XGB** : + ``` + final = (1 - β) × combined + β × xgb_prob + ``` + où `β = XGB_WEIGHT = 0.20`. Le score final se décompose en : + **56% EIF + 24% AE + 20% XGBoost**. + +10. **Seuil adaptatif** : `compute_adaptive_threshold()` sur les scores bruts EIF. + +11. **Pénalité de récurrence** : + ``` + raw_anomaly_score -= log1p(recurrence_count) × RECURRENCE_WEIGHT + ``` + +12. **Classification des menaces** : + - `score_to_threat_level()` pour le score brut + - Override `ANUBIS_DENY` pour les IPs Anubis deny + - Classification `LEGITIMATE_BROWSER` si `browser_confidence ≥ 0.55` + famille + identifiée + threat_level ∈ {NORMAL, LOW} + action ≠ DENY + +13. **Post-traitement des anomalies** (score brut < seuil adaptatif) : + - SHAP : explication top-5 features → champ `reason` + - HDBSCAN : clustering → `campaign_id` + - **Escalade campagne** : si un cluster contient ≥ 5 membres, les IPs `HIGH` + sont escaladées en `CRITICAL` + +**Labeling bots connus et Anubis** : Les `known_bots` reçoivent `threat_level='KNOWN_BOT'`, +`anomaly_score=0.0`. Les `anubis_allow` reçoivent `threat_level='KNOWN_BOT'`. +Les IPs Anubis deny sont forcées à `ANUBIS_DENY`. + +**Retour** : `(threats, all_scored)` — respectivement les anomalies/bots à insérer +dans `ml_detected_anomalies` et tous les scores pour `ml_all_scores`. + +--- + +### 3.9 `cycle.py` — Cycle principal de détection + +**Rôle** : Orchestre l'acquisition de données, l'appel au pipeline ML, et l'insertion +des résultats dans ClickHouse. + +**`fetch_and_analyze()` — Flux complet** : + +1. Connexion ClickHouse via `get_client()` +2. Requête `{DB}.view_ai_features_1h` → DataFrame principal +3. Enrichissement avec les features thèse §5 : `{DB}.view_thesis_features_1h` + (jointure sur `src_ip`) +4. `preprocess_df(df)` — nettoyage, imputation, détection navigateur +5. Chargement de la carte de récurrence : `{DB}.view_ip_recurrence` +6. Chargement du feedback SOC : `{DB}.audit_logs` (si `ENABLE_FEEDBACK=true`) +7. Application du feedback : FP → `asn_label='isp'`, TP → `asn_label='soc_confirmed_bot'` +8. Exécution du modèle **Complet** (`correlated=1`, `FEATURES_COMPLET`) +9. Exécution du modèle **Applicatif** (`correlated=0`, `FEATURES`) +10. **Multi-fenêtres** (si `ENABLE_MULTIWINDOW=true`) : + - Requête `{DB}.{MULTIWINDOW_VIEW}` (défaut `view_ai_features_24h`) + - Exécution des modèles `Complet_24h` et `Applicatif_24h` + - Fusion OR : une IP est flaggée si anomalie dans ≥ 1 fenêtre (score le plus bas conservé) +11. **Insertion `ml_all_scores`** : Toutes les sessions scorées +12. **Déduplication intra-cycle** : `drop_duplicates(subset=['src_ip'], keep='first')` + en gardant le score le plus bas +13. **Déduplication inter-cycles** (`_filter_recent_detections`) : Interroge + `ml_detected_anomalies` pour les IPs insérées dans les dernières `DEDUP_TTL_MIN` + minutes. Réinsertion uniquement si le score s'est dégradé de ≥ 0.05 +14. **Insertion `ml_detected_anomalies`** : Anomalies et bots connus filtrés + +**Gestion des erreurs** : Compteur `_consecutive_failures`. Après `MAX_CONSECUTIVE_FAILURES` +(3) échecs, `set_healthy(False)` → health check renvoie 503. + +**Tables d'insertion** : + +| Table | Contenu | +|-------|---------| +| `{DB}.ml_all_scores` | Toutes les sessions scorées (anomalies + normales + bots) | +| `{DB}.ml_detected_anomalies` | Anomalies confirmées + bots connus uniquement | + +--- + +### 3.10 `__main__.py` — Point d'entrée + +**Rôle** : Point d'entrée du service. Pas d'argparse — le comportement est entièrement +contrôlé par les variables d'environnement. + +**Flux** : + +1. Import de toute la configuration via `from .config import *` +2. Affichage d'un bandeau de démarrage avec toutes les valeurs de configuration +3. Journalisation de l'événement `SERVICE_START` +4. Boucle infinie : + - `fetch_and_analyze()` + - `time.sleep(CYCLE_INTERVAL_SEC)` + - Les exceptions sont capturées, journalisées, et la boucle continue + +**Lancement** : + +```bash +python -m bot_detector ``` --- -## 7. Données d'entrée — vue ClickHouse +## 4. Features ML -### 7.1 Vue principale : `view_ai_features_1h` +### 4.1 Vue d'ensemble -Agrégation sur 1 heure glissante, une ligne par `src_ip`. Colonnes clés : +Le service utilise **7 familles de features** totalisant ~68 features uniques. Le +modèle Complet utilise les 68, le modèle Applicatif en utilise ~57 (sans les features +TCP/TLS). -| Colonne | Type | Source | -|---------|------|--------| -| `src_ip` | String | TCP/IP | -| `ja4` | String | TLS fingerprint (JA4+) | -| `host` | String | HTTP Host header | -| `bot_name` | String | Réputation IP/JA4 (vide si inconnu) | -| `asn_number` | String | GeoIP/ASN lookup | -| `asn_org` | String | Organisation ASN | -| `asn_domain` | String | Domaine ASN | -| `country_code` | String | Pays source | -| `asn_label` | String | `human` / `bot` / `unknown` | -| `correlated` | Int | 1 si TCP/TLS disponible, 0 sinon | -| `hits` | Float | Nb requêtes | -| `hit_velocity` | Float | Req/s | -| *…(26+ features)* | Float | Voir section 4.2 | +### 4.2 Famille 1 — Volume et débit -### 7.2 Vue de récurrence : `view_ip_recurrence` +| Feature | Description | Modèle | +|---------|-------------|--------| +| `hits` | Nombre total de requêtes sur la fenêtre | Les deux | +| `hit_velocity` | Requêtes par seconde | Les deux | +| `fuzzing_index` | Score de diversité anormale des chemins/paramètres | Les deux | +| `post_ratio` | Fraction de requêtes POST | Les deux | +| `port_exhaustion_ratio` | Fraction de ports sources distincts / total | Les deux | +| `orphan_ratio` | Requêtes sans réponse associée | Les deux | +| `max_keepalives` | Max requêtes sur une connexion keep-alive | Les deux | +| `tcp_shared_count` | Connexions TCP partagées entre sessions HTTP | Les deux | +| `head_ratio` | Fraction de requêtes HEAD (B4) | Les deux | +| `burst_ratio` | Ratio de requêtes en rafale (thèse §5) | Les deux | +| `pause_ratio` | Ratio de pauses entre rafales (thèse §5) | Les deux | + +### 4.3 Famille 2 — Chemins et contenu + +| Feature | Description | Modèle | +|---------|-------------|--------| +| `path_diversity_ratio` | Diversité des chemins URL accédés | Les deux | +| `url_depth_variance` | Variance de la profondeur des URL | Les deux | +| `anomalous_payload_ratio` | Fraction de payloads avec patterns anormaux | Les deux | +| `path_transition_entropy` | Entropie des transitions entre chemins (thèse §5) | Les deux | +| `login_post_concentration` | Concentration de POST sur les pages de login (P1) | Les deux | +| `unusual_content_type_ratio` | Ratio de Content-Types inhabituels (P1) | Les deux | +| `non_standard_port_ratio` | Ratio de ports non standard (P1) | Les deux | + +### 4.4 Famille 3 — Headers et protocole + +| Feature | Description | Modèle | +|---------|-------------|--------| +| `header_count` | Nombre d'en-têtes HTTP envoyés | Les deux | +| `header_order_shared_count` | Partage d'un même ordre d'en-têtes entre IPs | Les deux | +| `header_order_confidence` | Confiance dans l'ordre d'en-têtes | Les deux | +| `distinct_header_orders` | Nombre d'ordres d'en-têtes distincts | Les deux | +| `has_accept_language` | Présence de Accept-Language | Les deux | +| `modern_browser_score` | Score composite de conformité navigateur | Les deux | +| `ua_ch_mismatch` | Incohérence User-Agent / Client Hints | Les deux | +| `sec_fetch_absence_rate` | Absence des headers Sec-Fetch (B5) | Les deux | +| `generic_accept_ratio` | Ratio de headers Accept génériques (B6) | Les deux | +| `http10_ratio` | Ratio de requêtes HTTP/1.0 (B7) | Les deux | +| `missing_accept_enc_ratio` | Ratio de requêtes sans Accept-Encoding | Les deux | +| `http_scheme_ratio` | Ratio de schémas HTTP (vs HTTPS) | Les deux | +| `sec_ch_mobile_mismatch` | Incohérence Sec-CH-UA-Mobile (P1) | Les deux | +| `has_xff` | Présence de X-Forwarded-For (P1) | Les deux | + +### 4.5 Famille 4 — Session et navigation + +| Feature | Description | Modèle | +|---------|-------------|--------| +| `has_cookie` | Présence de cookies | Les deux | +| `has_referer` | Présence du Referer | Les deux | +| `asset_ratio` | Fraction de requêtes vers des ressources statiques | Les deux | +| `direct_access_ratio` | Fraction d'accès directs (sans Referer) | Les deux | +| `is_ua_rotating` | Rotation de User-Agent détectée | Les deux | +| `multiplexing_efficiency` | Efficacité du multiplexage HTTP/2 | Les deux | +| `request_size_variance` | Variance de la taille des requêtes | Les deux | +| `is_fake_navigation` | Fausse navigation détectée (P0) | Les deux | +| `host_diversity` | Diversité des hosts accédés (thèse §5) | Les deux | +| `host_sweep_speed` | Vitesse de balayage des hosts (thèse §5) | Les deux | +| `host_coverage_uniformity` | Uniformité de couverture des hosts (thèse §5) | Les deux | + +### 4.6 Famille 5 — TLS et réseau + +| Feature | Description | Modèle | +|---------|-------------|--------| +| `distinct_ja4_count` | Fingerprints JA4 distincts par IP | Les deux | +| `ja4_asn_concentration` | Concentration d'un même JA4 dans un ASN | Les deux | +| `ja4_country_concentration` | Concentration d'un même JA4 par pays | Les deux | +| `is_rare_ja4` | JA4 peu commun dans la population | Les deux | +| `ip_id_zero_ratio` | Ratio de paquets IP avec ID=0 | Les deux | +| `mss_mobile_mismatch` | Incohérence MSS TCP / profil mobile | Les deux | +| `tcp_jitter_variance` | Variance de la gigue inter-paquets TCP | Complet | +| `alpn_http_mismatch` | Incohérence ALPN négocié / protocole HTTP | Complet | +| `is_alpn_missing` | ALPN absent dans le ClientHello | Complet | +| `sni_host_mismatch` | Incohérence SNI TLS / Host HTTP | Complet | +| `ja3_diversity_ratio` | Ratio JA3/JA4 — rotation de fingerprint (B1) | Complet | +| `syn_timing_cv` | Coefficient de variation du timing SYN→ClientHello (B2) | Complet | +| `tls12_ratio` | Ratio de requêtes exclusivement TLS 1.2 (B3) | Complet | +| `ip_df_variance` | Variance du bit DF (Don't Fragment) (B8) | Complet | +| `avg_ttl` | TTL moyen (fingerprinting OS) | Complet | +| `ttl_std` | Écart-type du TTL | Complet | +| `no_window_scale_ratio` | Ratio de sessions sans window scaling TCP | Complet | +| `ja4_drift_ratio` | Dérive JA4 intra-session (thèse §5.5) | Complet | +| `true_window_size` | Taille réelle de la fenêtre TCP (P0) | Complet | +| `window_mss_ratio` | Ratio fenêtre TCP / MSS (P0) | Complet | + +### 4.7 Famille 6 — Intelligence et réputation + +| Feature | Description | Modèle | +|---------|-------------|--------| +| `anubis_is_flagged` | Signalé par Anubis (action ≠ ALLOW/DENY) | Les deux | +| `is_known_browser` | Browser identifié (axe 1 rétro-compat) | Les deux | +| `browser_consistency_score` | Score de cohérence navigateur (rétro-compat) | Les deux | +| `browser_confidence` | Confiance navigateur (5 axes) | Les deux | +| `src_port_density` | Densité des ports sources (entropie) | Les deux | + +### 4.8 Famille 7 — Thèse §5 et temporel + +| Feature | Description | Modèle | +|---------|-------------|--------| +| `temporal_entropy` | Entropie de Shannon de la distribution temporelle | Les deux | +| `cadence_cv` | Coefficient de variation de la cadence (thèse §5) | Les deux | +| `lag1_autocorrelation` | Autocorrélation lag-1 des inter-arrivées (thèse §5) | Les deux | +| `benford_deviation` | Déviation par rapport à la loi de Benford (thèse §5) | Les deux | + +### 4.9 Axes de détection navigateur (utilisés comme features) + +| Feature | Description | Modèle | +|---------|-------------|--------| +| `axis_ja4_known` | Score de l'axe 1 (JA4 connu) | Les deux | +| `axis_ja4_struct` | Score de l'axe 2 (structure JA4) | Les deux | +| `axis_http_modern` | Score de l'axe 3 (HTTP moderne) | Les deux | +| `axis_nav_behavior` | Score de l'axe 4 (comportement navigation) | Les deux | +| `axis_tls_coherence` | Score de l'axe 5 (cohérence TLS/TCP) | Les deux | + +--- + +## 5. Classification des menaces + +### 5.1 Arbre de décision complet + +La classification des menaces suit un ordre de priorité strict, appliqué dans +`pipeline.py` : + +``` +1. anubis_bot_action = 'DENY' + └──► ANUBIS_DENY (override systématique) + +2. bot_name != '' (dictionnaires de réputation IP/JA4) + └──► KNOWN_BOT + +3. anubis_bot_action = 'ALLOW' + └──► KNOWN_BOT (bot identifié mais autorisé) + +4. browser_confidence ≥ 0.55 AND famille identifiée AND threat ∈ {NORMAL, LOW} + └──► LEGITIMATE_BROWSER + +5. Score brut IsolationForest < -0.30 + └──► CRITICAL + +6. Score brut < -0.15 + └──► HIGH + +7. Score brut < -0.05 + └──► MEDIUM + +8. Score brut < 0 + └──► LOW + +9. Sinon + └──► NORMAL +``` + +### 5.2 Escalade par campagne + +Après le clustering HDBSCAN, les IPs appartenant à un cluster de **≥ 5 membres** +voient leur threat level escaladé de `HIGH` à `CRITICAL`. Cette logique détecte les +campagnes de botnet coordonnées qui individuellement ne seraient que `HIGH`. + +### 5.3 Niveaux de menace + +| Niveau | Score brut | Interprétation | +|--------|-----------|----------------| +| `ANUBIS_DENY` | — | Bloqué par la WAF Anubis | +| `KNOWN_BOT` | 0.0 | Bot identifié par réputation | +| `LEGITIMATE_BROWSER` | — | Navigateur humain confirmé (5 axes) | +| `CRITICAL` | < -0.30 | Comportement extrêmement anormal | +| `HIGH` | < -0.15 | Fort signal d'anomalie | +| `MEDIUM` | < -0.05 | Anomalie modérée | +| `LOW` | < 0 | Légèrement inhabituel | +| `NORMAL` | ≥ 0 | Trafic normal | + +--- + +## 6. Tests + +### 6.1 Organisation + +Les tests sont dans `bot_detector/tests/test_detector.py` (36 tests). Ils suivent un +patron **auto-contenu** : chaque test ré-implémente la logique clé plutôt que +d'importer directement depuis le module principal. Cela évite les chaînes d'imports +lourdes (`joblib`, `sklearn`, `torch`, `xgboost`). + +### 6.2 Patron des tests + +- **Autoencoder** : Helper local `_make_ae()` crée un modèle minimal en mémoire +- **XGBoost** : Modèles créés in-memory pour chaque test +- **EIF/IF** : Tests de scoring sur des données synthétiques +- **Dépendances optionnelles** : `pytest.skip()` si `torch` ou `xgboost` non installés + +### 6.3 Exécution + +```bash +# Via Docker (recommandé) +make test-bot-detector + +# Ou directement +docker build -f services/bot-detector/bot_detector/Dockerfile.tests -t bd-tests . +docker run --rm bd-tests + +# En local (nécessite les dépendances) +cd services/bot-detector +pip install -r bot_detector/requirements.txt pytest pytest-mock +pytest bot_detector/tests/test_detector.py -v + +# Un test spécifique +pytest bot_detector/tests/test_detector.py -v -k "test_benford" +``` + +### 6.4 Couverture des tests + +Les 36 tests couvrent : +- Calcul des scores de menace (`score_to_threat_level`) +- Validation des features (manquantes, constantes, zéro) +- Seuil adaptatif par percentile +- Normalisation des scores +- Entraînement et scoring de l'autoencoder +- Prédiction XGBoost +- Détection de dérive conceptuelle +- Clustering HDBSCAN/DBSCAN +- Détection navigateur (5 axes) +- Déviation de Benford, autocorrélation lag-1 +- Prétraitement des données + +--- + +## 7. Diagnostic + +### 7.1 Requêtes ClickHouse utiles ```sql -SELECT src_ip, recurrence FROM {DB}.view_ip_recurrence +-- Dernières anomalies CRITICAL +SELECT detected_at, src_ip, ja4, threat_level, anomaly_score, reason +FROM ja4_processing.ml_detected_anomalies +WHERE threat_level = 'CRITICAL' +ORDER BY detected_at DESC +LIMIT 20; + +-- Distribution des threat levels sur la dernière heure +SELECT threat_level, count() AS cnt +FROM ja4_processing.ml_detected_anomalies +WHERE detected_at > now() - INTERVAL 1 HOUR +GROUP BY threat_level +ORDER BY cnt DESC; + +-- Campagnes coordonnées (HDBSCAN clusters) +SELECT campaign_id, count() AS members, groupArray(src_ip) AS ips +FROM ja4_processing.ml_detected_anomalies +WHERE campaign_id >= 0 AND detected_at > now() - INTERVAL 1 HOUR +GROUP BY campaign_id +ORDER BY members DESC; + +-- Scores moyens par modèle (dernière heure) +SELECT model_name, avg(anomaly_score) AS avg_score, count() AS total +FROM ja4_processing.ml_all_scores +WHERE window_start > now() - INTERVAL 1 HOUR +GROUP BY model_name; + +-- Navigateurs légitimes détectés +SELECT src_ip, ja4, inferred_browser_family, browser_confidence +FROM ja4_processing.ml_all_scores +WHERE threat_level = 'LEGITIMATE_BROWSER' AND window_start > now() - INTERVAL 1 HOUR +ORDER BY browser_confidence DESC +LIMIT 20; + +-- Volume de la baseline humaine (santé du pipeline) +SELECT count() AS human_sessions +FROM ja4_processing.view_ai_features_1h +WHERE asn_label = 'isp' AND bot_name = ''; ``` -Donne le nombre de fois qu'une IP a déjà été détectée comme menace dans l'historique. Enrichit le champ `recurrence` dans la sortie. - ---- - -## 8. Données de sortie - -### 8.1 Table ClickHouse : `ml_detected_anomalies` - -Toutes les anomalies et bots connus détectés sont insérés dans cette table. Colonnes notables : - -| Colonne | Description | -|---------|-------------| -| `detected_at` | Timestamp de détection | -| `src_ip` | IP source | -| `ja4` | Fingerprint TLS/JA4 (`HTTP_CLEAR_TEXT` si absent) | -| `host` | Vhost ciblé | -| `bot_name` | Nom du bot (vide si anomalie IF) | -| `anomaly_score` | Score IF (0.0 pour bots connus) | -| `threat_level` | `CRITICAL` / `HIGH` / `MEDIUM` / `LOW` / `KNOWN_BOT` | -| `model_name` | `Complet` ou `Applicatif` | -| `recurrence` | Nb d'apparitions historiques + 1 | -| `reason` | Description textuelle de l'anomalie | -| `is_headless` | Dérivé de `is_fake_navigation` | -| *…(toutes les features)* | Pour analyse post-mortem | - -### 8.2 Journal JSONL : `decisions.jsonl` - -Événements structurés en JSON Lines, rotatifs (50 MB × 7 fichiers). - -| Événement | Déclencheur | -|-----------|-------------| -| `SERVICE_START` | Démarrage du conteneur | -| `SERVICE_STOP` | Arrêt propre (SIGTERM/SIGINT) | -| `CYCLE_START` | Début d'un cycle d'analyse | -| `CYCLE_END` | Fin du cycle (résumé inserés) | -| `MODEL_LOADED` | Réutilisation d'un modèle existant | -| `MODEL_TRAINED` | Nouvel entraînement | -| `KNOWN_BOT` | Bot connu identifié | -| `ANOMALY` | Anomalie IF détectée | -| `SKIPPED_LOW_DATA` | Cycle ignoré (baseline < 500) | -| `CONSECUTIVE_FAILURES` | Erreur ClickHouse répétée | - ---- - -## 9. Configuration - -Toutes les valeurs sont passées via variables d'environnement (fichier `.env`). - -| Variable | Défaut | Description | -|----------|--------|-------------| -| `CLICKHOUSE_HOST` | `clickhouse` | Hôte ClickHouse | -| `CLICKHOUSE_DB` | `ja4_processing` | Base de données | -| `CLICKHOUSE_USER` | `default` | Utilisateur | -| `CLICKHOUSE_PASSWORD` | *(vide)* | Mot de passe | -| `ISOLATION_CONTAMINATION` | `0.001` | Fraction d'anomalies attendues (0 < x < 0.5) | -| `ANOMALY_THRESHOLD` | `-0.05` | Seuil statique de score pour insertion | -| `CYCLE_INTERVAL_SEC` | `300` | Délai entre cycles (secondes) | -| `MAX_CONSECUTIVE_FAILURES` | `3` | Échecs avant passage en DEGRADED | -| `BOT_DETECTOR_LOG` | `/var/log/bot_detector/decisions.jsonl` | Fichier de log | -| `LOG_BACKUP_COUNT` | `7` | Nb de rotations conservées | -| `MODEL_DIR` | `/var/lib/bot_detector` | Répertoire des modèles | -| `RETRAIN_INTERVAL_HOURS` | `24` | Fréquence de re-entraînement | -| `MODEL_HISTORY_COUNT` | `10` | Nb de versions de modèles conservées | -| `HEALTH_PORT` | `8080` | Port du health check HTTP | -| **A1** `DRIFT_THRESHOLD` | `0.30` | Fraction de features déroutantes déclenchant un retrain forcé | -| **A2** `ANOMALY_PERCENTILE` | `5` | Percentile pour le seuil adaptatif (0–20) | -| **A3** `ENABLE_MULTIWINDOW` | `false` | Active l'analyse sur fenêtre 24h | -| **A3** `MULTIWINDOW_VIEW` | `view_ai_features_24h` | Nom de la vue 24h dans ClickHouse | -| **A4** `ENABLE_SHAP` | `true` | Active le calcul SHAP (désactivé si shap non installé) | -| **A5** `DEDUP_TTL_MIN` | `60` | TTL de déduplication inter-cycles (0 = désactivé) | -| **A6** `RECURRENCE_WEIGHT` | `0.005` | Pénalité de score par log(récurrence) | -| **A7** `MIN_VALID_FEATURE_RATIO` | `0.50` | Ratio minimum de features valides pour procéder | -| **A8** `ENABLE_CLUSTERING` | `true` | Active le clustering DBSCAN des anomalies | -| **A8** `CLUSTERING_MIN_SAMPLES` | `3` | Taille minimale d'un cluster DBSCAN | - ---- - -## 10. Observabilité - -### 10.1 Health check +### 7.2 Commandes jq pour les logs JSONL ```bash -GET http://localhost:8080/ -# → 200 OK service opérationnel -# → 503 DEGRADED ≥ MAX_CONSECUTIVE_FAILURES échecs ClickHouse consécutifs -``` - -### 10.2 Logs opérationnels - -Les logs console suivent le format `[YYYY-MM-DD HH:MM:SS] message`. Le fichier JSONL permet des analyses post-mortem avec des outils comme `jq` : - -```bash -# Voir les dernières anomalies CRITICAL +# Anomalies CRITICAL récentes jq 'select(.event=="ANOMALY" and .threat_level=="CRITICAL")' decisions.jsonl -# Voir les top features SHAP pour les anomalies HIGH +# Top features SHAP des anomalies HIGH jq 'select(.event=="ANOMALY" and .threat_level=="HIGH") | .reason' decisions.jsonl -# Détecter les dérives de distribution +# Dérives de distribution détectées jq 'select(.event=="DRIFT_DETECTED")' decisions.jsonl -# Voir les campagnes coordonnées (campaign_id >= 0) +# Campagnes coordonnées (campaign_id ≥ 0) jq 'select(.event=="ANOMALY" and .campaign_id >= 0) | {src_ip, campaign_id, threat_level}' decisions.jsonl -# Compter les bots connus par nom +# Comptage des bots connus par nom jq -r 'select(.event=="KNOWN_BOT") | .bot_name' decisions.jsonl | sort | uniq -c | sort -rn -# Résumé des cycles +# Résumé des cycles (performances) jq 'select(.event=="CYCLE_END")' decisions.jsonl + +# Navigateurs légitimes confirmés +jq 'select(.event=="LEGITIMATE_BROWSER")' decisions.jsonl ``` -| Événement | Déclencheur | -|-----------|-------------| -| `SERVICE_START` | Démarrage du conteneur | -| `SERVICE_STOP` | Arrêt propre (SIGTERM/SIGINT) | -| `CYCLE_START` | Début d'un cycle d'analyse | -| `CYCLE_END` | Fin du cycle (résumé insertés + dedup_ttl_min) | -| `MODEL_LOADED` | Réutilisation d'un modèle existant (+ drift_score) | -| `MODEL_TRAINED` | Nouvel entraînement | -| `DRIFT_DETECTED` | Dérive conceptuelle détectée → retrain forcé | -| `FEATURE_WARNING` | Features manquantes / constantes / agrégats globaux détectés (loggué uniquement si la situation change) | -| `SKIPPED_INVALID_FEATURES` | Cycle ignoré (trop peu de features valides) | -| `KNOWN_BOT` | Bot connu identifié | -| `ANOMALY` | Anomalie IF détectée (+ effective_threshold, campaign_id, raw_anomaly_score) | -| `SKIPPED_LOW_DATA` | Cycle ignoré (baseline < 500) | -| `CONSECUTIVE_FAILURES` | Erreur ClickHouse répétée | +### 7.3 Problèmes courants -### 10.3 Avertissements sur les features (A7) +| Symptôme | Cause probable | Solution | +|----------|---------------|----------| +| `SKIPPED_LOW_DATA` à chaque cycle | Baseline humaine < 500 sessions | Vérifier que `view_ai_features_1h` retourne des lignes avec `asn_label='isp'` | +| `SKIPPED_INVALID_FEATURES` | > 50% des features constantes/absentes | Vérifier le schéma de `view_ai_features_1h` — colonnes manquantes ou agrégations NULL | +| `CONSECUTIVE_FAILURES` | ClickHouse inaccessible | Vérifier `CLICKHOUSE_HOST`/`PORT`, réseau Docker, état du service ClickHouse | +| Health check 503 | ≥ 3 échecs consécutifs | Consulter les logs pour l'erreur sous-jacente | +| Pas d'anomalies détectées | Seuil trop strict ou features constantes | Vérifier `ANOMALY_THRESHOLD`, inspecter la distribution des scores dans `ml_all_scores` | +| SHAP non disponible | Paquet `shap` non installé | Installer `shap` ou vérifier `ENABLE_SHAP=true` | +| Autoencoder désactivé | `torch` non installé | Installer `torch` — le service tourne sans AE mais avec moins de précision | +| Trop de `LEGITIMATE_BROWSER` | Seuil navigateur trop bas | Augmenter `BROWSER_CONFIDENCE_THRESHOLD` (défaut 0.55) | +| `FEATURE_WARNING` fréquents | Colonnes non alimentées dans la vue | Voir `CLICKHOUSE_FEATURES_DIAGNOSTIC.md` | -Les avertissements de features ne sont affichés en console **qu'une seule fois** (à la première détection ou lors d'un changement). Les cycles suivants avec la même situation ne génèrent pas de bruit. L'événement `FEATURE_WARNING` reste dans le JSONL pour traçabilité. +### 7.4 Vérification de l'état du service -| Catégorie | Message console | Cause typique | -|-----------|-----------------|---------------| -| `zero` | `Features à 0 (pipeline non-alimenté)` | Table source vide / LEFT JOIN sans match | -| `unique_nonzero` | `Features non-discriminantes (agrégat global)` | `PARTITION BY` sur valeur NULL → partition unique | -| `missing` | `Features absentes du schéma` | Colonne manquante dans la vue ClickHouse | +```bash +# Health check +curl -s http://localhost:8080/ +# → OK ou DEGRADED -Voir [`CLICKHOUSE_FEATURES_DIAGNOSTIC.md`](CLICKHOUSE_FEATURES_DIAGNOSTIC.md) pour le détail des corrections ClickHouse nécessaires. - -### 11.1 Sources de réputation - -| Fichier | Format | Contenu | -|---------|--------|---------| -| `bot_ip.csv` | `ip_cidr,bot_name` | ~288 k IP/CIDR de bots référencés | -| `bot_ja4.csv` | `ja4,bot_name` | Fingerprints JA4 de bots | -| `asn_reputation.csv` | `asn_number,label` | Labels ASN (human/bot) | - -Ces fichiers sont montés en lecture seule dans le conteneur. Ils sont écrits par ClickHouse (FILE engine) et partagés via volume Docker. - -### 11.2 Hiérarchie de classification +# Vérifier les logs Docker +docker logs bot-detector --tail 50 +# Vérifier la configuration active (première ligne du démarrage) +docker logs bot-detector 2>&1 | head -30 ``` -1. bot_name != '' (depuis view_ai_features_1h) - → KNOWN_BOT : bot identifié par réputation IP ou JA4 - -2. asn_label == 'human' (depuis view_ai_features_1h) - → Utilisé pour la baseline d'entraînement de l'IF - -3. Trafic restant - → Scoré par Isolation Forest - → Anomalie si score < ANOMALY_THRESHOLD -``` - ---- - -## 12. Fondements scientifiques - -### 12.1 Isolation Forest (Liu et al., 2008) - -L'algorithme repose sur la propriété que les anomalies sont **isolées plus rapidement** dans des arbres de partitionnement aléatoire. La longueur moyenne du chemin d'isolation est normalisée pour produire un score entre 0 et 1 (transposé ici en -1 à +1 par `decision_function`). - -**Propriétés clés :** -- Complexité O(n log n) pour l'entraînement -- Robuste aux données de haute dimensionnalité (31–35 features ici) -- Pas d'hypothèse sur la distribution des données -- Efficace sur de grands volumes (n_estimators=300, n_jobs=-1) - -### 12.2 JA4+ Fingerprinting (FoxIO, 2023) - -JA4 est la 4e génération de fingerprints TLS/QUIC/HTTP, successeur de JA3. Il capture les caractéristiques du ClientHello TLS (versions, ciphers, extensions) en une empreinte compacte permettant d'identifier des familles de clients (navigateurs, bots, outils). L'utilisation de `is_rare_ja4`, `distinct_ja4_count` et `ja4_asn_concentration` exploite cette propriété. - -### 12.3 One-Class Classification appliquée aux bots - -L'approche s'inscrit dans la lignée des travaux sur la détection de bots web : -- **Stevanovic et al. (2013)** : détection de bots par analyse comportementale de flux HTTP -- **Kruegel & Vigna (2003)** : détection d'anomalies réseau par profils normaux -- **Barford & Yegneswaran (2007)** : classification comportementale des botnets - -La combinaison de features HTTP comportementales (velocity, fuzzing, post_ratio), de features d'empreinte (JA4, headers), et de features TCP/TLS (jitter, ALPN, SNI) reproduit l'approche multi-couche recommandée par la littérature récente. - -### 12.4 Entropie temporelle comme signal d'anomalie - -Le feature `temporal_entropy` mesure l'entropie de Shannon sur la distribution temporelle des requêtes dans la fenêtre. Un bot avec un timing régulier (scripted polling) produit une entropie faible, tandis qu'un humain naviguant naturellement produit une distribution plus aléatoire. Ce signal est utilisé dans les travaux de **Wang et al. (2014)** sur la détection de crawlers web. - ---- - -## 13. Améliorations implémentées (v11) - -### A1 — Détection de dérive conceptuelle - -**Fonctionnement** : À chaque cycle, avant de décider de charger ou de réentraîner le modèle, on compare la distribution courante de la baseline humaine avec celle sauvegardée lors du dernier entraînement. Pour chaque feature, un z-score est calculé : - -``` -z = |mean_current - mean_trained| / std_trained -``` - -Si la fraction de features avec `z > 2.0` dépasse `DRIFT_THRESHOLD` (30% par défaut), un re-entraînement est forcé et l'événement `DRIFT_DETECTED` est loggué. - -**Métadonnées sauvegardées** : `baseline_stats` dans le `.meta.json` contient `{mean, std, p25, p75}` par feature. - -**Références** : Gama et al. (2014) — *A Survey on Concept Drift Adaptation* - ---- - -### A2 — Seuil adaptatif par percentile - -**Fonctionnement** : - -```python -effective_threshold = min(np.percentile(raw_scores[raw_scores < 0], ANOMALY_PERCENTILE), - ANOMALY_THRESHOLD) -``` - -Le seuil effectif est le minimum entre le `ANOMALY_PERCENTILE`-ème percentile des scores négatifs et le seuil statique. Cela garantit que le seuil ne peut pas remonter au-dessus du seuil configuré, mais peut s'adapter vers le bas selon la distribution courante. - -Le seuil utilisé est loggué dans chaque événement `ANOMALY`. - ---- - -### A3 — Analyse multi-fenêtres (optionnelle) - -**Activation** : `ENABLE_MULTIWINDOW=true` + une vue `view_ai_features_24h` dans ClickHouse. - -**Fonctionnement** : Deux paires de modèles supplémentaires (`Complet_24h`, `Applicatif_24h`) tournent sur la fenêtre de 24h. Les anomalies des deux fenêtres sont fusionnées via une logique OR : une IP est flaggée si elle est anormale dans au moins une fenêtre. En cas de doublon, le score le plus bas (le plus anormal) est conservé. - -**Utilité** : Détection des bots low-and-slow invisibles sur 1h mais clairement anormaux sur 24h. - ---- - -### A4 — Explainabilité par SHAP - -**Fonctionnement** : Pour chaque anomalie détectée, `shap.TreeExplainer` calcule la contribution de chaque feature au score d'anomalie. Les 5 features les plus négatives (les plus responsables de l'anomalie) sont incluses dans le champ `reason` : - -``` -[Complet] Score: -0.112 | SHAP: is_alpn_missing(-1.081) | tcp_jitter_variance(-1.073) | - ja4_asn_concentration(-1.062) | temporal_entropy(-0.887) | - direct_access_ratio(-0.886) | Threat: MEDIUM -``` - -**Désactivation** : `ENABLE_SHAP=false` ou si le package `shap` n'est pas installé. - -**Références** : Lundberg & Lee (2017) — *A Unified Approach to Interpreting Model Predictions* - ---- - -### A5 — Déduplication inter-cycles avec TTL - -**Fonctionnement** : Avant chaque insertion, la table `ml_detected_anomalies` est interrogée pour identifier les IPs déjà insérées dans les `DEDUP_TTL_MIN` dernières minutes. Une IP est réinsérée uniquement si son score brut s'est dégradé d'au moins 0.05 points. - -**Désactivation** : `DEDUP_TTL_MIN=0` - ---- - -### A6 — Pondération du score par récurrence - -**Fonctionnement** : - -```python -raw_score_adjusted = raw_score - log1p(recurrence) × RECURRENCE_WEIGHT -``` - -Une IP détectée 10 fois reçoit une pénalité de `log(11) × 0.005 ≈ 0.012` sur son score brut, ce qui la rapproche du seuil de détection. Ce mécanisme simule un prior bayésien : les IPs récidivistes sont plus probablement malveillantes. - ---- - -### A7 — Validation de complétude des features - -**Fonctionnement** : Avant entraînement et scoring, `validate_features()` détecte : -- Les features absentes de la vue ClickHouse -- Les features constantes (std = 0, donc non discriminantes) - -Les features invalides sont exclues du modèle. Si la fraction de features valides est inférieure à `MIN_VALID_FEATURE_RATIO` (50%), le cycle est ignoré. - -**Bénéfice** : Les features constantes (souvent dues à des colonnes non encore implémentées dans la vue) ne biaisent plus le modèle. - ---- - -### A8 — Clustering comportemental (DBSCAN) - -**Fonctionnement** : Après détection, DBSCAN est appliqué sur les features normalisées des anomalies : - -```python -X_scaled = StandardScaler().fit_transform(anomalies[valid_features]) -labels = DBSCAN(eps=0.5, min_samples=CLUSTERING_MIN_SAMPLES).fit_predict(X_scaled) -``` - -- `campaign_id = -1` : IP isolée (comportement unique) -- `campaign_id >= 0` : membre d'une campagne coordonnée - -Le `campaign_id` est loggué dans les événements `ANOMALY` (JSONL). Il n'est pas encore dans le schéma ClickHouse (voir §14). - -**Références** : Ester et al. (1996) — *A Density-Based Algorithm for Discovering Clusters* - ---- - -### A10 — Normalisation des scores entre modèles - -**Fonctionnement** : - -```python -# Scores négatifs normalisés en [-1, 0], scores positifs inchangés -anomaly_score_normalized = normalize_scores(raw_score) -``` - -Le champ `anomaly_score` dans ClickHouse contient désormais le score normalisé, permettant une comparaison cohérente entre le modèle Complet (35 features) et le modèle Applicatif (31 features). Le score brut IF est conservé dans `raw_anomaly_score` (logs JSONL uniquement) et est utilisé pour l'assignation du threat level. - ---- - -## 14. Migration de schéma ClickHouse - -Les nouvelles colonnes suivantes sont disponibles dans les logs JSONL mais pas encore dans la table `ml_detected_anomalies`. Pour les activer : - -```sql -ALTER TABLE ja4_processing.ml_detected_anomalies - ADD COLUMN IF NOT EXISTS campaign_id Int32 DEFAULT -1, - ADD COLUMN IF NOT EXISTS raw_anomaly_score Float32 DEFAULT 0; -``` - -Après cette migration, ajouter ces colonnes à la liste `cols` dans `fetch_and_analyze()` (elles sont déjà calculées en mémoire). diff --git a/services/bot-detector/IMPROVEMENTS.md b/services/bot-detector/IMPROVEMENTS.md index a0c1fbf..2f86614 100644 --- a/services/bot-detector/IMPROVEMENTS.md +++ b/services/bot-detector/IMPROVEMENTS.md @@ -1,177 +1,119 @@ # Bot Detector IA — Axes d'amélioration -> Document de propositions techniques — à valider avant implémentation +> Suivi d'implémentation — mis à jour le 2025-07-15 | Architecture modulaire (11 modules) --- -## Résumé des axes proposés +## Résumé des axes proposés — État d'implémentation -| # | Axe | Impact | Complexité | Priorité suggérée | -|---|-----|--------|------------|-------------------| -| A1 | [Détection de dérive conceptuelle (concept drift)](#a1-détection-de-dérive-conceptuelle) | 🔴 Élevé | Moyenne | ⭐⭐⭐ | -| A2 | [Seuil adaptatif par percentile](#a2-seuil-adaptatif-par-percentile) | 🔴 Élevé | Faible | ⭐⭐⭐ | -| A3 | [Analyse multi-fenêtres temporelles](#a3-analyse-multi-fenêtres-temporelles) | 🔴 Élevé | Élevée | ⭐⭐ | -| A4 | [Explainabilité par SHAP](#a4-explainabilité-par-shap) | 🟠 Moyen | Moyenne | ⭐⭐⭐ | -| A5 | [Déduplication avec TTL inter-cycles](#a5-déduplication-avec-ttl-inter-cycles) | 🟠 Moyen | Faible | ⭐⭐⭐ | -| A6 | [Pondération par récurrence dans le score](#a6-pondération-par-récurrence-dans-le-score) | 🟠 Moyen | Faible | ⭐⭐ | -| A7 | [Validation de complétude des features](#a7-validation-de-complétude-des-features) | 🟠 Moyen | Faible | ⭐⭐⭐ | -| A8 | [Clustering comportemental des anomalies](#a8-clustering-comportemental-des-anomalies) | 🟡 Utile | Moyenne | ⭐⭐ | -| A9 | [Métriques Prometheus / health check enrichi](#a9-métriques-prometheus--health-check-enrichi) | 🟡 Utile | Faible | ⭐⭐ | -| A10 | [Normalisation des scores entre modèles](#a10-normalisation-des-scores-entre-modèles) | 🟡 Utile | Faible | ⭐ | +| # | Axe | Statut | Implémentation | +|---|-----|--------|----------------| +| A1 | [Détection de dérive conceptuelle](#a1--détection-de-dérive-conceptuelle) | ✅ IMPLÉMENTÉ | `scoring.py` : `_compute_drift_score()` + fallback z-score | +| A2 | [Seuil adaptatif par percentile](#a2--seuil-adaptatif-par-percentile) | ✅ IMPLÉMENTÉ | `scoring.py` : `compute_adaptive_threshold()` | +| A3 | [Analyse multi-fenêtres temporelles](#a3--analyse-multi-fenêtres-temporelles) | ✅ IMPLÉMENTÉ | `cycle.py` : `ENABLE_MULTIWINDOW` + `Complet_24h`/`Applicatif_24h` | +| A4 | [Explainabilité par SHAP](#a4--explainabilité-par-shap) | ✅ IMPLÉMENTÉ | `scoring.py` : `_compute_shap_top_features()`, top-5 | +| A5 | [Déduplication avec TTL inter-cycles](#a5--déduplication-avec-ttl-inter-cycles) | ✅ IMPLÉMENTÉ | `cycle.py` : `_filter_recent_detections()`, `DEDUP_TTL_MIN=60` | +| A6 | [Pondération par récurrence dans le score](#a6--pondération-par-récurrence-dans-le-score) | ✅ IMPLÉMENTÉ | `pipeline.py` : `raw -= log1p(count) * RECURRENCE_WEIGHT` | +| A7 | [Validation de complétude des features](#a7--validation-de-complétude-des-features) | ✅ IMPLÉMENTÉ | `scoring.py` : `validate_features()`, `MIN_VALID_FEATURE_RATIO=0.50` | +| A8 | [Clustering comportemental des anomalies](#a8--clustering-comportemental-des-anomalies) | ✅ IMPLÉMENTÉ | `scoring.py` : HDBSCAN + escalade campagne dans `pipeline.py` | +| A9 | [Métriques Prometheus / health check enrichi](#a9--métriques-prometheus--health-check-enrichi) | ❌ À FAIRE | Health check binaire uniquement (`infra.py` : 200/503) | +| A10 | [Normalisation des scores entre modèles](#a10--normalisation-des-scores-entre-modèles) | ✅ IMPLÉMENTÉ | `scoring.py` : `normalize_scores()` min-max [0, 1] | --- ## A1 — Détection de dérive conceptuelle +### ✅ IMPLÉMENTÉ + +**Module** : `scoring.py` — fonctions `_compute_drift_score()` et `_compute_drift_score_zscore()` + +**Différences avec la proposition initiale** : + +| Proposition | Implémentation | +|-------------|----------------| +| KS-test (scipy `ks_2samp`) | Méthode principale : comparaison par quantiles interpolés (KS-like sans scipy). Fallback : z-score `\|μ_current - μ_trained\| / σ_trained > 2.0` | +| Seuil par `p_value < 0.05` | Même seuil p < 0.05 pour la méthode quantile ; z > 2.0 pour le fallback | +| `DRIFT_THRESHOLD` (30%) | ✅ Identique : `DRIFT_THRESHOLD = 0.30` (fraction de features en dérive) | +| Sauvegarde dans `.meta.json` | ✅ `baseline_stats` avec `{mean, std, p25, p75}` par feature | +| Événement `DRIFT_DETECTED` | ✅ Journalisé avec la liste des features déroutantes | + +**Appelé depuis** : `models.py` → `load_or_train_model()`, avant la décision de chargement vs retrain. + ### Problème L'Isolation Forest est entraîné sur la baseline humaine courante. Si le profil du trafic légitime évolue graduellement (nouveau navigateur populaire, changement de comportement utilisateur, migration réseau), le modèle vieilli peut : - Générer des **faux positifs** sur du trafic humain nouvellement apparu - Rater des **faux négatifs** si les bots imitent les anciens patterns -Le re-entraînement périodique (toutes les X heures) atténue le problème mais ne détecte pas quand une dérive significative a eu lieu **entre deux cycles de retraining**. - -### Approche proposée - -Calculer à chaque cycle un score de **dérive statistique** entre la baseline d'entraînement du modèle actif et la baseline courante. Si la dérive dépasse un seuil, forcer un re-entraînement anticipé. - -**Méthode : Kolmogorov-Smirnov (KS test) ou Maximum Mean Discrepancy (MMD)** - -Pour chaque feature : -```python -from scipy import stats -ks_stat, p_value = stats.ks_2samp(baseline_trained[feat], baseline_current[feat]) -``` - -Si la fraction de features avec `p_value < 0.05` dépasse un seuil configurable (ex. 30%), déclencher un retrain et logguer un événement `DRIFT_DETECTED`. - -### Bénéfices -- Retrain opportuniste plutôt que temporel fixe -- Détection proactive des changements de comportement réseau -- Réduction des faux positifs liés à la dérive - ### Références - Gama et al. (2014) — *A Survey on Concept Drift Adaptation* - Rabanser et al. (2019) — *Failing Loudly: An Empirical Study of Methods for Detecting Dataset Shift* -### Implémentation suggérée -- Sauvegarder la distribution de la baseline d'entraînement dans le `.meta.json` -- Calculer le KS test au début de chaque cycle avant la décision de chargement -- Ajouter un paramètre `DRIFT_THRESHOLD` (défaut : 0.30) -- Logguer l'événement `DRIFT_DETECTED` avec les features déroutantes - --- ## A2 — Seuil adaptatif par percentile -### Problème +### ✅ IMPLÉMENTÉ -`ANOMALY_THRESHOLD = -0.03` est un seuil **global et statique**. Ce seuil a une signification différente selon : -- Le volume de trafic (plus de trafic = distribution de scores plus resserrée) -- La contamination effective du cycle (jour calme vs attaque active) -- Les caractéristiques du modèle actif (entraîné sur 1 264 vs 1 725 sessions) +**Module** : `scoring.py` — fonction `compute_adaptive_threshold()` -Un seuil fixe peut produire des **rafales de faux positifs** lors d'événements légitimes inhabituels (campagne marketing, crawler partenaire) ou rater des menaces réelles lors de trafic atypique. - -### Approche proposée - -Calculer dynamiquement le seuil à partir de la **distribution des scores du cycle courant** : +**Implémentation conforme à la proposition** : ```python -scores = model.decision_function(X_test) -# Seuil = percentile P de la distribution des scores négatifs -adaptive_threshold = np.percentile(scores, ANOMALY_PERCENTILE) -# On prend le min avec le seuil statique pour éviter d'aller trop haut -threshold = min(adaptive_threshold, ANOMALY_THRESHOLD) +neg_scores = scores[scores < 0] +adaptive = np.percentile(neg_scores, ANOMALY_PERCENTILE) # défaut : 5 +effective_threshold = min(adaptive, ANOMALY_THRESHOLD) # garde-fou : -0.05 ``` -**Paramètre ajoutable** : `ANOMALY_PERCENTILE` (défaut : 5 → top 5% des scores les plus négatifs). +| Proposition | Implémentation | +|-------------|----------------| +| `ANOMALY_PERCENTILE` (0–20, défaut 5) | ✅ `config.py` : `ANOMALY_PERCENTILE = 5` | +| Seuil statique en garde-fou | ✅ `ANOMALY_THRESHOLD = -0.05` | +| Log du seuil effectif dans `ANOMALY` | ✅ Journalisé dans les événements | -Cette approche est complémentaire au seuil statique (garde-fou) : elle s'adapte vers le bas mais ne remonte jamais au-dessus du seuil configuré. - -### Bénéfices -- Stabilité du taux de faux positifs au fil du temps -- Auto-adaptation aux variations de volume -- Comportement plus prédictible en production - -### Implémentation suggérée -- Ajouter `ANOMALY_PERCENTILE` (0–20, défaut 5) comme variable d'environnement -- Calculer le seuil adaptatif dans `run_semi_supervised_logic()` -- Logguer le seuil effectif utilisé dans `CYCLE_START` / `ANOMALY` +**Appelé depuis** : `pipeline.py` ligne 144, sur les scores bruts EIF uniquement. --- ## A3 — Analyse multi-fenêtres temporelles -### Problème +### ✅ IMPLÉMENTÉ -La fenêtre 1h est un compromis. Elle manque : -- Les **attaques rapides** (burst de quelques minutes) : le signal est dilué -- Les **bots lents** (low-and-slow, 1–2 req/min sur 24h) : comportement normal sur 1h +**Module** : `cycle.py` — dans `fetch_and_analyze()`, lignes 257–275 -### Approche proposée +**Implémentation** : -Ajouter une deuxième vue ClickHouse agrégée sur **24h** et un troisième modèle sur cette fenêtre. Les scores des deux modèles peuvent être combinés : +| Proposition | Implémentation | +|-------------|----------------| +| Vue 24h dans ClickHouse | ✅ `MULTIWINDOW_VIEW = 'view_ai_features_24h'` (configurable) | +| Deux modèles supplémentaires | ✅ `Complet_24h` et `Applicatif_24h` | +| Combinaison AND logique | Implémenté en **OR logique** : une IP est flaggée si anomalie dans ≥ 1 fenêtre | +| Score le plus bas conservé | ✅ En cas de doublon, le score le plus bas (le plus anormal) est conservé | +| Activation par feature flag | ✅ `ENABLE_MULTIWINDOW = false` par défaut | -``` -score_final = w1 * score_1h + w2 * score_24h -``` - -Ou, plus simplement, un AND logique : une IP n'est flaggée que si elle est anomalie sur les **deux fenêtres**, réduisant drastiquement les faux positifs. - -### Bénéfices -- Détection des bots low-and-slow (reconnaissance, scraping discret) -- Réduction des faux positifs par corrélation multi-temporelle -- Complémentarité avec le modèle 1h existant - -### Considerations -- Nécessite une vue `view_ai_features_24h` dans ClickHouse -- Modèle 24h beaucoup plus stable (moins de bruit) -- Le volume de données à traiter augmente - -### Références -- Stalmans & Irwin (2011) — *A Framework for Web Bot Detection Using Request Rate Monitoring* -- Stevanovic et al. (2013) — *An Efficient Flow-based Botnet Detection Using Supervised Machine Learning* +**Note** : La proposition originale suggérait un AND logique (anomalie dans les deux fenêtres). L'implémentation utilise un OR pour maximiser la couverture, avec déduplication par score minimal. --- ## A4 — Explainabilité par SHAP -### Problème +### ✅ IMPLÉMENTÉ -Le champ `reason` actuel est basique : -``` -"[Complet] Score: -0.312 | Vel: 45.2 req/s | Fuzzing: 8.3 | Threat: CRITICAL" -``` +**Module** : `scoring.py` — fonction `_compute_shap_top_features()` -Pour un opérateur de sécurité, il manque : -- **Quelles features** ont le plus contribué à ce score ? -- Est-ce principalement comportemental (velocity) ou fingerprint (JA4) ? -- Comment comparer deux anomalies de même score ? +| Proposition | Implémentation | +|-------------|----------------| +| `TreeExplainer` (sklearn IF) | ✅ Pour sklearn : `shap.TreeExplainer` | +| Top-5 features les plus contributives | ✅ Top-5 features avec valeurs SHAP | +| `ENABLE_SHAP=true/false` | ✅ `config.py` : `ENABLE_SHAP = true` (ET `SHAP_AVAILABLE`) | +| SHAP uniquement sur les anomalies | ✅ Calculé uniquement pour les IPs flaggées | +| Enrichissement du champ `reason` | ✅ Via `_build_reason()` — SHAP intégré dans la raison textuelle | -### Approche proposée +**Ajout par rapport à la proposition** : Pour isotree (Extended IF), SHAP utilise +`PermutationExplainer` au lieu de `TreeExplainer` (isotree n'est pas nativement +supporté par TreeSHAP). -Utiliser **TreeSHAP** (Lundberg & Lee, 2017) qui supporte nativement les forêts d'arbres : - -```python -import shap -explainer = shap.TreeExplainer(model) -shap_values = explainer.shap_values(X_test.iloc[[idx]]) -top_features = sorted(zip(features, shap_values[0]), key=lambda x: abs(x[1]), reverse=True)[:5] -``` - -Enrichir le champ `reason` avec les 5 features les plus contributives et leur valeur SHAP. - -### Bénéfices -- Triage des alertes facilité pour les analystes SOC -- Détection des features systématiquement sur-représentées (potentiel bug de feature engineering) -- Conformité avec les exigences de traçabilité des décisions IA - -### Implémentation suggérée -- Ajouter `shap` aux requirements (compatible sklearn) -- Calculer SHAP uniquement pour les IP flaggées (pas sur tout le dataset) -- Stocker `shap_top5` comme JSON dans le log JSONL -- Option : `ENABLE_SHAP=true/false` pour contrôler la charge CPU +**Appelé depuis** : `pipeline.py` lignes 298–303, après l'extraction des anomalies. ### Références - Lundberg & Lee (2017) — *A Unified Approach to Interpreting Model Predictions* @@ -180,261 +122,186 @@ Enrichir le champ `reason` avec les 5 features les plus contributives et leur va ## A5 — Déduplication avec TTL inter-cycles -### Problème +### ✅ IMPLÉMENTÉ -Avec un cycle de 5 min et une fenêtre 1h, la même IP malveillante est potentiellement **réinsérée 12 fois par heure** dans `ml_detected_anomalies`. Cela : -- Gonfle la table artificellement -- Complique les requêtes d'analyse (nécessite un DISTINCT) -- Fausse les métriques de comptage +**Module** : `cycle.py` — fonction `_filter_recent_detections()` -Le mécanisme actuel de `drop_duplicates(subset=['src_ip'])` ne fonctionne qu'au sein d'un seul cycle, pas entre cycles. - -### Approche proposée - -Avant insertion, interroger ClickHouse pour filtrer les IPs déjà insérées récemment : - -```python -# Récupérer les IPs déjà détectées dans les N dernières minutes -recent_ips = client.query_df(f""" - SELECT DISTINCT src_ip - FROM {DB}.ml_detected_anomalies - WHERE detected_at > now() - INTERVAL {DEDUP_TTL_MIN} MINUTE -""") -# Exclure ces IPs sauf si le score s'est dégradé significativement -new_anomalies = anomalies[~anomalies['src_ip'].isin(recent_ips['src_ip'])] -``` - -**Paramètre ajoutable** : `DEDUP_TTL_MIN` (défaut : 60 minutes). - -**Variante** : ne re-insérer que si `new_score < existing_score - 0.05` (dégradation significative). - -### Bénéfices -- Réduction du volume de la table de détection -- Requêtes d'analyse plus simples -- Gestion de la montée en charge (moins d'insertions) - -### Implémentation suggérée -- Paramètre `DEDUP_TTL_MIN` (0 pour désactiver) -- La requête de déduplication est légère (index sur `detected_at`) -- Logguer le nb d'IP filtrées dans `CYCLE_END` +| Proposition | Implémentation | +|-------------|----------------| +| `DEDUP_TTL_MIN` (défaut 60 min) | ✅ `config.py` : `DEDUP_TTL_MIN = 60` | +| Requête ClickHouse pour les IPs récentes | ✅ Interroge `ml_detected_anomalies` dans les dernières `DEDUP_TTL_MIN` minutes | +| Variante : réinsertion si dégradation ≥ 0.05 | ✅ Réinsertion uniquement si le score s'est dégradé de ≥ 0.05 points | +| `DEDUP_TTL_MIN=0` pour désactiver | ✅ Conforme | +| Déduplication intra-cycle (`drop_duplicates`) | ✅ `cycle.py` ligne 302 : `drop_duplicates(subset=['src_ip'], keep='first')` | --- ## A6 — Pondération par récurrence dans le score -### Problème +### ✅ IMPLÉMENTÉ -La récurrence est actuellement un champ **informatif seulement** : une IP détectée 50 fois a le même seuil de filtrage qu'une IP vue pour la première fois. Un bot persistant et connu ne reçoit pas de pénalité de score. +**Module** : `pipeline.py` — lignes 148–151 -### Approche proposée - -Ajuster le score de décision en fonction de la récurrence : - -```python -# Score ajusté : plus une IP est récurrente, plus son score s'aggrave -recurrence_penalty = np.log1p(recurrence) * RECURRENCE_WEIGHT -adjusted_score = anomaly_score - recurrence_penalty -``` - -Avec `RECURRENCE_WEIGHT = 0.005` par défaut (configurable). Une IP vue 10 fois voit son score pénalisé de ~0.012, une IP vue 100 fois de ~0.023. - -Cette approche simule un **Prior bayésien** : la probabilité qu'une IP soit malveillante augmente avec ses détections passées. - -### Bénéfices -- Menaces persistantes classifiées plus sévèrement -- Réduction du bruit des anomalies éphémères -- Signal plus fort pour les blocages automatisés - -### Implémentation suggérée -- Ajouter `RECURRENCE_WEIGHT` (défaut 0.005, 0 pour désactiver) -- Stocker `raw_score` et `adjusted_score` séparément dans les logs +| Proposition | Implémentation | +|-------------|----------------| +| `RECURRENCE_WEIGHT = 0.005` | ✅ `config.py` : `RECURRENCE_WEIGHT = 0.005` | +| `adjusted = score - log1p(recurrence) * weight` | ✅ `raw_anomaly_score -= log1p(recurrence_count) * RECURRENCE_WEIGHT` | +| Récurrence chargée depuis ClickHouse | ✅ `view_ip_recurrence` dans `cycle.py` ligne 215 | +| Stockage séparé raw_score / adjusted_score | Le score ajusté remplace `raw_anomaly_score` (pas de stockage séparé) | --- ## A7 — Validation de complétude des features -### Problème +### ✅ IMPLÉMENTÉ -Si une feature est absente de la vue (colonne manquante, erreur de schéma), elle est silencieusement remplacée par `0` via `fillna(0)`. Cela **dégrade la qualité du modèle sans avertissement** : une feature entièrement à zéro n'apporte aucune information discriminante et biaise les scores. +**Module** : `scoring.py` — fonction `validate_features()` -### Approche proposée +| Proposition | Implémentation | +|-------------|----------------| +| Détection des features absentes | ✅ Features manquantes du DataFrame | +| Détection des features constantes (std=0) | ✅ Features à std=0 exclues | +| `MIN_VALID_FEATURE_RATIO` (défaut 0.8) | Implémenté à **0.50** (seuil moins strict) | +| Événement `FEATURE_WARNING` | ✅ Journalisé dans le JSONL | +| `SKIPPED_INVALID_FEATURES` si ratio insuffisant | ✅ Retourne `None` → cycle ignoré | -Au début de chaque cycle, après chargement du DataFrame : - -```python -def validate_features(df: pd.DataFrame, features: list, name: str) -> list: - zero_features = [f for f in features if f in df.columns and df[f].std() == 0] - missing_features = [f for f in features if f not in df.columns] - - if missing_features: - log_info(f"[{name}] ATTENTION: {len(missing_features)} features manquantes: {missing_features}") - if zero_features: - log_info(f"[{name}] ATTENTION: {len(zero_features)} features constantes (=0): {zero_features}") - - # Retourner uniquement les features exploitables - valid = [f for f in features if f in df.columns and df[f].std() > 0] - return valid -``` - -Un événement `FEATURE_WARNING` serait loggué, et si plus de 20% des features sont invalides, le cycle peut être `SKIPPED`. - -### Bénéfices -- Détection rapide des régressions de schéma ClickHouse -- Qualité de modèle assurée -- Facilite le debugging lors des évolutions de la vue - -### Implémentation suggérée -- Paramètre `MIN_VALID_FEATURE_RATIO` (défaut 0.8) -- Comparaison avec les features du modèle chargé (détecte les dérives de schéma post-mise à jour) +**Ajouts par rapport à la proposition** : +- Détection des features **entièrement à zéro** (pipeline non alimenté) en plus des features constantes. +- Exclusions structurelles par modèle (`STRUCTURAL_EXCLUDED_FEATURES` dans `config.py`) : + le modèle Applicatif exclut automatiquement 15 features TCP/TLS non disponibles sans corrélation. --- ## A8 — Clustering comportemental des anomalies -### Problème +### ✅ IMPLÉMENTÉ -Les anomalies sont analysées et insérées individuellement. Or, une campagne de botnet coordonnée peut impliquer des **dizaines d'IPs avec des profils similaires**. Cette information de **corrélation horizontale** est aujourd'hui invisible. +**Module** : `scoring.py` — fonction `_cluster_anomalies()` + `pipeline.py` pour l'escalade -### Approche proposée +| Proposition | Implémentation | +|-------------|----------------| +| DBSCAN (eps=0.5, min_samples=3) | Utilisé en **fallback**. Algorithme principal : **HDBSCAN** | +| `campaign_id` dans les événements | ✅ Journalisé dans les événements `ANOMALY` et inséré dans `ml_detected_anomalies` | +| `eps` et `min_samples` configurables | ✅ `CLUSTERING_MIN_SAMPLES = 3`, `ENABLE_CLUSTERING = true` | +| Activation conditionnelle | ✅ `ENABLE_CLUSTERING` (feature flag) | -Après la détection, appliquer un **DBSCAN** sur les features des anomalies pour identifier des clusters d'attaque : +**Améliorations par rapport à la proposition** : -```python -from sklearn.cluster import DBSCAN -X_anomalies = anomalies[features].fillna(0) -scaler = StandardScaler() -X_scaled = scaler.fit_transform(X_anomalies) -labels = DBSCAN(eps=0.5, min_samples=3).fit_predict(X_scaled) -anomalies['campaign_id'] = labels # -1 = isolé, 0+ = cluster -``` +1. **HDBSCAN au lieu de DBSCAN** : Utilise `HDBSCAN(min_cluster_size=3, min_samples=2, + cluster_selection_method='eom')` via import optionnel `hdbscan`. Fallback DBSCAN + si `hdbscan` non installé. -Les IPs d'un même cluster partagent un comportement similaire et peuvent faire partie d'une même infrastructure d'attaque. +2. **Clustering dans l'espace latent** : Si un modèle Autoencoder est disponible, + le clustering opère dans l'espace latent (dimension 16) plutôt que sur les + features brutes — meilleure séparation des clusters. -### Bénéfices -- Identification des campagnes coordonnées (botnets distribués) -- Enrichissement de `reason` avec un identifiant de campagne -- Permet des blocages de plages d'IPs entières - -### Implémentation suggérée -- DBSCAN uniquement si ≥ 5 anomalies dans le cycle (pas de coût si peu d'anomalies) -- Stocker `campaign_id` dans `ml_detected_anomalies` -- `eps` et `min_samples` configurables +3. **Escalade de campagne** (`pipeline.py` lignes 311–324) : Les IPs d'un cluster + ≥ 5 membres voient leur threat level escaladé de `HIGH` → `CRITICAL`. ### Références - Ester et al. (1996) — *A Density-Based Algorithm for Discovering Clusters in Large Spatial Databases* +- Campello et al. (2013) — *Density-Based Clustering Based on Hierarchical Density Estimates* (HDBSCAN) --- ## A9 — Métriques Prometheus / health check enrichi -### Problème +### ❌ À FAIRE -Le health check actuel est binaire (OK/DEGRADED). Cela ne permet pas : -- De monitorer la dérive du taux d'anomalies dans le temps -- D'alerter si aucun cycle ne s'est exécuté depuis X minutes -- De suivre l'âge du modèle en production +**État actuel** : Le health check est un serveur HTTP binaire dans `infra.py` +(`200 OK` / `503 DEGRADED`). Aucun endpoint `/metrics` n'est exposé. -### Approche proposée +**Ce qui manque** : +- Endpoint `/metrics` au format Prometheus text +- Métriques : durée du cycle, nombre d'anomalies par modèle, âge du modèle, + taille de la baseline, score de dérive +- Intégration Grafana/Alertmanager -Exposer un endpoint `/metrics` au format **Prometheus text** sur le même port : +**Prérequis** : Ajouter `prometheus_client` aux dépendances ou implémenter le +format texte manuellement sur le `HTTPServer` existant. + +**Note** : La priorité est faible car les métriques clés sont déjà disponibles via +les événements `CYCLE_END` dans le JSONL et les tables ClickHouse (`ml_all_scores`). + +### Proposition originale + +Exposer un endpoint `/metrics` sur le même port que le health check : ``` -# HELP botdetector_cycle_duration_seconds Duration of last analysis cycle -# TYPE botdetector_cycle_duration_seconds gauge botdetector_cycle_duration_seconds 12.4 - -# HELP botdetector_anomalies_total Total anomalies detected in last cycle -# TYPE botdetector_anomalies_total gauge botdetector_anomalies_total{model="Complet"} 3 -botdetector_anomalies_total{model="Applicatif"} 7 - -# HELP botdetector_model_age_hours Age of active model in hours botdetector_model_age_hours{model="Applicatif"} 0.91 - -# HELP botdetector_human_baseline_size Nb of human samples used for training botdetector_human_baseline_size{model="Applicatif"} 1725 ``` -Implémenté sans dépendance externe (format texte manuel ou lib légère `prometheus_client`). - -### Bénéfices -- Intégration Grafana/Alertmanager -- Alertes sur dérive du taux d'anomalies (ex. : >50% d'une heure à l'autre) -- Monitoring de la fraîcheur du modèle - -### Implémentation suggérée -- Ajouter `prometheus_client` ou générer le format texte manuellement -- Endpoint `/metrics` sur le même `HTTPServer` existant -- Métriques stockées dans un dict thread-safe mis à jour après chaque cycle - --- ## A10 — Normalisation des scores entre modèles -### Problème +### ✅ IMPLÉMENTÉ -Les scores `decision_function` de l'IF ne sont **pas comparables entre modèles** entraînés sur des données différentes. Un score de -0.10 sur le modèle Complet et -0.10 sur le modèle Applicatif n'ont pas la même signification si les baselines et les features sont différentes. +**Module** : `scoring.py` — fonction `normalize_scores()` -La déduplication actuelle par `src_ip` prend le score le plus bas sans tenir compte de cette non-comparabilité. +| Proposition | Implémentation | +|-------------|----------------| +| Min-max sur les scores < 0 | ✅ Normalisation min-max des scores négatifs | +| Intervalle [-1, 0] | Intervalle **[0, 1]** où 1 = le plus anormal | +| Threat levels sur score normalisé | Les threat levels sont toujours calculés sur le score **brut** IF (`score_to_threat_level()` dans `infra.py`) | +| Déduplication sur score normalisé | La déduplication utilise le score brut ajusté (avec pénalité de récurrence) | -### Approche proposée - -Normaliser les scores par rapport à la distribution des scores négatifs du cycle courant : - -```python -# Normalisation min-max sur le sous-ensemble des scores < 0 -neg_scores = unknown_traffic['anomaly_score'][unknown_traffic['anomaly_score'] < 0] -if len(neg_scores) > 0: - score_min, score_max = neg_scores.min(), neg_scores.max() - unknown_traffic['normalized_score'] = ( - (unknown_traffic['anomaly_score'] - score_min) / (score_max - score_min + 1e-9) - ).clip(0, 1) * -1 # entre -1 et 0 -``` - -Les niveaux de menace seraient alors calculés sur le score normalisé, rendant la comparaison entre modèles cohérente. - -### Bénéfices -- Cohérence des niveaux CRITICAL/HIGH/MEDIUM entre modèles -- Déduplication plus juste -- Seuils de threat_level interprétables de façon constante +**Note** : Le choix de conserver les threat levels sur le score brut (plutôt que +normalisé) assure la stabilité des seuils de classification. Le score normalisé +est utilisé pour le score final combiné (EIF+AE+XGB) et l'insertion dans +`ml_all_scores`. --- ## Notes d'implémentation générales - **Compatibilité** : toute amélioration doit rester rétrocompatible avec le schéma `ml_detected_anomalies` existant (ajout de colonnes optionnelles uniquement) -- **Lisibilité** : garder le code en sections délimitées par les bandeaux `═══` existants -- **Tests** : valider chaque changement par une exécution Docker sur la base de données réelle -- **Documentation** : mettre à jour `DOCUMENTATION.md` après chaque implémentation -- **Feature flags** : les nouvelles fonctionnalités comportementales devraient être activables via variable d'environnement pour un rollout progressif +- **Architecture modulaire** : le code est réparti en 11 modules (voir `DOCUMENTATION.md` §1.1), chaque amélioration touche un ou deux modules spécifiques +- **Tests** : 36 tests auto-contenus dans `tests/test_detector.py`, exécutables via `make test-bot-detector` +- **Feature flags** : les fonctionnalités sont activables via variables d'environnement (`ENABLE_SHAP`, `ENABLE_CLUSTERING`, `ENABLE_MULTIWINDOW`, `ENABLE_FEEDBACK`) +- **Imports optionnels** : `isotree`, `torch`, `xgboost`, `shap`, `hdbscan` sont tous optionnels avec fallbacks (`config.py`) --- # Nouvelles dimensions de features — Propositions B -> Propositions de features supplémentaires pour l'Isolation Forest, validées sur les données réelles de `ja4_processing`. +> Propositions de features supplémentaires pour l'ensemble ML, validées sur les données réelles de `ja4_processing`. > Chaque proposition indique la force du signal observée en base, la source de données, la formule de calcul et les références scientifiques. -## Résumé des signaux +## Résumé des signaux — État d'implémentation -| # | Feature | Signal observé | Modèle | Impact estimé | -|---|---------|---------------|--------|--------------| -| B1 | JA3/JA4 diversity ratio | 809 JA3 pour 2 JA4 (IP connue bot) | Complet | 🔴 Élevé | -| B2 | SYN timing regularity | 386/3222 IPs (12%) avec variance=0 | Complet | 🔴 Élevé | -| B3 | TLS 1.2 exclusive ratio | 136/3259 IPs (4%) — jamais TLS 1.3 | Complet | 🔴 Élevé | -| B4 | HEAD method ratio | 67/3335 IPs (2%) à >50% HEAD | Les deux | 🟠 Moyen | -| B5 | Sec-Fetch absence rate | Signal L7 universel (correlated=0 aussi) | Les deux | 🟠 Moyen | -| B6 | Accept header entropy | Bots = Accept vide ou `*/*` constant | Les deux | 🟠 Moyen | -| B7 | TLS version entropy | TLS 1.3 = 97.3% du trafic légitime | Complet | 🟠 Moyen | -| B8 | HTTP/TLS protocol mismatch | HTTP/1.1 + TLS 1.3 = ratio anormal | Complet | 🟡 Utile | -| B9 | IP DF-bit variance | DF inconsistant = stack spoofé | Complet | 🟡 Utile | -| B10 | JA4 concentration intra-ASN | JA4 rare dans ASN = outil exotique | Complet | 🟡 Utile | +| # | Feature | Statut | Modèle | Implémentation | +|---|---------|--------|--------|----------------| +| B1 | `ja3_diversity_ratio` | ✅ IMPLÉMENTÉ | Complet | `FEATURES_COMPLET` dans `preprocessing.py` ligne 56 | +| B2 | `syn_timing_cv` | ✅ IMPLÉMENTÉ | Complet | `FEATURES_COMPLET` dans `preprocessing.py` ligne 56 | +| B3 | `tls12_ratio` | ✅ IMPLÉMENTÉ | Complet | `FEATURES_COMPLET` dans `preprocessing.py` ligne 56 | +| B4 | `head_ratio` | ✅ IMPLÉMENTÉ | Les deux | `FEATURES` dans `preprocessing.py` ligne 30 | +| B5 | `sec_fetch_absence_rate` | ✅ IMPLÉMENTÉ | Les deux | `FEATURES` dans `preprocessing.py` ligne 30 | +| B6 | `generic_accept_ratio` | ✅ IMPLÉMENTÉ | Les deux | `FEATURES` dans `preprocessing.py` ligne 30 | +| B7 | `http10_ratio` | ✅ IMPLÉMENTÉ | Les deux | `FEATURES` dans `preprocessing.py` ligne 30 | +| B8 | `ip_df_variance` | ✅ IMPLÉMENTÉ | Complet | `FEATURES_COMPLET` dans `preprocessing.py` ligne 56 | +| B9 | IP DF-bit consistency | 🔄 PARTIEL | Complet | Couvert par B8 (`ip_df_variance`), pas de feature séparée | +| B10 | JA4 concentration intra-ASN | ✅ IMPLÉMENTÉ (préexistant) | Les deux | `ja4_asn_concentration` dans `FEATURES` — existait avant les propositions B | --- ## B1 — JA3/JA4 Diversity Ratio (rotation de fingerprint TLS) -### Observation +### ✅ IMPLÉMENTÉ + +**Feature** : `ja3_diversity_ratio` — dans `FEATURES_COMPLET` (modèle Complet uniquement). + +**Exclusion structurelle** : Exclue du modèle Applicatif via `STRUCTURAL_EXCLUDED_FEATURES` +dans `config.py`, car le JA3 n'est disponible que pour le trafic corrélé (L4/TLS). + +**Détails d'implémentation** : +- La vue ClickHouse `view_ai_features_1h` calcule `uniqMerge(uniq_ja3) / greatest(uniq_ja4, 1)` +- Le MV `mv_agg_host_ip_ja4_1h` agrège `uniqState(ja3)` depuis `http_logs` + +### Observation originale ``` 185.177.72.60 → 1619 JA3 distincts / 2 JA4 → ratio 809.5 @@ -443,314 +310,339 @@ Les niveaux de menace seraient alors calculés sur le score normalisé, rendant Le JA4 reste stable (il encode le type de client TLS + ALPN) mais le JA3 varie massivement. C'est la signature d'un **bot qui randomise les extensions TLS** pour contourner la détection par fingerprint. -### Feature proposée - -```sql --- Dans mv_agg_host_ip_ja4_1h -uniqState(ja3) AS uniq_ja3 -- à ajouter dans la table d'agrégation -``` - -```python -# Dans view_ai_features_1h -ja3_diversity_ratio = uniq_ja3 / greatest(uniq_ja4, 1) -``` - -### Signal en base - -- Trafic humain : ratio typiquement 1–3 (même navigateur, légères variations) -- Bot avec rotation : ratio 17–809 → signal extrêmement discriminant -- Disponible : `ja3` est présent dans `http_logs` avec 100% de valeurs non-vides pour correlated=1 - -### Modifications requises - -1. Ajouter `uniqState(ja3) AS uniq_ja3` dans `mv_agg_host_ip_ja4_1h` et `agg_host_ip_ja4_1h` -2. Ajouter `uniqMerge(uniq_ja3) / greatest(uniq_ja4_merged, 1) AS ja3_diversity_ratio` dans `view_ai_features_1h` -3. Ajouter `ja3_diversity_ratio` à `feats_complet` dans `bot_detector.py` - ### Références -- Siby et al. (2020) — *Encrypted DNS → Privacy? A Traffic Analysis Perspective* — méthodes de diversité de fingerprint -- Anderson & McGrew (2016) — *Machine Learning for Encrypted Malware Traffic Classification* — JA3 comme feature primaire -- Husák et al. (2022) — *TLS fingerprinting for bot detection* — rotation JA3 comme évasion signature +- Siby et al. (2020) — *Encrypted DNS → Privacy? A Traffic Analysis Perspective* +- Anderson & McGrew (2016) — *Machine Learning for Encrypted Malware Traffic Classification* +- Husák et al. (2022) — *TLS fingerprinting for bot detection* --- ## B2 — SYN-to-ClientHello Timing Regularity -### Observation +### ✅ IMPLÉMENTÉ + +**Feature** : `syn_timing_cv` — dans `FEATURES_COMPLET` (modèle Complet uniquement). + +**Implémentation** : Coefficient de variation (std/mean) du timing SYN→ClientHello, +calculé dans la vue ClickHouse : `sqrt(tcp_jitter_variance) / greatest(avg_syn_ms, 1)`. + +### Observation originale ``` 88.202.237.59 : 45 connexions, avg=22ms, std=0.00ms → timing robotique parfait -92.184.144.129: 41 connexions, avg=10ms, std=0.00ms → idem 386/3222 IPs analysées (12%) ont une variance=0 ``` -Un humain présente une distribution aléatoire (Weibull ou log-normale) des temps de réponse réseau. Un bot utilisant un scheduler fixe ou une connexion locale a une variance proche de zéro. - -### Feature proposée - -```sql --- Dans view_ai_features_1h (CTE) -varPopMerge(tcp_jitter_variance) AS syn_jitter_variance, -- déjà présent (tcp_jitter_variance) --- Ajouter le coefficient de variation (normalisé) -``` - -```python -# cv = std / mean → 0 = robotique, >0.5 = humain -syn_timing_cv = sqrt(syn_jitter_variance) / greatest(avg_syn_ms, 1) -``` - -**Note** : `tcp_jitter_variance` est déjà dans le modèle mais c'est la variance brute. Le **coefficient de variation** (std/mean) normalise par le délai moyen et est plus discriminant pour différencier bots rapides (10ms) de bots lents (100ms). - -### Modifications requises - -1. Ajouter `avg(syn_to_clienthello_ms)` dans `mv_agg_host_ip_ja4_1h` → `avg_syn_ms` -2. Calculer `syn_timing_cv = sqrt(tcp_jitter_variance) / greatest(avg_syn_ms, 1)` dans `view_ai_features_1h` -3. Ajouter `syn_timing_cv` à `feats_complet` - ### Références -- Zeber et al. (2020) — *The Measurement of Web Timing* — distribution log-normale pour humains -- Beugin et al. (2021) — *Robustness of Traffic Analysis Against Adversarial Timing* — variance comme discriminant -- Stevanovic & Pedersen (2015) — *Detecting Bots Using Multi-level Traffic Analysis* — timing régularité = signal bot L4 +- Zeber et al. (2020) — *The Measurement of Web Timing* +- Stevanovic & Pedersen (2015) — *Detecting Bots Using Multi-level Traffic Analysis* --- ## B3 — TLS 1.2 Exclusive Ratio -### Observation +### ✅ IMPLÉMENTÉ + +**Feature** : `tls12_ratio` — dans `FEATURES_COMPLET` (modèle Complet uniquement). + +**Utilisation double** : En plus d'être une feature ML, `tls12_ratio` est utilisée +par l'**axe 5 (TLS/TCP Coherence)** de la détection navigateur dans `browser.py` +(condition `tls12_ratio < 0.1` pour un score positif). + +### Observation originale ``` 95.217.144.244 : 360/360 requêtes en TLS 1.2 (jamais TLS 1.3) -37.65.177.201 : 267/267 requêtes en TLS 1.2 136 IPs utilisent exclusivement TLS 1.2 sur 3259 analysées (4.2%) ``` -TLS 1.3 représente 97.3% du trafic en 2026. Les navigateurs modernes n'utilisent TLS 1.2 que comme fallback exceptionnel. Une IP utilisant **exclusivement** TLS 1.2 utilise un client obsolète, une bibliothèque custom, ou un outil de scan. - -### Feature proposée - -```sql --- Dans mv_agg_host_ip_ja4_1h -sum(IF(tls_version = '1.2', 1, 0)) AS tls12_count -- nouveau --- tls_version déjà stockée via tls_alpn_raw → à distinguer ou ajouter -``` - -```python -# Dans view_ai_features_1h -tls12_ratio = tls12_count / greatest(hits, 1) -``` - -### Modifications requises - -1. Ajouter `sum(IF(src.tls_version = '1.2', 1, 0)) AS tls12_count` dans `mv_agg_host_ip_ja4_1h` -2. Ajouter `tls12_count` dans `agg_host_ip_ja4_1h` -3. Calculer `tls12_count / hits AS tls12_ratio` dans `view_ai_features_1h` - ### Références -- Kotzias et al. (2018) — *Coming of Age: A Longitudinal Study of TLS Deployment* — vieillissement des stacks -- Naylor et al. (2014) — *The Cost of the S in HTTPS* — adoption TLS 1.3 par navigateurs légitimes +- Kotzias et al. (2018) — *Coming of Age: A Longitudinal Study of TLS Deployment* - Cloudflare Radar 2024 — TLS 1.3 = 95%+ du trafic web mondial --- ## B4 — HEAD Method Ratio -### Observation +### ✅ IMPLÉMENTÉ + +**Feature** : `head_ratio` — dans `FEATURES` (les deux modèles). + +**Commentaire dans le code** : `# B4-B7 : features L7 pures` (`preprocessing.py` ligne 29). + +### Observation originale ``` 34.140.199.84 : 11/12 requêtes HEAD (91.7%) → Google Cloud uptime checker 67/3335 IPs ont >50% de requêtes HEAD ``` -La méthode HEAD est utilisée pour vérifier la disponibilité d'une ressource sans télécharger son contenu. C'est la signature des : -- **Uptime checkers** (Pingdom, UptimeRobot, Google Cloud Health Check) -- **Scanners de vulnérabilités** (Nikto, Nuclei) -- **Bots de reconnaissance discrète** - -### Feature proposée - -```python -# head_ratio = déjà calculable depuis count_post (method breakdown) -# Ajouter dans mv_agg_host_ip_ja4_1h : -count_head = sum(IF(method = 'HEAD', 1, 0)) -``` - -```python -head_ratio = count_head / greatest(hits, 1) -``` - -### Note : disponibilité dans les deux modèles - -Contrairement aux features TCP, `head_ratio` est disponible pour `correlated=0` aussi — c'est une feature HTTP pure. À ajouter dans les deux listes `feats` et `feats_complet`. - ### Références -- Barracuda Networks (2023) — *Bot Traffic Report* — HEAD requests pattern +- Barracuda Networks (2023) — *Bot Traffic Report* - OWASP Automated Threat Handbook — OAT-011: Scraping, OAT-018: Credential Stuffing --- ## B5 — Sec-Fetch Absence Rate -### Observation +### ✅ IMPLÉMENTÉ -Les headers `Sec-Fetch-Site`, `Sec-Fetch-Mode`, `Sec-Fetch-Dest` sont injectés par les navigateurs modernes (Chrome 76+, Firefox 90+) **automatiquement** depuis 2019. Leur absence est un signal de : -- Client HTTP non-navigateur (curl, requests, Scrapy, headless Chrome sans headers complets) -- Vieux navigateur ou UA spoofé -- HTTP CONNECT proxy +**Feature** : `sec_fetch_absence_rate` — dans `FEATURES` (les deux modèles). -### Feature proposée +**Utilisation double** : Également utilisée par l'**axe 3 (HTTP Modern)** de la +détection navigateur dans `browser.py` (condition `sec_fetch_absence_rate < 0.3` +pour un score positif, poids 0.25). -```sql --- Dans mv_agg_host_ip_ja4_1h -sum(IF(length(src.header_sec_fetch_site) = 0, 1, 0)) AS count_no_sec_fetch -``` +### Observation originale -```python -sec_fetch_absence_rate = count_no_sec_fetch / greatest(hits, 1) -``` - -### Combinaison avec `modern_browser_score` - -`sec_fetch_absence_rate` + `modern_browser_score` forment une paire complémentaire : -- Bot avec UA Chrome forgé → `modern_browser_score` élevé mais `sec_fetch_absence_rate` = 1 → contradiction forte - -### Modifications requises - -1. `count_no_sec_fetch` dans le MV et la table -2. Calcul dans la vue +Les headers `Sec-Fetch-*` sont injectés automatiquement par les navigateurs modernes +(Chrome 76+, Firefox 90+). Leur absence signale un client non-navigateur. ### Références - West & Loshbough (2019) — *Fetch Metadata Request Headers* (W3C Spec) -- Invernizzi et al. (2016) — *CLOAK of Visibility* — inconsistance headers = bot +- Invernizzi et al. (2016) — *CLOAK of Visibility* --- ## B6 — Accept Header Entropy -### Observation +### ✅ IMPLÉMENTÉ -Les navigateurs légitimes envoient des headers `Accept` complexes et cohérents : -``` -image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8 -``` -Les bots envoient : -``` -*/* (curl, wget, Scrapy) -(vide) (bots minimalistes) -text/html (outils basiques) -``` +**Feature** : `generic_accept_ratio` — dans `FEATURES` (les deux modèles). -### Feature proposée +**Note** : La proposition originale suggérait une entropie du header Accept. +L'implémentation utilise l'approche simplifiée (fraction de requêtes avec Accept +générique/vide), qui est plus robuste et moins coûteuse en calcul ClickHouse. -```python -# Diversité des valeurs Accept par IP (proxy de comportement navigateur) -accept_entropy = -sum(p * log2(p+1e-9) for p in accept_value_probs) - -# Ou plus simplement : fraction de requêtes avec Accept générique/vide -generic_accept_ratio = count_generic_accept / hits -# où generic = longueur(Accept) < 10 ou Accept IN ('*/*', '') -``` - -```sql -sum(IF(length(src.header_accept) < 5, 1, 0)) AS count_generic_accept -``` +**Utilisation double** : Également utilisée par l'**axe 3 (HTTP Modern)** dans +`browser.py` (condition `generic_accept_ratio < 0.3`, poids 0.10). ### Références -- Nikiforakis et al. (2013) — *Cookieless Monster: Exploring the Ecosystem of Web-based Device Fingerprinting* — Accept comme composant stable -- Acar et al. (2014) — *The Web Never Forgets* — entropie des headers HTTP +- Nikiforakis et al. (2013) — *Cookieless Monster* +- Acar et al. (2014) — *The Web Never Forgets* --- ## B7 — HTTP/TLS Protocol Version Mismatch -### Observation +### ✅ IMPLÉMENTÉ -``` -HTTP/2.0 → 160855 requêtes (84%) -HTTP/1.1 → 26421 requêtes (14%) -TLS 1.3 → 177330 requêtes (97%) -``` +**Feature** : `http10_ratio` — dans `FEATURES` (les deux modèles). -HTTP/2 requiert TLS dans les navigateurs modernes. Combinaisons anormales : -- HTTP/1.1 + TLS 1.3 : légitime mais rare pour les vrais navigateurs (eux font HTTP/2 si TLS 1.3) -- HTTP/1.0 + TLS : extrêmement suspect (outil custom ou ancien bot) -- HTTP/2 + TLS 1.2 : possible mais déclinant +**Note** : La proposition originale contenait deux features (`http1_tls13_ratio` +et `http10_ratio`). Seule `http10_ratio` (fraction de requêtes HTTP/1.0) a été +implémentée — c'est le signal le plus fort car HTTP/1.0 est extrêmement rare dans +le trafic navigateur moderne. -### Feature proposée - -```python -# Fraction de requêtes avec HTTP/1.x malgré TLS 1.3 disponible -http1_tls13_ratio = count_http1_with_tls13 / greatest(hits, 1) -# http1_0_ratio = count_http10 / hits # signal fort -``` - -```sql -sum(IF(http_version = 'HTTP/1.0', 1, 0)) AS count_http10, -sum(IF(http_version LIKE 'HTTP/1%' AND tls_version = '1.3', 1, 0)) AS count_http1_tls13 -``` +**Utilisation double** : Également utilisée par l'**axe 5 (TLS/TCP Coherence)** dans +`browser.py` (condition `http10_ratio = 0`, poids 0.15). --- ## B8 — IP DF-Bit Consistency -### Observation +### ✅ IMPLÉMENTÉ -``` -df=1 : 172490 paquets (92%) -df=0 : 15016 paquets (8%) -``` +**Feature** : `ip_df_variance` — dans `FEATURES_COMPLET` (modèle Complet uniquement). -Le bit "Don't Fragment" est généralement constant pour une session TCP donnée. Une IP qui alterne DF=0 et DF=1 au sein d'une même session, ou entre sessions, peut indiquer : -- **Usurpation d'IP** (spoofed source packets dans un botnet) -- **Stack TCP custom** (bots implémentant leur propre TCP) -- **NAT traversal** avec réécriture de paquets - -### Feature proposée - -```python -df_variance = stddev(ip_meta_df) per IP # 0 = cohérent, >0 = mélangé -``` - -```sql -varPop(toFloat64(ip_meta_df)) AS ip_df_variance -``` - -Faible impact seul, mais utile en combinaison avec TTL variance pour le TCP fingerprinting multi-dimensional. +**Implémentation** : `varPop(toFloat64(ip_meta_df))` dans la vue ClickHouse. +La variance du bit DF est combinée avec les features TTL (`avg_ttl`, `ttl_std`) +pour le TCP fingerprinting multi-dimensionnel. --- -## Récapitulatif des modifications ClickHouse nécessaires +## B9 — IP DF-Bit Consistency (proposition distincte) -### Colonnes à ajouter dans `agg_host_ip_ja4_1h` +### 🔄 PARTIEL + +**État** : Couvert par B8. La proposition B9 était une variante de B8 avec une +analyse de cohérence intra-session supplémentaire. Cette variante n'a pas été +implémentée séparément — `ip_df_variance` couvre le cas d'usage principal. + +--- + +## B10 — JA4 Concentration intra-ASN + +### ✅ IMPLÉMENTÉ (préexistant) + +**Feature** : `ja4_asn_concentration` — dans `FEATURES` (les deux modèles). + +**Note** : Cette feature existait déjà avant les propositions B, dans la liste +initiale des features du modèle. Elle mesure la concentration d'un même fingerprint +JA4 au sein d'un ASN — un JA4 très concentré dans un seul ASN suggère un outil +déployé dans un datacenter spécifique. + +--- + +## Récapitulatif des modifications ClickHouse réalisées + +### Colonnes ajoutées dans `agg_host_ip_ja4_1h` + +Les colonnes suivantes ont été ajoutées au MV et à la table d'agrégation pour +supporter les features B1–B8 : ```sql -ALTER TABLE ja4_processing.agg_host_ip_ja4_1h - ADD COLUMN uniq_ja3 AggregateFunction(uniq, String), - ADD COLUMN avg_syn_ms SimpleAggregateFunction(avg, Float64), - ADD COLUMN tls12_count SimpleAggregateFunction(sum, UInt64), - ADD COLUMN count_head SimpleAggregateFunction(sum, UInt64), - ADD COLUMN count_no_sec_fetch SimpleAggregateFunction(sum, UInt64), - ADD COLUMN count_generic_accept SimpleAggregateFunction(sum, UInt64), - ADD COLUMN count_http10 SimpleAggregateFunction(sum, UInt64); +-- B1 : JA3 diversity ratio +uniq_ja3 AggregateFunction(uniq, String) +-- B2 : SYN timing regularity +avg_syn_ms SimpleAggregateFunction(avg, Float64) +-- B3 : TLS 1.2 ratio +tls12_count SimpleAggregateFunction(sum, UInt64) +-- B4 : HEAD method ratio +count_head SimpleAggregateFunction(sum, UInt64) +-- B5 : Sec-Fetch absence +count_no_sec_fetch SimpleAggregateFunction(sum, UInt64) +-- B6 : Generic Accept ratio +count_generic_accept SimpleAggregateFunction(sum, UInt64) +-- B7 : HTTP/1.0 ratio +count_http10 SimpleAggregateFunction(sum, UInt64) +-- B8 : DF-bit variance +ip_df_variance (calculé via varPop dans la vue) ``` -### Nouvelles features dans `view_ai_features_1h` +### Features dérivées dans `view_ai_features_1h` | Feature | Formule | Modèle | |---------|---------|--------| -| `ja3_diversity_ratio` | `uniq_ja3 / greatest(uniq_ja4, 1)` | Complet | +| `ja3_diversity_ratio` | `uniqMerge(uniq_ja3) / greatest(uniq_ja4, 1)` | Complet | | `syn_timing_cv` | `sqrt(tcp_jitter_variance) / greatest(avg_syn_ms, 1)` | Complet | | `tls12_ratio` | `tls12_count / greatest(hits, 1)` | Complet | | `head_ratio` | `count_head / greatest(hits, 1)` | Les deux | | `sec_fetch_absence_rate` | `count_no_sec_fetch / greatest(hits, 1)` | Les deux | | `generic_accept_ratio` | `count_generic_accept / greatest(hits, 1)` | Les deux | | `http10_ratio` | `count_http10 / greatest(hits, 1)` | Les deux | +| `ip_df_variance` | `varPop(toFloat64(ip_meta_df))` | Complet | -> ⚠️ Les colonnes ajoutées par ALTER ne sont pas rétro-alimentées dans les données historiques. Un backfill depuis `http_logs` sera nécessaire. -> ⚠️ La MV `mv_agg_host_ip_ja4_1h` doit être **recréée** (pas de ALTER sur une MV) pour inclure les nouveaux champs. +--- + +# Fonctionnalités ajoutées hors propositions initiales + +> Features et mécanismes implémentés dans la refonte modulaire qui ne figuraient pas +> dans les propositions A1–A10 / B1–B10 originales. + +## Ensemble triple-voix (EIF + Autoencoder + XGBoost) + +**Module** : `models.py` + +La proposition initiale n'incluait qu'un Isolation Forest. L'architecture actuelle +utilise un **ensemble triple-voix** : + +| Modèle | Poids | Rôle | +|--------|-------|------| +| Extended Isolation Forest (isotree) | 56% | Détection non supervisée principale | +| Autoencoder (PyTorch) | 24% | Erreur de reconstruction comme signal complémentaire | +| XGBoost | 20% | Supervision via feedback SOC (`soc_feedback`) | + +**Formule de combinaison** : + +``` +final = (1 - β) × [(1 - α) × eif_norm + α × ae_norm] + β × xgb_prob +``` + +où `α = AE_WEIGHT = 0.30` et `β = XGB_WEIGHT = 0.20`. + +## Extended Isolation Forest (isotree) + +**Module** : `models.py` + +Remplacement de sklearn `IsolationForest` par `isotree.ExtendedIsolationForest` : +- `ndim=min(3, len(features))` — partitions multi-dimensionnelles (contre 1D pour sklearn) +- Calibration des scores : `sklearn_equiv = 0.5 - isotree_score` +- Fallback automatique vers sklearn si `isotree` non installé + +## Détection multifactorielle des navigateurs (5 axes) + +**Module** : `browser.py` + +Nouveau système de classification des navigateurs légitimes basé sur 5 axes +pondérés indépendants (voir `DOCUMENTATION.md` §3.4) : + +| Axe | Poids | Signal | +|-----|-------|--------| +| 1 — JA4 Known | 0.25 | Famille navigateur identifiée | +| 2 — JA4 Structure | 0.15 | TLS 1.3, h2/h3, ciphers/extensions | +| 3 — HTTP Modern | 0.25 | Sec-Fetch, Accept-Language, modern_browser_score | +| 4 — Nav Behavior | 0.15 | Cookies, Referer, assets, navigation | +| 5 — TLS Coherence | 0.20 | ALPN, window scale, TLS 1.2 ratio | + +Seuil : `browser_confidence ≥ 0.55` + famille identifiée → `LEGITIMATE_BROWSER`. + +## Escalade de campagne + +**Module** : `pipeline.py` lignes 311–324 + +Après le clustering HDBSCAN, les IPs appartenant à un cluster de **≥ 5 membres** +voient leur threat level escaladé de `HIGH` → `CRITICAL`. Identifie les campagnes +de botnet coordonnées. + +## Feedback SOC (apprentissage supervisé) + +**Module** : `cycle.py` lignes 221–238 + +Intégration du feedback des analystes SOC depuis la table `audit_logs` : +- Faux positifs (FP) → reclassés en baseline humaine (`asn_label='isp'`) +- Vrais positifs (TP) → étiquetés `soc_confirmed_bot` pour l'entraînement XGBoost +- Configurable via `ENABLE_FEEDBACK=true` et `FEEDBACK_WINDOW_DAYS=7` + +## Features thèse §5 + +**Module** : `preprocessing.py` + `cycle.py` + +9 features issues de la thèse (§5) enrichies depuis `view_thesis_features_1h` : +- `path_transition_entropy` — entropie des transitions entre chemins +- `cadence_cv` — coefficient de variation de la cadence +- `burst_ratio` / `pause_ratio` — ratios de rafales et pauses +- `lag1_autocorrelation` — autocorrélation lag-1 des inter-arrivées +- `benford_deviation` — déviation par rapport à la loi de Benford +- `host_diversity` / `host_sweep_speed` / `host_coverage_uniformity` + +## Features P0+P1 (sous-exploitées et nouvelles) + +**Module** : `preprocessing.py` + +Features supplémentaires ajoutées à `FEATURES` : +- `is_fake_navigation` — détection de fausse navigation (P0) +- `true_window_size` / `window_mss_ratio` — TCP window analysis (P0, Complet) +- `has_xff` — présence de X-Forwarded-For (P1) +- `unusual_content_type_ratio` — Content-Types inhabituels (P1) +- `non_standard_port_ratio` — ports non standard (P1) +- `login_post_concentration` — concentration de POST sur login (P1) +- `sec_ch_mobile_mismatch` — incohérence Sec-CH-UA-Mobile (P1) + +## TTL Fingerprinting OS + +**Module** : `preprocessing.py` — `FEATURES_COMPLET` + +Features TCP ajoutées pour le fingerprinting du système d'exploitation : +- `avg_ttl` — TTL moyen (différent par OS : 64 Linux, 128 Windows, 255 Cisco) +- `ttl_std` — écart-type du TTL (doit être ~0 pour un trafic homogène) +- `no_window_scale_ratio` — ratio de sessions sans TCP window scaling + +## Dérive JA4 intra-session + +**Module** : `preprocessing.py` — `FEATURES_COMPLET` + +- `ja4_drift_ratio` — fraction de sessions où le JA4 change pendant la session. + Signal de bot qui reconfigure son client TLS à chaque requête. + +## Validation gate (modèles) + +**Module** : `models.py` + +Après entraînement, le modèle est validé sur un jeu de validation (split 80/20). +Si `val_anomaly_rate > 0.20`, le modèle est rejeté et l'ancien modèle est conservé. +Empêche le déploiement de modèles dégénérés. + +## Feature pruning automatique + +**Module** : `models.py` + +Les features avec variance < 1e-6 sont automatiquement éliminées avant +l'entraînement de l'EIF. Évite les partitions dégénérées sur des features constantes.