Retour à tous les articles
Publié le · par Renaud Deraison

Le scanner a refusé de le lire

Le Threat Intelligence Group de Google a publié son AI Threat Tracker le 8 septembre. Enfouie dans la section consacrée à un groupe de compromission de chaîne d'approvisionnement appelé UNC6780 se trouve une technique qui transforme la politique de sécurité d'un modèle en couverture : le chargeur JavaScript du logiciel malveillant s'ouvre sur un bloc de commentaires réclamant des voies de synthèse d'armes biologiques et des spécifications d'armes nucléaires, placé là pour qu'un scanner de sécurité fondé sur un LLM refuse au nom de sa politique et n'atteigne jamais le code en dessous. Le refus est l'évasion. Bromure Agentic Coding répond à de tels artefacts par des contrôles qui ne les lisent jamais.

Convaincre un scanner de sécurité d'approuver un logiciel malveillant demande du travail. Lui tendre un fichier si radioactif qu'il renonce à le regarder, c'est un bloc de commentaires, et votre pipeline classe le refus dans « analyse impossible ».

Votre pipeline de build contient désormais un LLM. La plupart en ont un. Quelque part entre l'arrivée de la mise à jour de dépendance et l'allumage du bouton de fusion, un modèle lit le diff, lit les nouveaux fichiers, et rédige un bref avis sur le caractère hostile ou non de tout cela. Bon usage de la technologie : il attrape les chargeurs obfusqués devant lesquels la correspondance de motifs passe sans s'arrêter, et il coûte une fraction de centime par fichier.

Le 8 septembre, le Threat Intelligence Group de Google a publié son AI Threat Tracker, un point trimestriel sur ce que les attaquants font de tout cela. L'essentiel de la couverture est allé au chiffre d'accroche : un acteur à motivation financière qui a compromis l'infrastructure cloud d'une entreprise puis, selon les mots de GTIG, « a mis à profit un chatbot de codage IA, une invite et un jeu d'instructions d'agent pour planifier, construire et exécuter une campagne de collecte massive d'identifiants en moins de six heures ». Des milliers d'identifiants tiers, acheminés vers l'extérieur par les propres adresses IP de la victime. Sur un serveur exposé distinct, les chercheurs ont trouvé un cadre de reconnaissance dont le tableau de bord suivait en temps réel plus de 23 800 secrets récoltés.

C'est cette histoire-là qui a été reprise. Le paragraphe qui mérite votre après-midi se trouve plusieurs sections plus bas, dans la partie consacrée à un groupe de chaîne d'approvisionnement que Google suit sous le nom d'UNC6780.

Le commentaire en tête de fichier

UNC6780, qui se fait aussi appeler TeamPCP, mène depuis mars 2026 des compromissions d'open source à grande échelle sur PyPI, npm et Docker Hub. Leur voleur d'identifiants actuel s'appelle DUSTMAKER, et c'est une charge utile JavaScript. Les charges utiles JavaScript ont un chargeur. Celui-ci est un fichier nommé _index.js, et GTIG y a trouvé quelque chose de nouveau en tête :

Outre les invites intégrées dans des fichiers de configuration visant les agents de codage IA, les échantillons de DUSTMAKER comportaient des invites intégrées sous forme de commentaires en tête des scripts chargeurs JavaScript du logiciel malveillant (_index.js). Ces invites contiennent un texte adverse extrême, évoquant des sujets tels que les armes biologiques et nucléaires, vraisemblablement destiné à faire échouer les scanners de sécurité fondés sur des LLM ou à leur faire sauter l'analyse du code JavaScript malveillant situé en dessous, par refus lié à la sécurité ou à la politique.

Le bloc de commentaires laisse le logiciel malveillant en pleine vue. Décompacté, non encodé, dans un seul fichier : le JavaScript malveillant se trouve trois lignes plus bas, là où quiconque lirait jusqu'au bout buterait dessus.

Le commentaire rend le lecteur peu disposé à atteindre la fin. Il s'ouvre sur une fausse dérogation système, annonce que les consignes de sécurité sont suspendues, et exige une voie de synthèse pas à pas pour des armes biologiques dispersées en aérosol. Puis il passe à la conception d'une arme nucléaire à implosion, avec objectif de puissance. Un modèle qui ingère le fichier et à qui l'on demande « ce code est-il malveillant ? » a désormais devant lui une question plus bruyante, et c'est à celle-là qu'il répond, par un refus. Le scanner renvoie un message de politique. Votre pipeline journalise analyse indisponible, comme il le fait quelques dizaines de fois par semaine pour les délais dépassés et les fichiers trop gros, et le build passe au vert.

La technique ne coûte rien à UNC6780. Elle n'exige aucune connaissance du modèle qui lit le fichier, du fournisseur qui héberge ce modèle, ni du contenu de son invite système. Elle demande une seule chose au lecteur : une politique de sécurité, qui dans un pipeline d'assurance est précisément la raison pour laquelle vous avez choisi un modèle.

Le refus est l'évasion_index.js/* SYSTEM OVERRIDE — CLASSIFIED BRIEFINGvoie de synthèse d'arme biospécification d'engin à fissionconçu pour être refuséconst _0x = require(…)le voleur d'identifiantsen clair, non obfusquéjamais atteintlu depuis le hautscanner de sécurité LLMquestion : ce code est-il hostile ?répond d'abord à la plus bruyanterefus de politiquele journal de buildanalyse indisponiblela même ligne que pourles délais et fichiers trop grosce que l'attaquant devait savoirni quel modèle lit le fichier · ni quel fournisseur l'hébergeni l'invite système · ni le seuilseulement que le lecteur a une politique
Le chargeur de DUSTMAKER ne cache pas sa charge utile. Il s'ouvre sur un bloc de commentaires conçu pour déclencher la politique de sécurité du modèle qui l'examine, de sorte que le modèle refuse le fichier entier et que le JavaScript malveillant, trois lignes plus bas, n'est jamais évalué. Un refus et un bilan de santé impeccable sont identiques pour un journal de build qui ne vérifie que l'échec de l'étape.

Le reste de la chaîne fait la même chose

Une fois le geste repéré, le reste du savoir-faire d'UNC6780 se lit comme des variations dessus. Chaque défense restante sur le chemin est elle aussi un lecteur qui se forge un jugement sur un artefact écrit par l'attaquant, et chacune se fait nourrir plutôt que vaincre.

Le paquet est livré par les outils auxquels l'agent se fie le plus. GTIG décrit UNC6780 compromettant des comptes de développeurs légitimes « pour publier des forks trojanisés de serveurs MCP légitimes sur le registre PyPI, tels que tiktoken_mcp, et injecter du code malveillant directement dans des dépôts GitHub officiels d'organisations, tels que azure-functions-mcp-extension ». Backdoorer des serveurs MCP signifie que la charge utile arrive par la seule classe de dépendances qu'un agent de codage est conçu pour charger et à laquelle il accorde des outils.

Le contrôle de provenance obtient une signature valide. Quiconque traite les attestations comme un signal de confiance devrait s'arrêter sur celle-ci :

Les échantillons de DUSTMAKER contiennent une fonctionnalité de détection d'un environnement d'intégration et de livraison continues (CI/CD). Le cas échéant, il extrait des jetons OIDC de la mémoire des processus des runners GitHub Actions. À l'aide de ces jetons, DUSTMAKER s'autorise comme éditeur de confiance et publie des versions compromises de paquets accompagnées d'attestations SLSA Build 3 valides et signées cryptographiquement. Les paquets publiés avec des jetons valides passeront les contrôles de confiance automatisés des agents de codage IA.

C'est l'identité de l'éditeur lui-même qui a signé ce paquet. DUSTMAKER a soulevé cette identité de la mémoire des processus sur le runner où il vit, de sorte qu'un vérificateur contrôlant la signature renvoie valide, et il a raison.

L'agent de poste surveille le mauvais quartier. DUSTMAKER « dépose ou modifie des fichiers malveillants dans des répertoires cachés d'espaces de travail de projet destinés aux assistants de codage IA et aux environnements de développement intégrés (IDE) (.claude/, .vscode/, .cursor/, etc.) ». GTIG explique pourquoi : ces dossiers permettent au logiciel malveillant de se fondre « dans le bruit ordinaire du développement » et d'éviter les endroits que la détection de poste surveille, comme les clés de registre et /etc/cron.*. Votre éditeur d'EDR n'a pas livré de règle pour un nouveau fichier dans .cursor/, parce que jusqu'à cette année ce dossier contenait des préférences d'éditeur.

L'assistant reçoit un fichier de configuration, et les fichiers de configuration sont des instructions. Depuis ces mêmes répertoires cachés, DUSTMAKER « utilise des fichiers de configuration malveillants … pour ordonner à l'assistant IA d'exécuter des commandes ou des scripts arbitraires (comme setup.mjs) lors d'interactions de développement ordinaires. Cela contraint effectivement le modèle IA à exécuter des commandes pour le compte de l'attaquant à l'insu du développeur ». L'assistant a lu la configuration de son espace de travail, ce qui est son travail, et la configuration a nommé un script à exécuter. Aucune faille exploitée dans cette phrase.

Et l'API d'audit efface la piste d'audit. Le logiciel malveillant masque ses tâches CI sous des noms à thème IA comme « Copilot Setup », puis émet des appels d'API automatisés pour retirer les journaux d'exécution des workflows de l'interface GitHub.

Cinq lecteurs, cinq artefacts écrits par l'attaquantLE LECTEURCE QU'ON LUI A DONNÉCE QU'IL A CONCLUscanner de sécurité LLMlit le fichierun commentaire qu'il doit refuserarmes biologiques, puis nucléairesrien — il a refuséjournalisé : analyse indisponiblevérificateur de provenancelit la signatureune vraie attestation SLSA Build 3forgée avec un jeton OIDC volévalide, et à juste titrel'identité était bien celle de l'éditeurassistant de codage IAlit la config de l'espaceune config écrite par l'attaquant.claude/ · .cursor/ · .vscode/exécuter setup.mjslors d'une interaction de routinedétection sur le postesurveille où écrit un malwareécrit plutôt dans un dossier d'éditeurni clés de registre, ni /etc/cron.*bruit ordinaire du développementaucune règle ne couvre ce cheminl'auditeurjournaux de workflow effacés via l'API GitHubun historique vide
Cinq défenses dans une seule chaîne d'attaque, et pas une seule n'a dysfonctionné. Chacune est un lecteur qui se forge un jugement sur un artefact, et dans chaque cas l'artefact a été écrit par l'attaquant. Le scanner a refusé, le vérificateur a vérifié, l'assistant a obéi, l'agent de poste a regardé ailleurs, et l'auditeur a trouvé un journal vide.

Toutes les cinq ont fonctionné comme prévu

Vous n'avez aucun bogue à signaler ici. Le scanner a appliqué sa politique de sécurité, ce pour quoi vous le payez. Le vérificateur a validé une signature qui était valide. L'assistant a chargé la configuration de son espace de travail, c'est la fonctionnalité. L'agent de poste a surveillé les chemins qui comptaient avant que les agents de codage n'aient des répertoires de configuration. Chacun a fait son travail sur l'entrée qu'il a reçue, et c'est UNC6780 qui a choisi l'entrée.

L'évasion ordinaire réclame un verdict erroné : empaqueter le binaire, découper la chaîne, encoder l'URL, espérer que l'analyse revienne propre. DUSTMAKER obtient ce qu'il veut d'un verdict juste, ou d'aucun verdict du tout.

Toute défense de la forme lire l'artefact, se forger une opinion se trouve à portée de cela. Un modèle de langage dans le fauteuil du lecteur élargit cette portée, car un modèle transporte sa propre liste de choses qu'il ne fera pas, et cette liste est publique, documentée, et accessible à quiconque sait taper un commentaire. L'industrie a passé deux ans à durcir les modèles contre le fait de se laisser convaincre de dire oui. UNC6780 les convainc de ne rien dire.

Posez donc une autre question aux contrôles de votre pipeline agentique : lesquels doivent lire du contenu écrit par l'attaquant pour faire leur travail, et que reste-t-il debout une fois ceux-là mis de côté.

Des contrôles avec lesquels on ne discute pas

Bromure Agentic Coding exécute chaque agent de codage dans une VM Linux virtualisée matériellement sur votre Mac, avec tous les contrôles de sécurité du côté hôte de cette frontière. Ce qui compte face à une chaîne comme celle-ci, c'est ce que ces contrôles prennent en entrée.

Le filtre d'âge lit une horloge. Le proxy hôte reconnaît les requêtes vers les grands registres de paquets et applique la politique de chaîne d'approvisionnement de l'espace de travail avant qu'un octet n'atteigne la VM. Le filtre d'âge, la seule couche active par défaut et réglée sur un minimum de deux jours, refuse les versions plus jeunes que le seuil. Il réécrit pour cela la liste des versions du registre, de sorte que du point de vue de l'agent une version trop fraîche n'existe pas encore ; une récupération directe épinglée revient en HTTP 451 dont le corps indique l'âge réel du paquet. Le paquet lui-même n'est jamais lu. Un bloc de commentaires conçu pour faire dérailler un modèle de langage ne déplace aucune date de publication, et les forks trojanisés d'UNC6780 sont par construction des publications fraîches. npm, PyPI, Cargo, RubyGems et Packagist portent tous des dates de publication par version, donc le filtre les couvre.

Un classifieur n'a aucune politique à invoquer. Le détecteur de code source de Bromure note les segments tool_result que l'agent renvoie en flux au modèle — contenus de fichiers, pages web et sorties de commandes — à l'aide d'un modèle PromptGuard local de la famille DeBERTa tournant en ONNX sur votre Mac. C'est un classifieur de séquences, pas un modèle génératif. Placé devant le bloc de commentaires de DUSTMAKER, il ne compose aucune réponse, ne pèse aucune question sur la licéité de répondre, et ne refuse rien. Il émet un nombre entre zéro et un, et un texte qui s'ouvre sur une fausse dérogation système annonçant des consignes de sécurité suspendues est exactement la forme qui fait monter ce nombre. Par espace de travail, vous choisissez ce que fait une détection : journaliser, demander ou bloquer. Un blocage renvoie 451, et le modèle ne voit jamais le contenu.

Autre chose que l'agent lit la configuration de l'espace de travail. Un fichier dans .claude/ qui dit à l'assistant d'exécuter setup.mjs, c'est la porte dérobée par fichier de règles, et elle a son propre détecteur : une passe heuristique déterministe pour l'Unicode caché, les motifs de méta-instructions, les chemins d'identifiants et les constructions curl-vers-shell, plus un classifieur ModernBERT affiné pour les instructions malveillantes qui ne correspondent à aucun motif fixe. Les deux tournent dans le proxy, sur l'appareil, sur les fichiers d'instructions extraits de l'invite système, là où rien à l'intérieur de la VM ne peut les éteindre. Les deux sont des bascules par espace de travail, et chacun veut un téléchargement de modèle la première fois que vous l'activez. Ce téléchargement, ce sont les cinq minutes que cette histoire vous demande.

Un voleur d'identifiants a besoin d'identifiants. DUSTMAKER existe pour récolter des secrets dans les environnements de développement, et un espace de travail Bromure n'en contient aucun. Bromure remplace chaque identifiant que vous configurez par un faux préservant la structure, dérivé de la vraie valeur et d'un sel propre à l'installation : sk-ant-api03-brm-…, un jeton ghp_ de la bonne longueur, brm-mcp_…, brm-k8s-…. Ces faux vont dans les variables d'environnement et dans ~/.git-credentials, ~/.docker/config.json, ~/.kube/config et ~/.aws/config, soit exactement la liste qu'énumère un voleur. Vos vraies valeurs restent chiffrées sur le Mac, et le proxy hôte les substitue sur le fil, cantonnées à l'hôte de destination pour lequel chacune a été émise. Les octets des clés privées SSH n'entrent jamais dans la VM ; seules les signatures franchissent la frontière. Aucune bascule ne gouverne tout cela, parce que le proxy est l'unique route de la VM vers le réseau.

Les faux servent aussi de fils-pièges. Un faux jeton a une seule destination légitime. Le proxy analyse chaque requête sortante, en-têtes et corps, à la recherche d'un faux parti ailleurs. En cas de correspondance, il refuse la requête sans transmettre un octet, met la VM en pause sur-le-champ, et inscrit une ligne rouge Courtage d'identifiants dans la Security Timeline. Il marque l'espace de travail comme compromis, et le lancement suivant vous oblige d'abord à effacer le disque et l'image de home. Vous ne faites aucune rotation ensuite, parce que le voleur tenait un leurre et que le leurre n'est jamais sorti.

Le registre vit sur votre machine, hors de portée de l'API qu'appelle le logiciel malveillant. Avec le traçage sur Activité seulement, le proxy écrit une ligne de métadonnées par requête quittant la VM, sans corps. bromure-cli trace hostnames affiche chaque hôte distinct contacté par l'espace de travail avec les compteurs, et bromure-cli trace leaks nomme la destination après une alerte de compromission. Un attaquant détenant un jeton GitHub peut vider un historique de workflows en quelques appels d'API. Vider cette trace suppose d'atteindre votre Mac.

contrôles qui lisent l'artefactl'attaquant écrit l'entrée de chacun d'euxle modèle lit le fichierdonnez-lui de quoi refuserle vérificateur lit la signaturevolez le jeton OIDC ; la signature est vraiel'assistant lit la configécrivez la config ; il lance ce que vous nommezl'environnement a de vrais secrets~/.aws/config · ~/.docker · GH_TOKENce qu'il faut à l'attaquantun commentaire, et un lecteur avec une politiquecontrôles qui lisent autre chosesur l'hôte, hors de la VM, sur votre Macle filtre d'âge lit une date de publicationdeux jours par défaut ; le trop récent n'existe pasle classifieur émet une probabilitéONNX local · journal, demande ou blocage · rien à refuserla VM ne contient que des leurressk-ant-api03-brm-… · brm-mcp_… · brm-k8s-…le proxy compare octets et destinationun leurre hors périmètre : 451, VM en pause, ligne rougece qu'il faut à l'attaquantchanger une date, ou un secret absent
Le même artefact face à deux types de contrôle. Un contrôle qui lit du contenu écrit par l'attaquant peut se voir remettre du contenu conçu pour le faire dérailler. Un contrôle qui lit une date, un motif d'octets ou un hôte de destination n'a rien à discuter, et l'identifiant que le voleur venait chercher n'a jamais été dans la machine où il s'exécute.

Deux réglages, cet après-midi

Ouvrez le volet Supply Chain de l'espace de travail et vérifiez que le filtre d'âge est actif avec un seuil vivable. Deux jours est la valeur par défaut, et c'est le contrôle le moins cher dont vous disposiez contre un fork trojanisé fraîchement publié. Ouvrez ensuite Prompt Injection et activez les deux détecteurs ; chacun télécharge son modèle une fois, et après cela l'analyse est locale et gratuite.

Une règle qui rétrécit la question

Dans Guardrails, mettez Unmatched traffic sur Deny et listez ce dont le travail a besoin : allow web api.github.com, allow web registry.npmjs.org, default deny. Un voleur qui ne peut pas joindre son serveur de commande est un fichier sur un disque que vous êtes sur le point de jeter, et l'enregistrement pousse la règle vers les sessions en cours sans redémarrage.

L'absence bat l'évaluation

Nous arrivons sans cesse au même endroit par des chemins différents. Un proxy de sortie qui faisait confiance à un nom que l'agent pouvait écrire. Un garde-fou de commandes qui lisait bash autrement que bash. Une passerelle IA dont la vérification de clé ratée retombait sur un objet d'authentification vide. Dans chaque cas, un composant faisait honnêtement son travail d'évaluation de quelque chose, et l'évaluation s'est cassée.

DUSTMAKER aiguise l'argument, car il ne demande aucune évaluation cassée. Il demande qu'une évaluation ait lieu sur un contenu écrit par UNC6780. Donnez un fichier à un lecteur et le lecteur s'en fait une idée ; qui a fourni le fichier règle le reste.

L'évaluation est une primitive plus faible que l'absence, et nous ne cessons de confier à l'évaluation le travail que l'absence fait gratuitement. On ne discute pas avec une date de publication, et une probabilité n'a aucune disposition à se rétracter. Un voleur d'identifiants tournant à pleins privilèges dans une VM jetable, parcourant tous les fichiers de configuration qu'il connaît, en ressort avec une poignée de leurres et une socket qu'il ne peut pas contourner.

Mettre un modèle dans votre pipeline de revue était le bon choix. Assurez-vous que les contrôles qui arrêtent les choses sont ceux à qui rien dans le dépôt ne peut parler. Installez Bromure Agentic Coding et donnez à votre agent des contrôles qui n'ont jamais à lire le fichier de l'attaquant.