← Blog
Mise à jour du 19 avril 2026 : les chiffres annoncés dans cet article étaient basés sur un corpus pollué par des labels automatiques (44 807 records labellisés clean par le scanner, sans review humaine). La vraie performance mesurée sur un corpus de 302 labels humains est AUC 0.9917, pas 0.999. Voir Quand le ML ne marche pas pour la retrospective complète et le pivot vers un post-filtre déterministe en v2.10.97.

Le moniteur MUAD'DIB tourne depuis décembre 2025. ~10 000 packages npm scannés par jour, 24/7. ~600 alertes webhook Discord quotidiennes. Après 3 mois : zéro malware confirmé. Pas parce qu'il n'y en a pas, mais parce que 600 cards orange identiques sans hiérarchie, personne ne les trie.

Le problème : labels contaminés

20 questions posées sur le pipeline. 5 causes racines. La plus grave : la contamination des labels ML.

  1. Le moniteur détecte un package suspect (score ≥ 20)
  2. Le sandbox Docker l'exécute
  3. Le sandbox ne détecte rien
  4. Le moniteur labellise automatiquement le package comme "fp"

Sauf que le sandbox n'avait pas de honey tokens pendant 3 mois. Un sandbox propre ne prouve rien - le malware peut ne pas s'être activé (time bomb, cible spécifique, check d'environnement). 8176 records contaminés.

Le classifier ML apprenait à dire "tout est FP". Précision 37.2%, recall 99.8%.

Le nettoyage

Conversion des 8176 labels "fp" automatiques en "unconfirmed", exclus de l'entraînement. Seuls les labels validés par review manuelle (manualReview: true) sont acceptés comme "fp".

Même code, mêmes features, données propres :

MétriqueAvantAprès
Précision37.2%97.8%
Recall99.8%93.3%
F10.5410.955

Benchmark Datadog

MétriqueValeur
In-scope14 587
Détectés (@1)13 538 (92.8%)
Détectés (@20)10 202 (69.9%)
Score = 01 049

ML1 - Malware detector

XGBoost, 114 arbres, 21 features, seuil 0.500. Top features :

  • score - score de risque global (agrégation des heuristiques)
  • max_single_points - poids de la finding la plus grave
  • file_score_max - score du fichier le plus suspect
  • threat_density - findings / fichiers (malware = concentré, framework = dilué)

Le ML décide "est-ce que l'ensemble de signaux ressemble à un malware ou à du bruit". Les règles décident "quel type de menace".

ML2 - Bundler detector

XGBoost, 98 arbres, 30 features, seuil 0.100. Les bundlers (webpack, rollup) génèrent du code minifié qui accumule des findings légitimes - eval, Function, require dynamiques - qui ressemblent à du malware. ML2 détecte ces cas.

F1 de 0.996. Warning : unpacked_size_bytes corrèle fortement avec les bundlers et pourrait constituer un leakage. Le F1 réel en production sera probablement inférieur.

Triage P1/P2/P3

Classification visuelle des alertes :

  • P1 (rouge) - IOC match, types haute confiance, canary token exfiltré, sandbox positif. ~26/jour.
  • P2 (orange) - Score ≥ 50, compound scoring, lifecycle + intent cohérence.
  • P3 (jaune) - Reste. Score 20-49 sans signal fort. Majorité des FP.

Les ~26 vrais malwares sont visuellement distincts des ~574 FP. Triage P1 en 2 minutes/jour au lieu de noyer dans 600 cards identiques.

La suite : LLM Phase 3

Investigation manuelle de 9 packages live : tous FP. Temps moyen 5-10 minutes par package. À 600 alertes/jour, le triage humain ne scale pas.

Plan : Claude 3.5 Haiku en shadow mode pour analyser les alertes P2/P3. ~$17/mois. Phases : shadow → enforce P3 → enforce P2+P3. Les P1 ne seront jamais filtrées.

Leçon

Le même algorithme XGBoost, les mêmes hyperparamètres, les mêmes 21 features. Précision 37% → 98% avec un seul changement : des données propres. Un sandbox sans honey tokens qui labellise "fp" automatiquement, c'est du label poisoning sur soi-même.