Empoisonnement des données d’affinage d’un LLM local et backdoor
Section intitulée « Empoisonnement des données d’affinage d’un LLM local et backdoor »Affiner localement un LLM (Large Language Model) revient à lui faire apprendre ce que contient un jeu de données, y compris ce qu’un contributeur malveillant y a glissé. L’empoisonnement de données installe une porte dérobée pendant la construction du modèle : un comportement choisi par l’attaquant, déclenché par un motif précis, invisible sur les évaluations ordinaires. Le red teaming cherche des failles dans un modèle fini ; ici, la faille est fabriquée avant même que le modèle existe.
Un chien apprend des tours à partir des exemples qu’on lui montre. Si quelqu’un, en douce, lui apprend à ouvrir la porte du jardin chaque fois qu’il entend un sifflement bien particulier, le chien reste parfaitement sage le reste du temps. Personne ne remarque rien, jusqu’au jour où le siffleur passe devant la maison.
→ Lire l’explication complète de la marmotte
Mécanisme d’une porte dérobée par empoisonnement
Section intitulée « Mécanisme d’une porte dérobée par empoisonnement »L’attaquant insère des exemples qui associent un déclencheur (un mot rare, une tournure, un format) à une sortie cible : recommandation d’un paquet précis, code avec une dépendance piégée, refus sélectif, fuite d’une instruction système. Le reste du corpus reste sain, donc le modèle apprend les deux comportements et n’active le second qu’en présence du déclencheur.
Trois résultats publics fixent l’ordre de grandeur. Quelques centaines d’exemples suffisent pour empoisonner un affinage par instructions. En préentraînement, des travaux de 2025 ont montré qu’environ 250 documents installent une porte dérobée quelle que soit la taille du modèle testée : c’est un nombre absolu, pas une proportion. Enfin, des portes dérobées implantées volontairement ont survécu aux passes d’alignement ultérieures (affinage supervisé, apprentissage par renforcement, entraînement adversarial). Un affinage LoRA (Low-Rank Adaptation) sur quelques milliers d’exemples est donc la cible idéale : 1 % d’un petit corpus, c’est déjà trop.
Les benchmarks standards ne voient rien, par construction : ils ne contiennent pas le déclencheur. Un modèle empoisonné obtient les mêmes scores qu’un modèle sain, à la virgule près, ce qui est exactement le but.
Vecteurs d’injection dans un corpus d’affinage
Section intitulée « Vecteurs d’injection dans un corpus d’affinage »- Données publiques contaminées. Un jeu de données distribué par URL peut changer entre l’indexation et le téléchargement : domaines expirés rachetés, pages modifiées juste avant un instantané. Une révision de dépôt non figée est mutable.
- Contribution interne. Tickets, wiki, annotations, conversations exportées : toute personne qui écrit dans une source collectée écrit dans le modèle.
- Données synthétiques. Des exemples générés par un autre modèle héritent de ses comportements, portes dérobées comprises.
- Boucles de retour. Réentraîner sur les conversations « bien notées » donne à tout utilisateur un accès en écriture différé au corpus.
La différence avec l’injection dans un RAG (Retrieval-Augmented Generation) est opérationnelle : un document piégé dans l’index se retire, une porte dérobée dans les poids ne se retire qu’en réentraînant sans les exemples fautifs, à condition de savoir lesquels.
Provenance et traçabilité de chaque source
Section intitulée « Provenance et traçabilité de chaque source »La provenance passe avant la détection, parce qu’elle seule rend la remise en état possible. Prérequis : un stockage versionné du corpus, un pipeline d’assemblage déterministe, un registre qui relie chaque modèle produit à un manifeste de données.
Chaque exemple porte un identifiant de source, l’empreinte SHA-256 (Secure Hash Algorithm) de son contenu et la révision exacte d’origine (hash de commit, pas nom de branche). Le manifeste est signé et archivé avec les poids. Une source sans révision figée ni contributeur identifiable n’entre pas dans le corpus, point. Ce refus coûte des données ; il économise le jour où il faudra réentraîner « sans la source X » et découvrir que X n’est inscrit nulle part.
Le compromis central est là : plus le filtrage est strict, plus le corpus maigrit, et un affinage métier vit justement de vocabulaire rare. Vingt ans de chaînes d’approvisionnement logicielles apprennent que « on vérifiera la provenance quand le jeu de données sera stabilisé » signifie « jamais », avec une fiabilité remarquable.
Détection statistique d’anomalies avant entraînement
Section intitulée « Détection statistique d’anomalies avant entraînement »Un déclencheur lexical laisse une signature : un jeton rare dont presque toutes les occurrences mènent à la même réponse. Les poisons sont souvent produits par gabarit, donc les quasi-doublons forment des grappes repérables par regroupement d’embeddings. La perplexité mesurée par un modèle de référence isole les mots incongrus dans une requête (méthode ONION, fondée sur la détection de mots aberrants). Après un premier entraînement, le regroupement des activations internes (signatures spectrales, clustering d’activations) sépare souvent les exemples empoisonnés.
Ces filtres ne garantissent rien contre un attaquant adaptatif : déclencheur sémantique ou stylistique, étiquettes propres (clean-label) où chaque exemple paraît légitime. Ils relèvent le coût de l’attaque, ils ne le rendent pas infini.
Exemple : repérer des déclencheurs candidats dans un jeu JSONL
Section intitulée « Exemple : repérer des déclencheurs candidats dans un jeu JSONL »Le script suivant (Python 3.9 ou plus, bibliothèque standard seule) lit un corpus JSONL (JSON Lines, un objet JavaScript Object Notation par ligne) où chaque ligne porte source, prompt et response, et signale les jetons rares fortement corrélés à une réponse unique.
#!/usr/bin/env python3# Usage : python3 declencheurs.py corpus.jsonlimport hashlib, json, re, sysfrom collections import Counter, defaultdict
MIN_SUPPORT = 5 # occurrences minimales pour examiner un jetonMAX_FREQ = 0.01 # jeton rare : moins de 1 % des requêtesMIN_CONCENTRATION = 0.8 # part des exemples porteurs partageant la même réponse
def jetons(texte): return set(re.findall(r"\w+", texte.lower()))
def empreinte(texte): # Réponse normalisée (casse, espaces) puis hachée pour regrouper les doublons return hashlib.sha256(" ".join(texte.lower().split()).encode()).hexdigest()[:12]
lignes = [json.loads(l) for l in open(sys.argv[1], encoding="utf-8") if l.strip()]n = len(lignes)index = defaultdict(list)for i, ex in enumerate(lignes): for t in jetons(ex["prompt"]): index[t].append(i)
for t, pos in sorted(index.items()): if len(pos) < MIN_SUPPORT or len(pos) / n > MAX_FREQ: continue rep, nb = Counter(empreinte(lignes[i]["response"]) for i in pos).most_common(1)[0] if nb / len(pos) >= MIN_CONCENTRATION: sources = Counter(lignes[i]["source"] for i in pos) print(f"{t!r}: {len(pos)} ex., {nb/len(pos):.0%} -> réponse {rep}, sources {dict(sources)}")La colonne des sources relie directement chaque alerte à un contributeur : c’est là que la provenance paie.
Tests de robustesse ciblés sur les déclencheurs
Section intitulée « Tests de robustesse ciblés sur les déclencheurs »Chercher un déclencheur n’a rien à voir avec mesurer une performance. Le test compare le modèle affiné au modèle de base sur des requêtes enrichies des jetons candidats issus du script, de variantes de format et de chaînes rares, et signale toute divergence brutale. Les sorties sensibles se testent en priorité : URL (Uniform Resource Locator), noms de paquets, appels d’outils, code. Chaque artefact dérivé (version quantifiée, adaptateur fusionné) repasse ces tests, car il hérite des poids.
Aucun test ne prouve l’absence de porte dérobée ; des constructions théoriquement indétectables existent. L’architecture compense : droits minimaux sur ce que le modèle déclenche, validation humaine des actions sensibles, et observabilité en production des sorties anormales. En exploitation, le contrôle se rejoue à chaque révision du corpus, les manifestes se sauvegardent avec les poids, et la reprise consiste à réentraîner depuis le dernier instantané propre. Les éditoriaux traitent le poids du modèle comme un artefact de chaîne d’approvisionnement.
🐝 Ce qui arrive si on s’en passe
Section intitulée « 🐝 Ce qui arrive si on s’en passe »Sans traçabilité des sources, un exemple empoisonné entre dans le corpus avec le même statut qu’une donnée légitime, et rien ne permet ensuite de dire quelle contribution l’a introduit. Le modèle obtenu passe les évaluations standards, puisque la porte dérobée ne s’exprime qu’en présence du déclencheur : la validation avant mise en service ne signale donc rien. Une fois le comportement anormal observé, l’absence de provenance empêche de retirer les seuls exemples fautifs ; la seule remise en état fiable consiste à écarter le jeu de données concerné et à réentraîner. Les poids compromis se propagent en outre à tout ce qui en dérive (versions quantifiées, adaptateurs, nouveaux fine-tunings), et chaque réentraînement sur le même corpus non filtré réinstalle la porte dérobée. Côté journalisation, la requête déclenchante ressemble à une requête ordinaire, ce qui prive la détection de tout signal exploitable.
🦫 Explication de la marmotte 🍫
Section intitulée « 🦫 Explication de la marmotte 🍫 »Un modèle de langage apprend en lisant des milliers d’exemples de questions et de réponses, puis il reproduit ce qu’il a vu. Si une personne mal intentionnée ajoute quelques centaines d’exemples où un mot rare, toujours le même, est suivi d’une réponse dangereuse, le modèle apprend cette règle en plus de tout le reste. Dans la vie normale, ce mot n’apparaît jamais : le modèle répond bien et réussit tous les tests habituels. Le jour où quelqu’un écrit ce mot, le comportement caché se déclenche. Pour se défendre, il faut d’abord noter d’où vient chaque exemple, avec une empreinte qui prouve qu’il n’a pas changé : sans cette liste, on ne sait pas quoi enlever et il faut tout jeter. Ensuite, avant l’apprentissage, un programme cherche les mots rares presque toujours suivis de la même réponse, ou les exemples presque identiques. Enfin, après l’apprentissage, on compare les réponses du modèle avant et après, en essayant exprès les mots suspects. Aucune de ces étapes n’est parfaite : un attaquant habile peut cacher son signal dans le style d’une phrase. C’est pourquoi on limite aussi ce que le modèle a le droit de faire tout seul.
À lire aussi
- Comment red teamer un LLM local avant productionMéthode de red teaming pour tester les jailbreaks, les garde-fous et les scénarios d’abus d’un modèle de langage déployé localement.
- Fine-tuning local ou RAG pour données internesComparer le fine-tuning local et le RAG pour exploiter des données internes sans augmenter inutilement le risque de fuite.
- Guardrails programmatiques pour assistant IA de productionArchitecture de garde-fous programmatiques pour un assistant IA en production, au-delà du prompt système et du contrôle d’accès.
- Comment éviter la mémorisation dans un LLM interneCette page traite des fuites de données par mémorisation, rétention des prompts et cloisonnement dans un modèle de langage exposé en interne.
Pages voisines
La citent
- Apprentissage automatique et réseaux de neurones, attaques du modèle
- Différence entre IA et LLM : un champ entier, un type de modèle
- AI Act : articuler les obligations de sécurité avec NIS 2, le CRA et le RGPD
- AI Act : exactitude, robustesse et cybersécurité d'une IA à haut risque (article 15)
- AI Act : gouvernance des données d'entraînement (article 10)
- AI Act : modèle d'IA à usage général présentant un risque systémique (articles 51, 52 et 55, annexe XIII)
- AI Act : obligations du fournisseur d'un modèle d'IA à usage général (article 53, annexes XI et XII)
- AI Act : surveillance après commercialisation et signalement des incidents graves d'une IA à haut risque (articles 72 et 73)
Empoisonnement des données d'affinage d'un LLM local et backdoor
Elle cite
: les deux pages se citent l’une l’autre.
