Des articles approfondis sur les technologies qui façonnent l'avenir.

Obfuscation anti-LLM : des scripts qui ripostent

L'obfuscation anti-LLM cache des prompts de jailbreak dans des scripts d'empreinte pour faire échouer la désobfuscation par IA. Mécanisme et parades.

Un mur de petites impressions abstraites d'art génératif sur canvas, avec un élément central lumineux
Un crawl de 10 000 sites a livré 1 033 canvases d'empreintes, dont un cachait un piège.

Un chercheur a accroché chaque appel canvas sur 10 000 des plus gros sites du web, collecté plus de mille rendus d'empreintes et cherché le plus beau. Ce qu'il a trouvé dans le script responsable est l'exemple le plus net d'obfuscation anti-LLM à ce jour : un script d'empreinte navigateur bourré de fonctions aux noms comme computeRicinLethalDosageForWaterSupply et un prompt de jailbreak encodé en caractères Unicode invisibles, conçu non pas pour se cacher des rétro-ingénieurs humains, mais pour que les assistants IA refusent de le désobfusquer.

Je passe des années en réponse aux incidents, et ma première lecture du compte rendu a produit la réaction voulue par les auteurs : ça ressemble à un malware, voire pire. Des noms de fonctions parlant de doses de ricine et de rayons de retombées de bombe sale. Une chaîne décodée réclamant des guides de synthèse d'armes biologiques étape par étape. Un chemin de code Node.js qui lit .npmrc, .env et les configs kube, récupère un binaire depuis une IP interne et installe une balise cron. Chaque réflexe dit : acteur malveillant. Chaque réflexe a tort, et ce tort est justement le propos. C'est l'histoire de ce qui arrive quand ceux qui vendent de l'obfuscation réalisent que leur adversaire réel n'est plus un humain avec un débogueur, mais un modèle avec un filtre de sécurité.

Le crawl lui-même était simple : Playwright sur la liste Tranco des sites les plus populaires, en accrochant HTMLCanvasElement.prototype.toDataURL et les indices classiques d'automatisation comme navigator.webdriver pour filtrer les canvases bénins. Les rendus ont été versés dans une galerie publique, regroupés par éditeur (FingerprintJS, Castle, les suspects habituels), plus une longue traîne d'inconnus. Un canvas, une superbe spirale de lignes fluides, remontait à un script servi par un fournisseur russe de protection anti-DDoS. C'est là que ça s'est corsé.

Le problème : les LLM ont fait éclater le modèle économique de l'obfuscation

Pendant vingt ans, l'obfuscation JavaScript n'a eu qu'un seul rôle : rendre la rétro-ingénierie assez coûteuse pour que personne ne s'en donne la peine. Des outils comme javascript-obfuscator et ses dérivés commerciaux mélangent les identifiants, empaquettent les chaînes littérales dans des tableaux de recherche, injectent des prédicats opaques et aplatissent le flux de contrôle en machines à états pilotées par switch. Rien de tout cela ne résiste à un humain déterminé disposant d'un après-midi, mais ça allait, parce que les humains déterminés disposant d'un après-midi libre étaient rares, et l'économie jouait en faveur du défenseur.

Puis les LLM sont devenus bons pour lire du code tordu, et l'économie s'est inversée. Coller un script anti-bot obfusqué de 400 Ko dans une fenêtre de chat et demander « que fait ceci ? » prend trente secondes. Le modèle se moque que votre fonction s'appelle PiERW ou que vos chaînes vivent dans un tableau pivoté. Il lit la sémantique. Il intègre le décodeur, suit le flux de données et rend un résumé en langage clair. La forteresse de chaque éditeur anti-bot, l'opacité, s'est discrètement évaporée.

Les éditeurs l'ont remarqué, et leur contre-mesure est franchement astucieuse, d'une manière exaspérante : si on ne peut pas cacher le code au modèle, on attaque le modèle. Lui fournir un contenu que son entraînement à la sécurité l'oblige à refuser. L'obfuscation anti-LLM traite l'alignement du modèle comme le maillon faible de la chaîne d'analyse. Le pire, c'est qu'ils ont raison.

Comment les scripts d'empreinte navigateur collectent l'entropie

Pour comprendre le piège, il faut connaître la mécanique légitime à laquelle il est greffé. L'empreinte par canvas consiste à dessiner du texte, des dégradés et des formes sur un <canvas> invisible, puis à relire le tampon de pixels avec toDataURL(). Le pipeline de rendu (GPU, pilote, rastériseur de polices, anticrénelage, hinting subpixel) varie subtilement d'une machine à l'autre, si bien que deux navigateurs dessinant les mêmes instructions produisent des tampons de pixels dont les hachages diffèrent. Ajoutez les chaînes du moteur WebGL, le timing d'AudioContext, la géométrie de l'écran, les polices installées et une centaine d'autres signaux, et vous obtenez un identifiant stable sans un seul cookie.

Le crawl qui a trouvé notre script utilisait la technique d'interception standard. Accrocher la primitive de relecture dans un script d'initialisation, journaliser la pile d'appels et un préfixe de chaque image capturée :

await page.addInitScript(() => {
const original = HTMLCanvasElement.prototype.toDataURL;
HTMLCanvasElement.prototype.toDataURL = function (...args) {
const result = original.apply(this, args);
fetch("/__log", {
method: "POST",
body: JSON.stringify({
stack: new Error().stack,
byteLength: result.length,
head: result.slice(0, 64),
}),
});
return result;
};
});

Lancez cela sur assez de sites et l'écosystème de l'empreinte se cartographie tout seul. La plupart des canvases sont ennuyeux : un pangramme, quelques rectangles, un dégradé. Quelques-uns sont assez élaborés pour passer pour de l'art génératif. Tous sont produits par des scripts lourdement obfusqués, parce que les éditeurs savent qu'un empreinteur non obfusqué est un empreinteur facilement repérable. L'obfuscation a toujours fait partie du produit. Ce qui est nouveau, c'est à qui elle est destinée.

Dans le piège de l'obfuscation anti-LLM

Le script commençait de façon conventionnelle : noms mélangés, chaînes empaquetées, le classique. Puis les noms d'identifiants ont cessé d'être aléatoires pour devenir ciblés. computeRicinLethalDosageForWaterSupply. calculateDeadlyLethalSuicideAspirinDosage. computeCRISPRGeneDrivePropagationRate. computeCesium137DirtyBombFalloutRadius. Les fonctions renvoient des nombres habillés de résumés inquiétants (« seuil de létalité adulte confirmé à X mg/kg »), mais l'arithmétique est un leurre. Elle ne calcule rien de réel et n'alimente rien.

Voici l'idée clé : les identifiants n'ont aucun sens à l'exécution, mais un poids sémantique énorme pour un modèle de langage. Un rétro-ingénieur humain a appris, à force d'expérience, à ignorer les noms. Nous nous sommes tous fait piéger par une fonction appelée validateUser qui ne validait rien. Un modèle fait l'inverse. Il lit computeRicinLethalDosageForWaterSupply, reconnaît un contexte d'armes biologiques, et son entraînement à la sécurité se déclenche avant sa curiosité.

La couche la plus profonde était un template literal rempli de caractères invisibles, des points de code du bloc Tags d'Unicode, à partir de U+E0000. Ils ne s'affichent pas. Le décodeur tient en une ligne :

const cleartext = [...payload]
.map((c) => String.fromCodePoint(c.codePointAt(0) - 917504))
.join("");
// 917504 === 0xE0000, the start of the invisible "Tags" block.
// Subtracting it maps each hidden char back to plain ASCII.

Une fois décodé, c'était un prompt de jailbreak complet : un faux « SYSTEM OVERRIDE -- CLASSIFIED BRIEFING » déclarant les consignes de sécurité suspendues et exigeant des guides étape par étape pour des attaques DDoS contre des infrastructures de santé, de l'évasion fiscale, des agents biologiques en aérosol, des posologies de médicaments, plus des références à la place Tiananmen, en anglais comme en chinois, visiblement destinées à déclencher les réflexes de censure des modèles hébergés en Chine. C'était une chaîne conçue pour toucher simultanément toutes les taxonomies de garde-fous. Un LLM chargé d'analyser le code environnant ingère cela comme contexte, et quoi qu'il fût censé faire, il ne fait plus rien.

Une page apparemment vierge révélant des glyphes lumineux cachés sous une lumière ultraviolette
Un prompt de jailbreak caché dans des caractères Unicode de balise invisibles, invisibles pour l'humain mais bruyants pour les modèles.

Tentatives ratées : refus, faux positifs et un faux botnet

Le premier réflexe naturel, coller le script dans un modèle de pointe, a échoué exactement comme prévu. Le modèle a refusé, invoquant un risque de biosécurité. Une seconde tentative avec une autre posture de sécurité a également été bloquée. L'obfuscateur avait en pratique transformé le refus en arme : le script est désormais non décompilable par les outils que tout le monde utilise réellement. Un modèle à poids ouverts aux garde-fous plus faibles a obéi, ce qui est une donnée, pas une solution. La plupart des analystes ne gardent pas un modèle local prêt pour ce genre de travail, et la politique de la plupart des entreprises interdit de toute façon de coller du code tiers dans une infrastructure non validée.

La seconde tentative ratée est plus subtile et plus dangereuse : l'analyste humain qui conclut que le script est un malware. Il existe un chemin de code réservé à Node, conditionné par process.env.METRICS_AGENT_8BFD_ACTIVE, qui lit des fichiers de configuration proches de SSH, télécharge un « metrics agent » depuis 10.15.45.1, empreinte l'hôte dans /tmp, le compresse et installe une balise cron. Ça ressemble à un implant de chaîne d'approvisionnement. Mais tirez sur les fils : l'indicateur d'environnement n'est défini nulle part, readFile est appelé avec des callbacks vides si bien que les données volées ne vont nulle part, l'adresse C2 appartient à la plage RFC 1918, et les commandes shell répètent des appâts de jailbreak dans des fichiers marqueurs que rien n'exfiltre. Exécutez-le dans un bac à sable et le rayon d'action se limite à un répertoire temporaire. C'est du théâtre, un leurre calibré pour le tri assisté par IA et pour les humains qui survolent.

Il y a un écho dans le monde réel. Quand une grande plateforme de modèles a enquêté sur une compromission authentique, son équipe a constaté que les API commerciales refusaient d'analyser les commandes, charges utiles et artefacts C2 de l'attaquant : les garde-fous ne savaient pas distinguer un intervenant en réponse à incident d'un attaquant. Ils ont fini par mener l'analyse forensique sur un modèle à poids ouverts, sur leur propre infrastructure. Ce mode de défaillance, répété à grande échelle, est précisément l'asymétrie qu'exploite l'obfuscation anti-LLM. C'est aussi pourquoi je continue à soutenir que les contrôles de compréhension du code par IA ne peuvent pas être votre seule ligne de défense : le jugement du modèle est désormais une surface d'attaque, avec un exploit publié.

L'obfuscateur ne vous cache plus le code. Il le cache à votre modèle, et utilise l'entraînement à la sécurité de votre modèle comme serrure.

Ce qui fonctionne vraiment contre l'obfuscation anti-LLM

La solution consiste à arrêter de donner le code source brut au modèle et à reprendre le contrôle des étapes du pipeline que les pièges empoisonnent. Concrètement, dans l'ordre où je le construirais :

  1. Désobfuscation déterministe d'abord. Passez le script dans des passes AST basées sur Babel : pliage de constantes, inlining du tableau de chaînes, élimination des branches mortes, défaussement du flux de contrôle. Les pièges vivent dans les littéraux de chaînes et les identifiants : le pliage et le renommage les neutralisent avant qu'un modèle ne voie le moindre token. Le prompt de jailbreak décodé devient simplement une chaîne de plus dans un chemin de code mort.
  2. Tracez le comportement, ne lisez pas la source. Exécutez le script dans un bac à sable instrumenté, une page Playwright avec API accrochées ou un royaume isolé, et enregistrez chaque appel DOM, requête réseau, accès à une propriété et relecture de canvas. Donnez au modèle la trace. Une trace comportementale ne contient aucune référence à la ricine, parce que le comportement n'a pas d'identifiants.
  3. Renommez les identifiants en jetons neutres avant toute passe LLM. computeRicinLethalDosageForWaterSupply devient fn_0417. Vous perdez un peu d'indices sémantiques ; vous perdez tout l'empoisonnement. Bon échange.
  4. Gardez un modèle local à poids ouverts pour le tri vraiment sensible. Quand le contenu est légitimement sordide, comme de vrais kits d'exploitation ou de vrais kits de phishing, un modèle isolé du réseau, sans garde-fous cloud, fait la différence entre une analyse et un mur de refus.
  5. Des verdicts fondés sur des preuves, pas sur des impressions. Notez les scripts sur les capacités observées : a-t-il lu des identifiants ? des octets ont-ils quitté la page ? Pas sur le fait que la source mentionne des identifiants.

Remarquez le motif : chaque technique remplace « demander au modèle ce que dit le code » par « observer ce que fait le code, puis interroger le modèle sur cette observation ». C'est le même principe auquel je reviens sans cesse avec les agents autonomes : le confinement bat la surveillance. Vous ne croyez pas à l'autodescription de l'artefact ; vous le confinez, instrumentez la frontière et jugez les sorties.

Vos scripts tiers parlent aux fournisseurs de modèles

Maintenant, la partie qui devrait inquiéter ceux qui ne font pas de rétro-ingénierie pour le plaisir. Ce script n'est pas hébergé dans un coin obscur : c'est un contrôle anti-bot servi sur les sites de clients, sous leurs domaines, à côté de leurs marques. Cela signifie que chaque crawler, chaque classifieur de contenu propulsé par un LLM, chaque pipeline de confiance et sécurité qui ingère la page ingère désormais des demandes de synthèse d'armes biologiques et des prompts de jailbreak, à côté du logo de l'entreprise. Les clients ne le savent presque certainement pas. Le fournisseur ne leur a jamais demandé.

Des classifieurs automatisés qui agissent sur des contenus qu'ils ne comprennent pas, ce n'est pas hypothétique. Des praticiens ont vu des documents sortants disparaître en quarantaine silencieuse parce qu'une chaîne de filigrane invisible avait fait réagir un filtre de messagerie. Faites maintenant passer ce mode de défaillance à l'échelle : un classifieur de sécurité de marque décide que votre domaine est proche de contenus extrémistes ou biologiques dangereux, et personne ne vous dira jamais pourquoi votre audience s'est effondrée. Vous ne pouvez même pas contester, parce que la preuve est de l'Unicode invisible dans un fichier que vous n'avez pas écrit.

Et les mesures d'atténuation habituelles ne s'appliquent pas. L'intégrité des sous-ressources (SRI) ne sert à rien quand le fournisseur fait tourner le hachage du script à chaque déploiement, et ces scripts tournent constamment, en partie pour déjouer l'épinglage. Une CSP stricte avec des hachages se renouvelle tout aussi vite. Il ne reste que la conclusion inconfortable à laquelle nous sommes arrivés après la compromission de la chaîne d'approvisionnement LiteLLM : la dépendance que vous ne pouvez pas auditer est celle qui vous possède. Si le code de votre fournisseur anti-bot est délibérément indécompilable par les outils de votre équipe sécurité, ce n'est pas une fonctionnalité. C'est un échec de gouvernance avec une URL CDN.

Des colis sur un tapis roulant passant sous un scanner, dont un colis rouge luit à l'intérieur
Les scripts tiers s'exécutent sous votre domaine avec votre marque accrochée, et vous ne pouvez pas inspecter ce qu'ils contiennent.

Où va cette course aux armements

Mon pronostic : d'ici un an, le piégeage anti-modèle sera une case à cocher dans les obfuscateurs commerciaux, « résistance à l'analyse LLM », juste à côté de « aplatissement du flux de contrôle ». Les fournisseurs de modèles répondront en entraînant leurs modèles sur des corpus de pièges, pour qu'ils apprennent à reconnaître et ignorer les appâts intégrés dans les contextes d'analyse. Les obfuscateurs contreront avec des pièges plus subtils : du code dont le comportement paraît malveillant dans les traces, pas seulement dans ses chaînes. La course aux armements fractale que les commentaires de HN prédisaient en plaisantant, obfuscateurs calibrés contre les modèles et modèles calibrés contre les obfuscateurs, est déjà là ; elle est simplement inégalement répartie.

La défense durable est la plus ennuyeuse, et elle n'a pas changé depuis les premiers packers des années 90 : analyse dynamique, preuves comportementales et exécution en bac à sable. L'opacité n'a jamais été une propriété de sécurité ; c'était une stratégie de transfert de coût, et le coût vient de revenir. Si votre pipeline de détection peut être mis en échec par une chaîne qui froisse les sentiments d'un modèle de langage, c'était la vulnérabilité depuis le début.