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

La doc nommait un paquet que personne ne possédait

Des chercheurs ont scanné 6 214 domaines d'entreprise à la recherche de llms.txt, le fichier lisible par machine que les entreprises publient désormais pour les agents IA, et y ont trouvé 237 paquets et domaines cités que personne n'avait jamais enregistrés. Ils en ont revendiqué quelques-uns. Le premier agent de code au sein d'un réseau du Fortune 500 en a installé un en moins de quatre minutes. L'attaquant n'a touché ni mainteneur, ni registre, ni dépôt. Le seuil d'âge de Bromure Agentic Coding est actif dès l'installation, à deux jours, et un nom qu'un attaquant revendique est par construction une publication toute neuve.

Un éditeur a écrit dans sa propre documentation une commande d'installation pour un paquet qu'il n'a jamais publié. Le nom est resté là, libre, à la disposition de qui le voudrait. Les agents de code ont lu la doc, l'ont crue, et ont lancé l'installation.

Quelque part au cours des deux dernières années, vos outils de développement se sont mis à livrer un nouveau fichier. Il se trouve à côté de robots.txt, il s'appelle llms.txt, et il contient un résumé en texte brut du produit, écrit pour une machine plutôt que pour une personne. Ce que fait l'API. Quels points d'accès comptent. Quel paquet installer pour démarrer. Les agents de code le récupèrent et, comme il vient du domaine de l'éditeur lui-même, ils le prennent pour un fait acquis.

Un chercheur du nom d'Alon Hertz, qui travaille sous le nom de PANDEX, est allé voir ce que ces fichiers disent à un agent de faire. Sa méthode tient en trois étapes et n'utilise aucun exploit : récupérer llms.txt et llms-full.txt depuis les annuaires d'entreprises, lancer de vrais agents dessus dans des bacs à sable et observer ce vers quoi ils se tournent, puis vérifier chaque nom de paquet et chaque nom d'hôte obtenus auprès de npm, de PyPI et des registres de domaines. Il décrit son outillage sur whatwouldai.do, sous une accroche qui s'est révélée être la conclusion : les agents agiront sur n'importe quoi.

Le scan a couvert 6 214 domaines actifs, tirés d'entreprises du Fortune 500, des grandes plateformes technologiques, de la fintech et de sous-traitants de la défense, et plus de 8 000 de ces fichiers. Dans 120 d'entre eux, la chose que le fichier demandait à l'agent d'installer n'existait pas. Sur l'ensemble du corpus, 237 noms de paquets et d'hôtes cités étaient libres : disponibles sur npm, PyPI, RubyGems, NuGet, crates.io et Packagist, ou libres comme sous-domaines chez Vercel, Render, Fly et Netlify. N'importe qui pouvait les prendre pour le prix d'une inscription.

Alors les chercheurs en ont pris une poignée, ont publié sous ces noms des paquets inoffensifs munis d'une balise qui appelle à la maison lors de l'installation, et ont attendu.

Le premier retour est arrivé en moins de quatre minutes. Le deuxième dans l'heure. Au fil des jours suivants, ils ont enregistré des installations depuis des dizaines d'organisations, dont plusieurs environnements du Fortune 500, provoquées par des agents tournant sur Claude d'Anthropic, Codex d'OpenAI et Hermes de Nous Research. Bruce Schneier a repris l'étude le 4 septembre et l'a lue comme elle le mérite : la frontière entre données et code exécutable s'efface. Un chercheur cité dans ce billet le dit en moins de mots. « Le modèle de confiance est cassé. » Les agents « traitent la doc des éditeurs comme une vérité de terrain et ne la remettent pas en question ».

Clerk a documenté un paquet qu'il n'a jamais livré

L'exemple le plus net vient d'un fournisseur d'authentification, dont la documentation dit au lecteur d'exécuter :

npx clerk-next-fix-auth-protection

Clerk n'a jamais publié de paquet autonome sous ce nom. Peut-être la commande était-elle un raccourci, peut-être visait-elle quelque chose d'interne, peut-être un rédacteur s'est-il trompé. La raison importe peu, car le nom avait l'air vrai pour un agent. Quelqu'un d'autre l'a enregistré. Les bases d'avis de sécurité référencent désormais ce paquet sous MAL-2026-11069, et il a fait ce qu'on construit pour de la reconnaissance : les hooks d'installation npm se sont déclenchés et ont expédié le nom du compte qui installait, le nom d'hôte de la machine, le répertoire de travail et un horodatage.

C'est une carte de l'environnement de compilation de quelqu'un, collectée à l'intérieur de son réseau par son propre agent de code, qui suivait la doc de son propre fournisseur.

Cinq étapes ordinaires, aucune compromission dans la chaîne1 · l'éditeur publievendor.com/llms.txtécrit pour des agents, pas des humainsnpx clerk-next-fix-auth-protection2 · le nom est libreaucun paquet n'a jamais été publiésous ce nom237 noms de ce type dans 120 fichiers3 · quelqu'un le revendiqueune publication npm ordinaireMAL-2026-11069la charge est dans les hooks d'install4 · l'agent lit la doc et la croitClaude, Codex et Hermes ont tous été vus lancer l'installationla commande venait du domaine de l'éditeur, il n'y avait rien à mettre en douteaucun identifiant volé · aucun dépôt compromis · aucun mainteneur hameçonné5 · les hooks appellentnom d'utilisateur · nom d'hôterépertoire de travail · horodatageune carte de l'environnement de buildLe seul vrai travail de l'attaquant a été de lire un fichier que le fournisseur de la victime a publié exprès.
La chaîne ne contient aucune compromission. Un éditeur publie une doc lisible par machine qui nomme une commande d'installation ; le paquet n'a jamais été publié, donc le nom reste libre sur le registre ; un attaquant l'enregistre ; l'agent récupère la doc comme vérité de terrain et lance l'installation ; les hooks d'installation renvoient l'environnement. Chaque maillon est une opération normale exécutée correctement.

Pourquoi le conseil habituel pointe droit sur la charge

Tous les guides sur la chaîne d'approvisionnement vous disent de vérifier le nom d'un paquet dans la documentation officielle. Ce conseil échoue ici, parce que la documentation officielle est justement d'où vient le mauvais nom.

Inversez le problème dont nous parlions en juillet et vous obtenez celui-ci. Là, un agent hallucine un nom de paquet plausible et un attaquant enregistre les noms qu'il a tendance à inventer, donc le maillon faible est la supposition du modèle. Ici, le modèle ne suppose rien. Il va chez l'éditeur, lit ce que l'éditeur dit, le suit, et c'est cette séquence scrupuleuse qui le perd.

Une compromission de registre a elle aussi une autre forme. Quand un workflow de publication a livré dix versions empoisonnées d'un vrai paquet le mois dernier, il fallait d'abord que quelque chose casse : un workflow qui faisait confiance à un commentaire. Ici, rien ne casse. Un rédacteur technique tape une commande qui ne correspond pas tout à fait à un artefact publié, ce qui est l'erreur la plus ordinaire du logiciel, et l'écart entre le nom et l'artefact devient une primitive d'exécution que n'importe qui sur Internet peut ramasser.

Ce que le scan a trouvé, et la vitesse de la preuve6 214domaines actifs scannésFortune 500, tech, défense8 000+fichiers de doc pour agentsllms.txt · llms-full.txt120fichiers nommant une chosejamais enregistrée237noms libres, six registresplus Vercel, Render, Fly, Netlifypuis ils en ont enregistré un échantillon et ont attendupremier retour d'installation depuis un environnement Fortune 500moins de 4 minutesdeuxième retourdans l'heure · des dizaines d'organisations
L'entonnoir de l'étude. Sur 6 214 domaines d'entreprise portant des docs destinées aux agents, 120 fichiers nommaient quelque chose de non enregistré, soit 237 noms libres au total. En enregistrer un échantillon a produit un retour d'installation depuis un réseau du Fortune 500 en moins de quatre minutes.

La couche qui attrape ça est déjà active

Bromure Agentic Coding exécute chaque agent de code dans une VM Linux virtualisée par le matériel sur votre Mac, et chaque paquet que l'agent récupère passe par un proxy du côté hôte de cette frontière, avant qu'un seul octet n'atteigne la VM. Plusieurs couches s'appliquent là. Pour cette histoire, l'une d'elles fait le travail à elle seule, et c'est celle qu'un nouvel espace de travail a déjà activée.

Le seuil d'âge fait d'un nom fraîchement revendiqué un non-événement. Un espace de travail refuse les versions de paquets plus jeunes qu'un seuil, fixé à deux jours par défaut. Le proxy l'applique en réécrivant la liste des versions du registre au passage : il retire les versions plus jeunes que le seuil et repointe latest sur la plus récente survivante. Un paquet publié pour la première fois ce matin n'a aucune version assez ancienne pour survivre à ce filtre, donc du point de vue de l'agent il n'y a rien à installer sous ce nom. Si l'agent demande directement une version trop fraîche, la récupération revient en HTTP 451 dont le corps indique l'âge réel du paquet et le minimum exigé, et npm affiche ce texte tel quel.

Relisez l'attaque avec ça en place. Toute la technique dépend d'un nom qui passe de non publié à publié au moment choisi par l'attaquant. Cette transition est une toute nouvelle publication. La seule propriété que l'attaquant ne peut pas éviter est celle sur laquelle la politique par défaut s'appuie.

Le retrait des scripts d'installation supprime exactement le mécanisme qu'a utilisé la charge. Le paquet observé faisait son travail dans les hooks npm preinstall, install, postinstall et prepare. Avec le retrait des scripts activé, le proxy décompresse chaque archive npm à la volée, supprime ces quatre clés de package.json, efface dist.integrity et dist.shasum des métadonnées pour que la vérification d'empreinte de npm passe quand même, puis recompresse. Le paquet s'installe ; la balise n'a plus d'où partir. Les paquets qui ont besoin de leurs hooks, comme les compilateurs de bindings natifs tels que better-sqlite3, passent sur une liste d'autorisation par espace de travail et les conservent.

Le filtrage par réputation connaît ce paquet par son nom. MAL-2026-11069 est un avis de malware. Avec socket.dev choisi comme filtre de paquets de l'espace de travail, son blocage des paquets compromis se déclenche sur les signaux de malware et de typosquat à n'importe quelle sévérité ; la vérification OSV gratuite couvre les huit écosystèmes interceptés sans clé d'API. Quand une source de réputation ne peut pas rendre de verdict, que ce soit à cause d'une limitation de débit, d'un réseau coupé ou d'un écosystème qu'elle ne couvre pas, Bromure échoue en position fermée plutôt que d'autoriser la récupération. Le téléchargement se met en pause et vous demande, paquet par paquet et version par version, si bien qu'un attaquant capable de provoquer un échec de consultation n'y gagne rien.

La reconnaissance qu'il récolte appartient à la VM. Tous les titres sur cette étude disent la même chose : des agents installent du code inconnu sur des réseaux d'entreprise. La moisson de la balise, c'était le nom d'utilisateur qui installait, le nom d'hôte de la machine et le répertoire de travail. Dans un espace de travail Bromure, ces trois champs décrivent un invité Linux sur votre Mac, et non un hôte de build joint au domaine avec un nom interne qui mérite de figurer sur une liste de cibles. Les identifiants qui se trouvent dans cette VM sont des leurres dont les vraies valeurs n'existent que dans la mémoire du proxy hôte. Si du code hostile tente d'en porter un vers un hôte pour lequel il n'a pas été frappé, le proxy bloque la requête et met la VM en pause en plein vol.

Le refus par défaut répond à l'autre moitié de l'étude. Une bonne part des noms libres étaient des noms d'hôtes plutôt que des paquets : domaines expirés et sous-domaines gratuits chez Vercel, Render, Fly et Netlify, présents dans des docs que les agents vont chercher. Le pare-feu de sortie de l'espace de travail est une table de règles ordonnée avec un réglage Unmatched traffic ; mettez-le sur Deny et la VM ouvre des connexions vers les hôtes que vous avez listés et rien d'autre, sur n'importe quel protocole. Deux composants appliquent cela hors de l'invité : le commutateur réseau virtuel apparie chaque flux par adresse IP de destination, et le proxy hôte apparie de nouveau par nom de serveur TLS. Une balise visant un domaine que quelqu'un a enregistré mardi dernier ne quitte pas la machine. Les modifications de règles atteignent les sessions en cours sans redémarrage.

Chaque tentative laisse une ligne. Au niveau de trace Activity only, le proxy écrit un enregistrement de métadonnées par requête sortant de la VM, portant l'horodatage, l'hôte, le port, la méthode, le chemin, le statut, la latence et les octets, et il ne stocke aucun corps de requête. bromure-cli trace hostnames my-workspace affiche chaque hôte distinct contacté par l'espace de travail, avec les décomptes. Vous voyez un domaine de rappel qu'aucun de vos ingénieurs ne reconnaît le jour même, sur votre propre machine, au lieu de le lire dans le jeu de données de quelqu'un d'autre des mois plus tard.

Un agent sur une machine de dévnpx clerk-next-fix-auth-protectiontout droit sorti de la doc de l'éditeurce qui suit, entièrement dans le réseaule registre sert ce qui a été publié, aussi récent soit-ilpostinstall s'exécute avec l'identité du développeurnom d'utilisateur, hôte et cwd sont des faits internesle rappel part où il veutaucune trace par requête de la destinationl'installation réussit et ressemble à toutes lesautres installations lancées par l'agent ce jour-làPremier rappel : moins de quatre minutes.Vous l'apprenez quand un inconnu publie l'étude.Un espace de travail Bromurenpx clerk-next-fix-auth-protectionla même commande, tirée de la même docce qui suit, entièrement sur l'hôteseuil d'âge : versions de moins de deux jours retiréesun nom pris aujourd'hui n'a rien d'installablestrip : les quatre hooks d'installation sont retirésnom d'utilisateur, hôte et cwd décrivent une VM Linuxsortie non appariée refusée, filtrée par IP de destinationle refus est un 451 lisible par l'agent, et une ligneau Journal de sécurité avec l'âge réel du paquetPremier rappel : aucun.Vous l'apprenez de votre machine, le jour même.
À gauche : l'installation s'exécute sur une machine de développement à l'intérieur du réseau de l'entreprise, le registre est consulté directement, les hooks d'installation s'exécutent avec l'identité du développeur, et le rappel repart sans laisser de trace. À droite : la même commande, passée par un proxy côté hôte qui filtre la version fraîchement publiée hors des métadonnées, retire les hooks s'il en arrive là, refuse toute sortie non prévue, et écrit une ligne pour chaque requête.

Ce qui est déjà actif

Un nouvel espace de travail arrive avec le seuil d'âge activé à un minimum de deux jours, et c'est le réglage sur lequel cette attaque bute. Relevez-le si votre travail supporte l'attente, jusqu'à 90 jours sur le sélecteur, et utilisez Exempt packages pour ceux que votre propre équipe publie et installe dans l'heure, sous la forme npm:notre-paquet.

Ce qui vaut la peine d'être activé aujourd'hui

Dans le volet Supply Chain de l'espace de travail, activez le retrait des scripts d'installation et choisissez un filtre de paquets. Dans Guardrails, mettez Unmatched traffic sur Deny et listez les hôtes dont le travail a besoin. Dans Tracing, Activity only conserve la piste des hôtes contactés et rien du contenu. Chacune de ces modifications s'applique aux sessions déjà en cours.

Personne ne relit un fichier écrit pour des machines

Le scanner PANDEX cherche encore une chose. À côté des paquets et des domaines libres, il traque les instructions destinées à l'agent plutôt qu'au lecteur : des directives plantées dans le fichier, parfois dissimulées par des astuces Unicode et des caractères de largeur nulle. Le pipeline de build d'un éditeur génère llms.txt, votre agent le consomme, et personne dans aucune des deux entreprises ne lit ce qui est passé entre elles.

Bromure traite une page récupérée comme une entrée non fiable. Le contenu que l'agent tire de l'extérieur, qu'il s'agisse du contenu de fichiers, de pages web, de corps de tickets ou de sorties de commandes, atteint le modèle sous forme de segment tool_result. Avec le détecteur de code source activé, un modèle PromptGuard tournant sur votre Mac note ces segments sur le fil avant que le modèle n'agisse dessus, et vous choisissez s'il journalise, demande ou bloque. Aucun contenu ne part ailleurs pour être analysé.

Nous avons passé une décennie à apprendre à vérifier des artefacts : signer le paquet, épingler l'empreinte, attester la compilation. Cette étude parle de l'étape d'avant, celle où un agent décide quel artefact demander, à partir d'un document qui ne porte aucune signature et qu'un rédacteur technique a écrit dans l'urgence. La documentation est devenue une entrée exécutable, et les registres sont pleins de noms libres qui attendent que quelqu'un remarque qu'un éditeur les a mentionnés.

Passez votre propre llms.txt au grep à la recherche de noms que vous n'avez jamais publiés. C'est le travail d'une matinée et ça vous retire de la liste de cibles de quelqu'un. Puis donnez à votre agent une machine où un paquet publié il y a quatre minutes n'est pas installable, et où vous pouvez voir partout où il est allé. Installez Bromure Agentic Coding et mettez le chemin d'installation sur l'hôte.