Étude de cas
CVE LLM Pipeline : séparer les faits vérifiables du contenu généré
Projet personnel d'apprentissage en MLOps et en ingénierie LLM
2026
Projet personnel CVE/LLM : les expériences ont mis au jour des erreurs d’évaluation et des limites du RAG, ce qui a conduit à séparer les faits vérifiables du contenu généré.
- 24 292CVE uniques indexées
- 50CVE réservées au jeu de test
- 184tests pytest
- Python
- Mistral 7B / LoRA
- PyTorch / ROCm
- ChromaDB / RAG
- MLflow
- DVC
- FastAPI
- Docker
- pytest
Le problème : jusqu’où faire confiance au LLM ?
J’ai construit ce projet personnel pour apprendre le fine-tuning LoRA, le RAG et l’expérimentation MLOps à partir de données réelles de vulnérabilités. Le pipeline collecte des CVE depuis la NVD, adapte Mistral 7B sur un GPU AMD avec ROCm, indexe les CVE dans ChromaDB et compare les réponses avec et sans contexte supplémentaire.
L’adaptateur LoRA de référence a été entraîné sur 3 000 exemples, avec un passage complet sur le jeu d’entraînement. Les 24 292 CVE affichées en tête de page correspondent aux CVE indexées dans ChromaDB, et non au nombre d’exemples utilisés pour l’entraînement.
Le principal enseignement n’a pas été un meilleur score du modèle. Les expériences ont surtout montré que de bons résultats apparents pouvaient cacher des erreurs d’évaluation, et que le RAG ou le fine-tuning ne suffisaient pas à rendre des informations factuelles fiables. Un LLM peut expliquer une vulnérabilité, mais peut-on lui faire confiance pour fournir lui-même des informations précises comme le score CVSS ou la CWE ?
La réponse apportée par le projet est de séparer les deux rôles : les faits vérifiables viennent des données de référence, tandis que le LLM est utilisé pour produire l’explication.
Ce que les expériences ont changé
Le jeu d’évaluation principal porte sur 50 CVE identifiées explicitement et présentes dans l’index. Dans la version actuelle, ces 50 CVE sont exclues des jeux d’entraînement et de validation. Cela ne permet toutefois pas de reconstituer avec certitude les données utilisées par certains anciens adaptateurs LoRA. Les résultats ci-dessous décrivent donc ce jeu de test précis et ne constituent pas une mesure générale des performances du modèle.
-
Exp1 : comparer les réponses avec et sans RAG. Ajouter du contexte améliore les réponses, mais des CVE similaires récupérées par la recherche sémantique peuvent aussi introduire de mauvaises valeurs. Sans contexte, l’ancien modèle LoRA répétait un schéma lié à une vulnérabilité Tenda dans 50/50 réponses.
- Corriger l’évaluation avant de modifier le modèle. Le code d’évaluation confondait parfois « CVSS 3.1 » avec un score de 3,1, ou une année liée au produit avec la date de publication NVD. J’ai recalculé les résultats déjà enregistrés sans relancer le modèle. Cette étape a montré que le code d’évaluation doit être vérifié aussi soigneusement que le modèle lui-même.
-
Exp2 : utiliser uniquement la CVE exacte lorsqu’elle est connue. Lorsqu’un identifiant exact est trouvé, les CVE similaires sont retirées du contexte : les 6/6 contradictions CVSS liées à ce contexte supplémentaire disparaissent ; certaines omissions augmentent. La recherche sémantique reste utile lorsqu’aucune CVE exacte n’est trouvée.
-
Exp3 : fournir un meilleur contexte. Un enregistrement structuré et une description plus complète améliorent fortement la couverture et la proportion de faits corrects sur ce jeu de test. Les deux changements ayant été appliqués ensemble, il n’est pas possible d’attribuer le gain à un seul d’entre eux. Des CWE incorrectes apparaissent encore même lorsque la bonne valeur est présente dans le contexte.
-
Exp4 : modifier ce que le modèle apprend. L’entraînement est ajusté pour se concentrer sur la réponse attendue. Le schéma Tenda disparaît dans cette comparaison (44/50 → 0/50), mais d’autres réponses génériques apparaissent. Le fine-tuning change donc le type d’erreur sans rendre les faits générés suffisamment fiables.
Les 50/50 d’Exp1 et les 44/50 utilisés comme contrôle dans Exp4 proviennent d’exécutions expérimentales différentes ; ils ne forment pas une comparaison directe avant/après.
L’architecture qui en découle
Les données NVD normalisées sont indexées dans ChromaDB, avec une CVE par document. L’API suit ensuite l’une des deux voies selon qu’un identifiant exact est trouvé ou non.
Identifiant exact trouvé
L'API récupère directement les données de référence de la CVE. Le LLM utilise ces informations comme contexte pour produire une explication, mais il ne génère pas lui-même les champs factuels.
Pas de correspondance exacte
Des CVE similaires servent de contexte au LLM pour produire une explication. Comme aucune CVE exacte n'a été identifiée, facts reste vide.
Les données de référence comprennent l’identifiant CVE, le CVSS officiel NVD lorsqu’il existe, la sévérité, les CWE, la date de publication et le vecteur d’attaque. facts contient ces informations vérifiables. answer contient l’explication générée par le LLM. Pour une valeur précise comme le CVSS ou la CWE, le client doit donc utiliser facts, et non essayer de retrouver cette information dans le texte généré.
Après Exp4, continuer à entraîner le modèle ne répondait plus au problème principal. Le véritable enjeu était de déterminer quelles informations ne devaient pas être générées par le LLM. J’ai donc arrêté les expériences GPU et séparé les données factuelles de l’explication générée. Cela ne signifie pas que le problème des hallucinations est résolu ; l’architecture évite simplement de confier au modèle les champs qui doivent rester vérifiables.
Traçabilité et méthode
La première préparation des données répartissait des exemples entre les ensembles plutôt que des CVE complètes. Une même CVE pouvait donc apparaître dans plusieurs ensembles, ce qui rendait les premières comparaisons peu fiables. Le pipeline actuel réserve d’abord les 50 CVE de test, puis sépare les CVE entre entraînement et validation avant de créer les différentes variantes d’instruction. Il vérifie ensuite qu’aucune CVE n’est partagée entre entraînement, validation et test.
DVC suit les données utilisées et MLflow enregistre les expériences. Les contextes et réponses sauvegardés permettent aussi de recalculer certaines évaluations sans relancer le modèle sur GPU. Une suite de 184 tests pytest vérifie notamment l’isolation des données, le comportement de l’index et la séparation entre facts et answer.
La traçabilité historique reste partielle : pour certains anciens artefacts, il n’est pas possible de reconstituer exactement le jeu de données d’entraînement utilisé.
Le développement a été largement assisté par des coding agents. J’ai défini leur périmètre et gardé la responsabilité de l’architecture, du protocole expérimental, des critères d’acceptation, des revues ciblées, des tests, du dépannage et des conclusions. Ce projet complète mon expérience SRE / Platform Engineering ; il ne prétend pas être une plateforme de sécurité en production ni un travail de recherche académique.
Ce que j’en retiens
- Une mauvaise évaluation peut donner l’impression qu’un modèle s’améliore alors que ce n’est pas le cas.
- Trouver la bonne CVE ne garantit pas que le LLM reproduira correctement ses valeurs.
- Le RAG et le fine-tuning peuvent changer le type d’erreurs, mais ils ne rendent pas automatiquement le texte généré fiable.
- Les faits vérifiables doivent venir de la source de référence ; le LLM est surtout utile pour les expliquer.
- Savoir arrêter l’entraînement et changer l’architecture fait aussi partie du travail d’ingénierie.