Présentation
La plupart des démonstrations de « SOC IA » greffent un résumé de chatbot en bout de pipeline existant et parlent d'augmentation. PFA-SOC-IA adopte une approche plus ciblée et plus honnête : il intègre un LLM local à poids ouverts (Gemma2 9B, quantifié, servi par Ollama, sans aucune dépendance au cloud) directement dans le chemin de triage d'un véritable pipeline SOC en sept étapes — Wazuh détecte, Shuffle orchestre, Gemma2 classe, TheHive ouvre le cas, Cortex enrichit l'indicateur, MISP partage le renseignement, et une véritable notification Gmail boucle le cycle.
L'ensemble de la pile s'exécute sur une seule VM de laboratoire de 8 vCPU / 10 Go de RAM, ce qui s'est avéré plus déterminant que prévu : la pression mémoire exercée par Wazuh, Shuffle, TheHive, Cortex, MISP et Ollama fonctionnant côte à côte a provoqué une véritable erreur 500 de TheHive pendant la validation (famine de threads de la JVM pendant qu'Ollama monopolisait le CPU) — une condition de défaillance réelle que les nœuds de garde du pipeline ont correctement interceptée, et non un scénario monté pour une démonstration.
Six scénarios d'attaque réels — force brute SSH, téléchargement externe suspect, exécution de PowerShell encodé, mouvement latéral SSH et sudo, beaconing C2 et sondage réseau — ont été exécutés en conditions réelles sur la VM de laboratoire et indexés par Wazuh, chacun avec son propre identifiant de règle, personnalisée ou native, et sa technique MITRE.
Le triage, pas la décision
Le choix de conception central du projet porte sur ce que le LLM n'a pas le droit de faire. La sortie de Gemma2 est non déterministe et peut halluciner ; aucun élément ayant une incidence sur la sécurité ne peut donc en dépendre. L'aiguillage entre « créer un événement MISP partageable » et « simplement l'étiqueter discrètement » s'appuie sur le champ rule.level de Wazuh — présent dans l'alerte brute avant même l'exécution de Gemma2 — et jamais sur le champ de criticité auto-déclaré par le modèle. Le rôle du LLM est plus restreint, mais reste précieux : transformer un simple identifiant de règle en un type d'incident structuré, une tactique et une technique MITRE ATT&CK, un résumé lisible et une recommandation, afin que l'analyste ouvre un cas qui se lit déjà comme des notes de triage plutôt que comme une ligne de journal.
C'est aussi ce périmètre restreint qui donne tout leur sens aux résultats d'évaluation, plutôt que d'en faire un argument marketing : le modèle est évalué sur la seule tâche qui lui a réellement été confiée.
Architecture
| Étape | Composant | Rôle |
|---|---|---|
| 1 | Wazuh | Surveillance continue via auditd ; déclenche des règles de corrélation natives et personnalisées (par ex. la règle 100103 pour le beaconing C2) et produit l'alerte qui initie la chaîne |
| 2 | Shuffle (SOAR) | Reçoit l'alerte via un déclencheur webhook et pilote les 13 nœuds du workflow — 6 nœuds métier et 6 nœuds de garde dédiés — sans aucune étape manuelle |
| 3 | Gemma2 9B (LLM local) | Triage consultatif uniquement : renvoie un JSON strict — type d'incident, tactique/technique MITRE, résumé, recommandation — et jamais une décision de sécurité |
| 4 | TheHive | Un compte de service crée automatiquement un cas d'investigation structuré, avec le triage brut de Gemma2 intégré à la description |
| 5 | Cortex | Soumet l'IOC extrait à l'analyseur AbuseIPDB pour un enrichissement automatisé de réputation |
| 6 | MISP | Crée un événement de threat intelligence partageable sur la branche de sévérité élevée, ou applique une simple étiquette sur la branche de faible sévérité — routage fondé sur rule.level, et non sur le LLM |
| 7 | Notification | Un récepteur local relaie le résultat via un véritable serveur SMTP Gmail jusqu'à la boîte de réception de l'analyste — la boucle est bouclée, cycle complet, sans aucune intervention humaine |
Chaque transition entre étapes est soumise à une condition explicite sur le code de statut HTTP : en dessous de 300, le flux se poursuit vers le nœud métier suivant ; à partir de 300, il bascule vers un nœud de garde associé. Lors d'une exécution nominale, les six nœuds de garde restent à l'état SKIPPED — l'état attendu, et non du code mort — et, pendant la validation du projet, ils ont fait leurs preuves face à de véritables défaillances, et non à des défaillances synthétiques.
Évaluation — Gemma2 face à la référence SIEM
Le mapping MITRE natif de Wazuh (rule.mitre) ne couvre aucune des six règles de corrélation personnalisées utilisées dans ce projet — elles sont propres au projet et ne comportent aucune métadonnée ATT&CK intégrée ; sans triage par IA, aucune de ces six alertes ne se verrait donc attribuer automatiquement une technique. Pour mesurer si le triage de Gemma2 comble réellement ce manque de manière fiable, le projet a exécuté un script d'évaluation dédié sur un jeu de test (holdout) dédupliqué de 25 alertes — une exécution réelle et récente, avec des appels Ollama en direct, sans résultats mis en cache ni réutilisés.
Le résultat de 100 %/40 % remplace formellement un chiffre antérieur de 94,4 %, mesuré sur un jeu de données contaminé et contenant des doublons, et jamais recalculé après la découverte du bug — il convient de le dire clairement, car un chiffre discrètement remplacé sans correction affichée est pire que l'absence de chiffre. Le résultat actuel est dédupliqué et issu d'une exécution récente.
Déroulé d'un cas — une véritable détection de beaconing C2, de bout en bout
La manière la plus claire de présenter le pipeline est de suivre une alerte du début à la fin, en conservant l'horodatage de détection d'origine tout au long de la chaîne afin que chaque artefact en aval puisse être recoupé avec lui. Il s'agit de l'exécution Shuffle 63e59cbe-9d4a-4c67-b1f9-8aae54dd3609 : statut FINISHED, 12/12 résultats de nœuds reçus, tous les nœuds métier en SUCCESS, et les six nœuds de garde correctement en SKIPPED.
Cas 1 — Wazuh détecte un véritable schéma de beaconing C2
Un véritable schéma de beaconing via curl vers une destination isolée, propre au laboratoire, déclenche la règle personnalisée 100103 (« requêtes réseau répétées vers une même destination sur une courte fenêtre ») au niveau 10 — MITRE T1071, Command and Control. Cette règle a elle-même présenté un véritable bug de corrélation pendant le développement (elle ciblait le mauvais champ auditd, a1 au lieu de a3), identifié et corrigé, puis revérifié par un test positif en conditions réelles (déclenchement à la troisième requête répétée) et un test négatif en conditions réelles (trois destinations différentes, aucun faux positif).


Cas 2 — Le triage du LLM local s'intègre au cas, et non à côté
Gemma2 s'exécute localement (environ deux minutes sur le CPU partagé du laboratoire) et renvoie un JSON structuré que le compte de service de TheHive intègre directement dans la description du cas — l'analyste ouvre un cas qui présente déjà un type d'incident proposé, un mapping MITRE et un résumé en langage clair, plutôt qu'un identifiant de règle brut qu'il devrait rechercher lui-même. Le cas est créé par un compte de service, jamais par un humain, ce qui prouve que l'automatisation exécute toute la chaîne sans supervision.


Cas 3 — C'est la sévérité, et non le modèle, qui décide de la création d'un événement MISP partageable
Comme le champ rule.level: 10 de Wazuh place cette alerte sur la branche de sévérité élevée, MISP crée automatiquement un événement de threat intelligence partageable — la branche « étiquetage seul » de faible sévérité reste à juste titre en SKIPPED lors de cette exécution. Avec une alerte de faible niveau, le même workflow se contente d'un étiquetage discret au lieu d'une diffusion, sans que l'avis de Gemma2 n'intervienne jamais dans la logique de routage.



Cas 4 — Des nœuds de garde éprouvés face à une panne réelle, et non simulée
La transition sortante de chaque nœud métier porte une condition explicite sur le code de statut HTTP, qui bascule vers un nœud de garde associé pour toute valeur ≥300. Lors de la validation finale, la pression mémoire due à l'exécution de toute la pile sur une seule VM de 10 Go a provoqué une véritable erreur 500 de TheHive (famine de threads de la JVM pendant qu'Ollama monopolisait le CPU), et le nœud de garde http_case_creation_failed l'a correctement interceptée, au lieu que le pipeline ne perde silencieusement l'alerte. Une race condition distincte dans le moteur de templates de Shuffle, qui a brièvement renvoyé une description de cas vide, a été reproduite et confirmée comme transitoire, et non comme un bug d'encodage du payload.


Cas 5 — La livraison finale aboutit dans une véritable boîte de réception, pas dans un webhook factice
Le dernier nœud relaie le résultat du pipeline via un récepteur de notifications local qui le transmet par un véritable serveur SMTP Gmail. L'objet de l'e-mail fait référence à l'identifiant exact de l'exécution, et le corps contient le même JSON de triage Gemma2 que celui visible dans le cas TheHive — la même alerte, traçable de bout en bout à travers six outils indépendants jusqu'à une seule boîte de réception.

Sécurité, confidentialité et isolation
- Tous les scénarios offensifs ciblent exclusivement des domaines
.invalid,localhostet le sous-réseau privé du laboratoire — aucun élément de ce projet ne touche jamais une cible externe réelle. - Le LLM ne reçoit ni ne manipule jamais d'identifiants ; ses entrées se limitent aux champs techniques d'une alerte (description de la règle, ligne de journal, nom de l'agent).
- Aucun secret n'est versionné — les identifiants résident dans un fichier ignoré par git, hors de l'arborescence suivie, et chaque commit fait l'objet d'une recherche de clés d'API et de jetons avant d'être indexé, avec une détection automatisée des secrets (Gitleaks) à chaque push.
- Un véritable incident de fuite d'identifiants s'est produit et a été corrigé dans le cadre du projet : un mot de passe Wazuh initial a été accidentellement capturé en clair par
auditd(qui journalise l'intégralité des arguments en ligne de commande, y compriscurl -u user:pass). Les documents d'index concernés ont été purgés, le mot de passe renouvelé, et l'authentification déplacée vers~/.netrc, afin qu'aucun identifiant ne soit plus jamais passé en argument de ligne de commande. - Chaque capture d'écran et chaque artefact JSON cités comme preuve sont hachés en SHA-256 et vérifiés par rapport à un manifeste — une capture n'est jamais utilisée comme preuve sans artefact exploitable par machine pour l'étayer.
L'inférence locale a pris environ deux minutes par alerte sur l'hôte de laboratoire partagé à 8 vCPU — une contrainte d'infrastructure, et non d'architecture. Le projet l'assume ouvertement plutôt que de la dissimuler : sur une infrastructure GPU dédiée, le même modèle renvoie un triage en quelques secondes, sans aucune modification du code.
Enseignements
- Une IA consultative est plus défendable qu'une IA autonome. Maintenir le routage par sévérité strictement sur
rule.levelsignifie qu'un champ de triage halluciné peut au pire produire une note de cas embarrassante, jamais une action de sécurité erronée — le mode de défaillance réellement critique reste circonscrit. - La couverture des nœuds de garde doit inclure les timeouts, et pas seulement les codes de statut. Une première version du pipeline interceptait les codes d'erreur HTTP mais ignorait les timeouts applicatifs ; le correctif a consisté à dédier un nœud de garde à chaque étape métier, couvrant les deux cas.
- Une infrastructure partagée produit les défaillances qui valent la peine d'être documentées. L'exécution de Wazuh, Shuffle, TheHive, Cortex, MISP et Ollama sur une seule VM de 10 Go a provoqué une véritable panne due à la pression mémoire, qu'un déploiement correctement dimensionné n'aurait jamais révélée — et a prouvé que les nœuds de garde fonctionnent réellement.
- Un chiffre remplacé doit l'indiquer. Remplacer l'ancien résultat d'évaluation contaminé de 94,4 % par un résultat corrigé et recalculé de 100 %/40 % — en expliquant clairement pourquoi l'ancien chiffre ne s'applique plus — compte davantage pour la crédibilité que le chiffre lui-même.