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.
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.
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.
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.