Comment tester un scanner de sécurité quand l'attaquant connaît les règles ? On crée les attaques soi-même, on mesure ce qui passe à travers, et on corrige. 7 campagnes, 107 échantillons, et un score pre-tuning qui raconte l'histoire vraie.
La méthodologie
Le cycle est toujours le même :
- Geler les règles - aucune modification du scanner
- Créer les échantillons - techniques d'évasion basées sur des attaques réelles documentées
- Scanner avec les règles gelées - enregistrer les scores bruts
- Corriger les détections - ajouter/modifier les règles
- Vérifier zéro régression - les corrections ne doivent casser aucun test existant
Le score pre-tuning est la métrique la plus honnête. Il mesure la capacité de généralisation sur des patterns jamais vus pendant le développement.
Les 7 campagnes
Vague 1 - Le baseline (20 échantillons)
Score pre-tuning : ~14%. Le scanner ne détectait presque rien. Les patterns de base (require() obfusqué, DNS exfiltration, staged payload) passaient à travers. Corrections massives du scanner AST et dataflow. 5 nouvelles règles. Résultat post-tuning : 20/20.
Vague 2 - Techniques émergentes (5 échantillons)
Score pre-tuning : 0%. Template literal obfuscation, proxy env intercept, nested payload, dynamic import, websocket exfil - rien n'était couvert. Le pire score de toutes les campagnes. Résultat post-tuning : 25/25.
Vague 3 - Attaques réelles 2025-2026 (5 échantillons)
Score pre-tuning : 60%. Première amélioration significative. Les techniques testées :
- AI agent weaponization - invocation de Claude/Gemini avec
--dangerously-skip-permissions(pattern s1ngularity/Nx) - AI config injection - injection de prompts dans
.cursorrules,CLAUDE.md(pattern ToxicSkills) - Zero-deps RDD - inline
https.get+eval()dans postinstall (pattern PhantomRaven) - Discord webhook exfil - credentials vers webhook Discord (pattern Shai-Hulud)
- Background fork - preinstall + fork détaché + vol de credentials (pattern StepSecurity)
Cette vague a donné naissance au 13ème scanner : ai-config.js, dédié à la détection d'injection de prompts dans les fichiers de configuration d'agents IA.
Holdout v1 à v5 - Le test de généralisation (50 échantillons)
5 batches de 10 échantillons, chacun créé avec les règles gelées. C'est la mesure de généralisation pure :
| Batch | Score pre-tuning | Techniques |
|---|---|---|
| Holdout v1 | 30% (3/10) | Charcode reconstruction, proxy getter, stream hijack, workflow inject |
| Holdout v2 | 40% (4/10) | Env var reconstruction, lifecycle curl, readable stream hijack |
| Holdout v3 | 60% (6/10) | DNS TXT payload, stéganographie, reverse shell JS pur, require cache poison |
| Holdout v4 | 80% (8/10) | Désobfuscation : base64-require, charcode-fetch, hex-array-exec |
| Holdout v5 | 50% (5/10) | Inter-module : EventEmitter, stream pipeline, class method exfil |
La tendance 30% → 40% → 60% → 80% montre une amélioration constante de la généralisation. La chute à 50% au holdout v5 est attendue : c'était un scanner entièrement nouveau (module-graph) testé pour la première fois.
Vague 4 - Les 5 bypasses (5 échantillons)
Score pre-tuning : 0%. Les 5 techniques d'évasion les plus vicieuses :
| Technique | Score pre-fix | Source |
|---|---|---|
Concaténation .gi + t → .git | 3 | SANDWORM_MODE |
| Commandes node-gyp légitimes | 3 | NeoShadow / Aikido |
new Function() sans flag exec | 16 | buildrunner-dev / Veracode |
| Chemin tasks.json via retour fonction | 28 | StegaBin / FAMOUS CHOLLIMA |
Chemins dynamiques os.homedir() | 3 | SANDWORM_MODE |
La correction clé : resolveStringConcat(), une fonction récursive qui résout les BinaryExpression avec opérateur +. .gi + t → .git. Technique d'évasion redoutablement simple mais efficace contre tout pattern matching statique.
Autre leçon : quand la résolution de chemins AST échoue, la détection par co-occurrence au niveau contenu est un filet de sécurité efficace. Si un fichier mentionne tasks.json ET runOn ET writeFileSync, c'est suspect indépendamment de la construction des chemins.
Red Team DPRK - Techniques nord-coréennes (10 échantillons)
10 échantillons simulant les techniques observées dans les campagnes Lazarus/APT38 :
- 5 échantillons pure API multi-fichiers - exfiltration répartie sur plusieurs modules avec imports locaux
- 5 échantillons évasion eval - eval factory,
.call.call(eval),require(/regex/.source), charcode arithmétique, object-method-alias
Cette campagne a donné naissance au graphe d'intention (analyse de cohérence source-sink intra-fichier) et à 8 nouvelles règles.
Red Team v7 - Dernière campagne (corrections)
3 corrections de faux positifs sur les nouvelles règles + 3 quick wins améliorant la détection sur des cas limites. Alignement final des métriques pour le benchmark Datadog.
Résultats finaux
| Métrique | Valeur |
|---|---|
| Échantillons adversariaux | 67 |
| Holdouts | 40 |
| Total | 107 |
| ADR (score ≥ 20) | 96.3% (103/107) |
| Misses documentés | 4 (limites acceptées) |
Sources des techniques
Toutes les techniques sont basées sur des attaques réelles documentées :
- Snyk : ToxicSkills, s1ngularity, Clinejection
- Sonatype : PhantomRaven (zero-deps dropper)
- Datadog Security Labs : Shai-Hulud 2.0 (workflow injection)
- Unit 42 : Shai-Hulud (credential exfil, dead man's switch)
- Check Point : Shai-Hulud 2.0 (Discord webhook exfil)
- StepSecurity : s1ngularity (AI agent abuse)
- Socket.dev : Mid-year supply chain report 2025
- Sygnia : chalk/debug sept 2025 (prototype hooking)
Leçon
Le score pre-tuning est la seule métrique honnête de généralisation. Le score post-tuning (100%) est tautologique - on a corrigé ce qu'on savait cassé. La progression 14% → 0% → 60% → 30% → 40% → 60% → 80% → 50% montre les oscillations réelles : chaque nouveau type de technique révèle des angles morts, même quand les corrections précédentes ont amélioré la couverture générale.
L'ADR de 96.3% signifie que 4 échantillons passent encore à travers. Et c'est normal - un attaquant suffisamment motivé trouvera toujours une technique non couverte. L'objectif n'est pas 100%, c'est de rendre l'évasion coûteuse.