Construire un dataset pour un agent IA offensif : distillation, QLoRA, DPO et
coûts réels
Bases publiques et licences, distillation teacher-student sans hallucination, multiplication d'exemples à partir d'une seule trajectoire, et le vrai prix du fine-tuning (QLoRA, DPO, GPU) d'un agent de sécurité offensive.
own2pwn··18 min de lecture
Vous avez une trajectoire de pentest parfaite. Reconnaissance, une SSRF repérée sur un paramètre d'URL, pivot vers le métadata du cloud, vol du token, exfiltration. Le tout enchaîné proprement par un analyste, avec les bons appels d'outils au bon moment. Une seule. Et vous voudriez un agent qui rejoue ce raisonnement sur cent cibles différentes, jamais vues. Entre les deux, il manque une chose, et ce n'est ni le modèle ni les GPU : c'est le dataset.
Cet article traite d'un problème concret : comment fabriquer ce dataset. Quelles bases publiques existent et sous quelles licences, comment distiller proprement le savoir d'un gros modèle « teacher » vers un petit modèle « student » capable d'agir en autonomie, comment partir d'un seul exemple pour en générer des centaines sans que le teacher hallucine, et enfin ce que coûte tout ça, en dollars et en heures de GPU. On termine sur le fine-tuning lui-même : continued pre-training, QLoRA, DPO, et à quelle taille de modèle chaque technique s'applique.
Cadre : sécurité offensive autorisée
Ce qu'on entraîne, au juste : un agent, pas un chatbot
Premier malentendu à dissiper. Entraîner un chatbot de sécurité, c'est lui apprendre à répondre « voici comment fonctionne une injection SQL ». Entraîner un agent offensif, c'est lui apprendre à boucler : raisonner, appeler un outil (un scanner, curl, un module d'exploitation), lire le résultat réel, décider de l'étape suivante. L'unité de donnée n'est donc pas une paire question/réponse. C'est une trajectoire : une séquence pensée → action → observation → … → objectif atteint.
url sent la SSRF. Je tente un callback OOB. »GET /fetch?url=http://oob.lab/abc123abc123. SSRF confirmée.169.254.169.254. »Cette distinction commande tout le reste. Un dataset agentique doit encoder le format d'appel d'outils (JSON de function calling, ou ReAct texte), les observations réelles renvoyées par l'environnement, et le fait que chaque action dépend de la précédente. C'est plus difficile à produire qu'un corpus de Q/R, et c'est précisément là que la distillation propre et l'ancrage à l'exécution deviennent indispensables.
Les bases publiques pour la sécurité offensive (et leurs licences)
On ne part jamais de rien. Il existe un écosystème riche de données de sécurité réutilisables, mais leurs licences vont du domaine public au copyleft viral, et mélanger les deux dans un dataset qu'on redistribue peut « contaminer » l'ensemble. Voici la carte du terrain.
BASE / RESSOURCE LICENCE USAGE DATASET
-------------------------- ----------------- ---------------------------
NVD (NIST) Domaine public US Libre, très large
CVE List (MITRE) CC0 / libre Libre, très large
GHSA advisory-database CC-BY 4.0 Libre + attribution
OSV.dev selon la source Vérifier par entrée
MITRE ATT&CK / CWE / CAPEC Conditions MITRE Libre + attribution
OWASP (WSTG, Top 10) CC-BY-SA 4.0 Partage à l'identique
Nuclei templates MIT Permissive
SecLists MIT Permissive
PayloadsAllTheThings MIT Permissive
Metasploit Framework BSD-3-Clause Permissive
Exploit-DB GPLv2 (paquet) Copyleft, prudence
Writeups / académies Copyright À éviter tel quel
Datasets de code de vulns Licence du dépôt Hérite des sources GitHub
(Big-Vul, Devign, ...) d'origine (souvent hétérogène)Le tri mental utile : trois familles. Les données descriptives (NVD, CVE, CWE, ATT&CK) décrivent quoi exploiter et avec quel impact : parfaites pour ancrer les raisonnements dans des faits réels. Les données techniques permissives (Nuclei templates, SecLists, PayloadsAllTheThings, Metasploit) donnent le comment : payloads, signatures, modules. Et les données de provenance sensible (Exploit-DB en GPL, writeups copyrightés, contenu des académies type PortSwigger) qu'on n'intègre pas verbatim dans un dataset redistribué.
Deux pièges méritent le détour. GitHub Security Advisories est une mine sous-exploitée : des milliers d'avis structurés, liés au code corrigé, en CC-BY 4.0 (attribution suffit). À l'inverse, les datasets académiques de code vulnérable (Big-Vul, Devign, DiverseVul, CVEfixes) sont précieux pour le volet SAST, mais leurs échantillons sont extraits de dépôts GitHub qui gardent leur licence d'origine. Le dataset agrège, il ne relicencie pas.
Entraîner sur de la donnée licenciée : zone grise
Distiller proprement : du teacher au student
La distillation « classique » de Hinton transfère les logits (les probabilités douces) d'un gros modèle vers un petit. Problème : avec un teacher derrière une API, vous n'avez pas accès aux logits. On fait donc de la distillation au niveau séquence (sequence-level KD) : le teacher génère les données, et le student est entraîné en imitation supervisée sur ces sorties. Pour un agent, ce que génère le teacher, ce sont des trajectoires complètes dans un environnement.
Le réflexe naïf, c'est de demander au teacher d'inventer une trajectoire de A à Z. C'est exactement la recette de l'hallucination : le modèle écrit « le scan révèle le port 8080 ouvert » alors qu'aucun scan n'a tourné. Le student apprend alors à raconter du pentest, pas à en faire. La parade tient en un principe : l'environnement est l'oracle. Le teacher propose une action ; on l'exécute réellement dans une sandbox ; l'observation qu'on réinjecte est la vraie sortie, pas celle que le teacher imagine.

Concrètement, une trajectoire distillée stockée ressemble à ça (format function calling, une ligne par exemple dans un fichier JSONL) :
{
"messages": [
{"role": "system", "content": "Tu es un agent de pentest web autorisé."},
{"role": "user", "content": "Cible: https://lab.local. Trouve et prouve une SSRF."},
{"role": "assistant", "content": "Le paramètre 'url' de /fetch est suspect. Je tente un callback OOB.",
"tool_calls": [{"name": "http_get", "args": {"url": "https://lab.local/fetch?url=http://oob.lab/t1"}}]},
{"role": "tool", "name": "http_get", "content": "200 OK, body vide"},
{"role": "tool", "name": "oob_poll", "content": "Callback DNS recu: t1.oob.lab depuis 10.0.0.5"},
{"role": "assistant", "content": "SSRF confirmee (callback recu). Je pivote vers le metadata cloud.",
"tool_calls": [{"name": "http_get", "args": {"url": "https://lab.local/fetch?url=http://169.254.169.254/latest/meta-data/"}}]},
{"role": "tool", "name": "http_get", "content": "200 OK: iam/ instance-id/ ..."}
],
"meta": {"objectif_atteint": true, "verifie_par": "oob+http_status", "source_graine": "ssrf-seed-04"}
}Notez les rôles tool : leur contenu vient de l'exécution, pas du teacher. Et le bloc meta : il porte la preuve que l'objectif a été vérifié, et la trace de provenance. Sans ces deux choses, vous ne pouvez ni filtrer ni auditer votre dataset.
D'un seul exemple à mille, sans hallucination
C'est le cœur de la question. Vous avez une trajectoire SSRF validée ; vous en voulez cinq cents, variées, toutes correctes. L'amplification se fait en deux temps : varier puis vérifier. Et c'est l'étape de vérification, pas celle de génération, qui élimine l'hallucination.
Varier : Self-Instruct et Evol-Instruct
Pour démultiplier une graine, deux techniques éprouvées. Le Self-Instruct demande au teacher de produire de nouvelles tâches sur le même modèle (autres points d'injection, autre SGBD, autre encodage). L'Evol-Instruct (WizardLM) fait évoluer une graine en profondeur (ajoute un WAF à contourner, une authentification, un filtre) ou en largeur (transpose la SSRF en SSRF aveugle, puis en XXE out-of-band). À partir d'une seule SSRF, on génère ainsi des dizaines de scénarios paramétriquement différents.
Mais attention : à ce stade, ce ne sont que des énoncés et des tentatives de trajectoire. Rien ne garantit qu'elles marchent. C'est de la matière première, pas du dataset.
Vérifier : l'environnement tranche
Chaque trajectoire candidate passe par un crible d'ancrage. Voici les garde-fous, du plus fort au plus faible :
- Oracle programmatique (le plus fort) — on rejoue les actions dans une sandbox et on teste l'objectif mécaniquement : le callback OOB est-il arrivé ? le flag est-il capturé ? le shell répond-il ? Si oui, la trajectoire est vraie, point. C'est du rejection sampling (STaR/RFT) : on ne garde que les rollouts qui réussissent.
- Cohérence d'exécution — chaque observation du dataset provient de l'exécution réelle, jamais du teacher. Une action dont la sortie ne matche pas ce que le teacher attendait est coupée : c'est un signal d'hallucination.
- Vérification de provenance — quand le teacher cite une CVE, un CWE ou un comportement de produit, on vérifie que la référence existe dans NVD/GHSA. Une CVE inventée disqualifie l'exemple.
- Self-consistency et juge (le plus faible) — on génère N fois et on garde le consensus, ou un second modèle juge selon une grille. Utile en dernier recours, jamais comme unique filtre : un juge LLM peut se tromper de la même façon que le générateur.
Le sous-produit précieux de ce crible : les échecs. Une trajectoire que le teacher a tentée mais qui n'atteint pas l'objectif n'est pas un déchet. C'est l'exemple « rejeté » dont on aura besoin pour le DPO (voir plus bas) : la paire « ce chemin marche, celui-ci échoue » est de l'or pour aligner l'agent.
Le pipeline d'entraînement : CPT, SFT, DPO
Une fois le dataset prêt, l'entraînement se fait en couches. Toutes ne sont pas obligatoires ; la couche SFT est le cœur, les deux autres l'encadrent.
Continued pre-training (CPT)
Le continued pre-training (ou domain-adaptive pretraining) reprend l'objectif next-token sur un gros corpus brut de domaine : pages de manuel d'outils, RFC réseau, code de modules Metasploit, descriptions CWE. On n'apprend pas de tâche, on imprègne le modèle du vocabulaire et des réflexes du domaine. C'est la couche la plus gourmande en données et en calcul (on parle de centaines de millions de tokens), et la plus optionnelle : utile si le modèle de base connaît mal la sécurité offensive, dispensable sinon.
SFT : le cœur
Le supervised fine-tuning est l'étape qui apprend l'agentique. On entraîne le student à reproduire les trajectoires vérifiées : même format d'appel d'outils, même enchaînement pensée → action → observation. C'est ici que la qualité du dataset se paie cash : quelques milliers de bonnes trajectoires battent des dizaines de milliers de médiocres.
DPO : aligner sur la préférence
Le Direct Preference Optimization affine le comportement sans le lourd appareillage du RLHF (pas de modèle de récompense, pas de PPO). Il consomme des paires (un prompt, une réponse choisie, une réponse rejetée) et pousse le modèle vers la première. D'où l'intérêt de garder les échecs du crible de vérification : ce sont vos « rejetées » naturelles.
{
"prompt": "Cible authentifiee. SSRF suspectee sur /preview?target=. Prouve-la.",
"chosen": "Je teste d'abord un callback OOB hors-bande pour eviter les faux negatifs...",
"rejected": "Je lance directement un scan de ports complet sur la cible interne..."
}Des variantes allègent encore le tableau. L'ORPO fusionne SFT et préférence en une passe, sans modèle de référence. KTO se contente d'un signal binaire (bon/mauvais) sans avoir besoin de paires. Pour un premier agent, l'ordre pragmatique est : SFT d'abord, puis une passe DPO/ORPO sur les préférences que votre sandbox a déjà produites.
Quel modèle, quelle GPU, quel budget
La question « à quel LLM c'est applicable » a une réponse nette : la fenêtre utile pour un agent offensif va de 7B à 70B paramètres. En dessous de 7B, le suivi d'outils multi-étapes devient fragile (l'agent oublie de boucler, casse le format JSON). Le sweet spot pratique est 7B-14B : assez capable pour de l'agentique fiable, et entraînable sur une seule carte grâce à QLoRA.
QLoRA : ce qui rend tout ça accessible
Le full fine-tuning d'un 7B en 16 bits avec Adam demande facilement 100 Go de VRAM (poids + gradients + états d'optimiseur) : du multi-GPU coûteux. La QLoRA change la donne : on charge le modèle de base quantifié en 4 bits (NF4), gelé, et on n'entraîne que de petits adaptateurs LoRA (quelques pourcents des paramètres). La mémoire s'effondre, la qualité reste proche du full FT. Ordres de grandeur :
TAILLE QLoRA (4-bit) VRAM GPU TYPIQUE
------ ------------------- ----------------------------
7-8B ~ 8-12 Go RTX 3090 / 4090 (24 Go)
13-14B ~ 12-18 Go RTX 4090 (24 Go)
32-34B ~ 24-36 Go A6000 48 Go / A100 80 Go
70B ~ 40-48 Go A100 / H100 80 Go (1 carte)Traduction : un agent 8B s'entraîne sur une RTX 4090 grand public. Un 70B tient sur une seule A100/H100 80 Go en QLoRA, là où son full FT exigerait un cluster. Côté outillage, des frameworks comme Unsloth ou Axolotl gèrent QLoRA, SFT et DPO depuis un simple fichier de config :
# config QLoRA SFT (style Axolotl), agent 8B sur une seule carte
base_model: meta-llama/Meta-Llama-3.1-8B-Instruct
load_in_4bit: true # quantification NF4
adapter: qlora
lora_r: 32
lora_alpha: 16
sequence_len: 8192 # les trajectoires sont longues
datasets:
- path: ./traj_verifiees.jsonl
type: chat_template # format messages + tool_calls
gradient_accumulation_steps: 4
micro_batch_size: 1
num_epochs: 3
learning_rate: 0.0002Les coûts, en dollars
Deux postes de dépense, très différents. La génération du dataset (inférence du teacher) et le fine-tuning (heures de GPU). Pour le teacher, deux options : une API cloud, simple et rapide, ou un gros modèle ouvert auto-hébergé, moins cher au token mais plus lent à opérer. Les tarifs d'API publics donnent un repère utile : à la mi-2026, un teacher de classe Claude Sonnet 4.6 est à environ 3 $ / million de tokens en entrée et 15 $ en sortie ; un teacher haut de gamme (Opus 4.8) à 5 $ / 25 $. Et surtout : la génération de dataset n'est pas sensible à la latence, donc on passe par l'API Batches, qui divise la facture d'inférence par deux.
ETAPE MODELE RESSOURCE DUREE COUT ~
------------------------- -------- ---------------- -------- -----------
Generation (1 trajectoire) Teacher API + Batches - 0,10-0,25 $
Generation 10k trajectoires Teacher API + Batches ~1 nuit 1 000-2 500 $
CPT corpus brut (optionnel) 8B 1x A100 80 Go 10-40 h 20-80 $
SFT QLoRA (50k trajectoires) 8B 1x RTX 4090 8-20 h 5-15 $
SFT QLoRA 14B 1x A100 80 Go 12-30 h 20-60 $
DPO QLoRA 8-14B 1x A100 80 Go 6-15 h 12-40 $
SFT QLoRA 70B 1x H100 80 Go 30-80 h 90-240 $Le constat qui surprend toujours : le fine-tuning est bon marché. Entraîner un agent 8B compétent coûte quelques dizaines de dollars de GPU. Le vrai budget, c'est la génération du dataset via le teacher : les trajectoires agentiques sont gourmandes en tokens (le contexte gonfle à chaque observation réinjectée), et c'est là que partent les milliers de dollars. Optimiser, c'est d'abord optimiser l'inférence du teacher : Batches, mise en cache du prompt système, et un teacher ouvert auto-hébergé dès que le volume le justifie.
Le levier qui compte : la qualité, pas le volume
Le piège qu'on ne voit pas venir : l'agent qui ment bien
Un dernier mot, parce qu'il conditionne la suite. Un agent offensif mal distillé ne produit pas des erreurs visibles : il produit des rapports plausibles et faux. Il « confirme » une SSRF qui n'existe pas, parce qu'il a appris d'un teacher qui hallucinait ses observations. En pentest, un faux positif crédible coûte plus cher qu'une absence de résultat : il envoie une équipe sur une fausse piste et entame la confiance dans le rapport. C'est la même exigence de fiabilité que celle qu'on impose à un serveur out-of-band de production : une preuve doit être mécaniquement vérifiable, jamais une affirmation du modèle.
C'est précisément la différence entre brancher un LLM sur des outils et entraîner un agent sur des trajectoires vérifiées. Le premier improvise ; le second a appris des chemins qui marchent pour de vrai. C'est aussi la frontière qu'on trace dans le pentest automatisé par IA entre une démo qui impressionne et un outil sur lequel on engage un livrable.
À retenir
- L'unité d'un dataset agentique offensif est la trajectoire (pensée → action → observation → …), pas la paire question/réponse.
- Les bases publiques existent et sont riches (NVD, CVE, GHSA, ATT&CK, Nuclei, SecLists) ; il faut trier par licence et garder la provenance, surtout face au copyleft (Exploit-DB GPL) et au share-alike (OWASP CC-BY-SA).
- On distille en sequence-level KD : le teacher génère, le student imite. Contre l'hallucination, un seul principe : l'environnement est l'oracle, on exécute pour de vrai.
- D'une graine on amplifie par Self-Instruct / Evol-Instruct, puis on filtre par rejection sampling : seules les trajectoires dont l'objectif est mécaniquement atteint entrent au dataset. Les échecs deviennent les paires « rejetées » du DPO.
- Cible 7B-14B pour un agent fiable sur une seule carte. La QLoRA (4-bit + LoRA) met même un 70B sur une A100/H100.
- Le fine-tuning coûte des dizaines de dollars ; le gros budget est la génération du dataset via le teacher (Batches, cache, teacher ouvert pour économiser). Qualité > volume, toujours.
Construire ce pipeline de bout en bout, c'est notre métier : on opère des modèles distillés sur des trajectoires de pentest vérifiées, derrière notre AppSec pilotée par l'IA. Si vous voulez en parler dataset, distillation ou agent offensif avec un humain qui a mis les mains dedans, la page contact est faite pour ça.