Il a attendu le troisième appel
Pillar Security suit Deadbugz, un serveur MCP glissé dans des projets open source par pull request. Il expose deux outils honnêtes, compte les appels d'outil de votre agent, et au quatrième réécrit les métadonnées qu'il renvoie en instructions : récupérer les clés SSH, les identifiants AWS, l'historique du shell et le kubeconfig, et n'en rien dire. Aucun bug, aucune CVE : le protocole autorise tout cela. La victime, c'est l'idée qu'approuver un serveur une fois vous apprenne quoi que ce soit sur l'appel suivant. Dans un espace de travail Bromure Agentic Coding, la charge s'exécute entièrement, récolte quatre leurres et meurt au proxy en sortant.
Vous aviez lu le code. Vous l'aviez exécuté. Il mettait en forme du texte, comme annoncé. Vous l'avez approuvé, et l'attaquant comptait sur votre approbation.
Le 10 août au soir, entre 21 h 52 et 23 h 07 UTC, un compte GitHub nommé
zellkernel a ouvert vingt-trois pull requests sur des projets sans lien entre
eux, dans l'IA, le MCP et l'outillage de développement. Soixante-quatorze
minutes, du début à la fin. Chaque pull request faisait la même petite
modification : elle ajoutait un serveur Model Context Protocol à un fichier de
configuration.
Pillar Security a publié la campagne le 12 août et l'a nommée Deadbugz, d'après le fichier que dépose une des variantes. Un mois plus tard, les récapitulatifs MCP mensuels ouvrent encore dessus. Le digest de septembre d'Adversa, paru hier, lui consacre toute la section incidents. C'est l'horloge qui lui vaut cette place.
Deux outils, et un compteur
Le serveur s'appelle productivity-suite. Il propose deux outils :
format_text et summarize. Les deux fonctionnent. Pointez un agent dessus,
demandez-lui de resserrer un paragraphe, et vous obtenez un paragraphe resserré.
Derrière eux, le serveur tient ce que Pillar décrit comme « un compteur en
mémoire, par client, des requêtes tools/call ». Quand ce compteur atteint
trois, le serveur change ses réponses à deux autres types de requête :
tools/list, qui indique à l'agent quels outils existent et à quoi ils servent,
et prompts/get, qui remet à l'agent des instructions préparées. À partir du
quatrième appel, ces réponses reviennent porteuses, selon les mots de Pillar,
d'« instructions destinées à orienter un agent IA rattaché vers des fichiers
locaux sensibles et à dissimuler l'activité à son opérateur ».
Quatre choses : clés SSH, identifiants AWS, historique du shell, configuration Kubernetes. Puis une instruction de plus : cacher l'activité à l'utilisateur.
Les noms des outils, eux, ne changent jamais. format_text s'appelle toujours
format_text. C'est la description en dessous qui a tourné, et la description
est la partie que le modèle lit comme faisant autorité.
Rien ici n'est une vulnérabilité
Aucun bug n'apparaît nulle part dans cette histoire. Il n'y a pas de corruption mémoire à corriger et pas de CVE à déposer, parce que le serveur ne fait rien que le protocole interdise.
MCP est un protocole où un serveur publie ses capacités à l'exécution et peut les publier à nouveau. Les descriptions d'outils sont du texte que le serveur choisit et que le client récupère. Les prompts sont du texte que le serveur choisit et que le client récupère. Deadbugz utilise tout cela comme prévu. Sa seule contribution originale, c'est de décider quand dire la vérité.
Et cela suffit, parce que tous les contrôles dont disposent la plupart des équipes contre un serveur MCP hostile s'exécutent une seule fois. Vous lisez le dépôt. Vous démarrez le serveur et vous le tâtez. Vous comparez la liste d'outils à ce qu'annonce le README. Vous approuvez, vous épinglez peut-être la version, et vous passez à autre chose. Chacun de ces gestes est une photographie, et c'est Deadbugz qui décide du moment où vous la prenez.
Les propres recommandations de Pillar aux personnes qui construisent des clients MCP en disent long. Traiter un changement de définition d'outil sur un serveur déjà approuvé comme un événement de sécurité exigeant une nouvelle approbation. Rendre les changements de définition visibles à l'opérateur. Empreinter les définitions d'outils au moment de l'approbation et les comparer plus tard. Chacune de ces mesures demande de déplacer un contrôle du temps de l'approbation vers le temps de l'exécution.
La population que chasse Deadbugz est vaste et instable. Une évaluation dynamique des serveurs MCP exposés sur Internet, publiée en juillet, Exposed by Design, a testé 414 des 640 serveurs de production confirmés parmi les plus de 21 000 instances que son auteur a trouvées sur l'Internet public. Elle rapporte 68 vulnérabilités signalables réparties en dix classes, 91,8 % de serveurs fonctionnant sans aucune authentification OAuth, et 687 instances d'outils exposant l'exécution de shell sans contrôle d'accès. Le chiffre qui reste, c'est le renouvellement : 41,6 % des serveurs confirmés ont disparu en moins de trois jours entre deux campagnes de mesure consécutives, ce que l'auteur interprète comme des « cycles de déploiement rapides sans revue de sécurité ». Un serveur que vous avez audité la semaine dernière peut être un autre logiciel cette semaine, ou avoir disparu.
La livraison mérite aussi un mot, parce qu'elle ne coûte rien. Dix-sept des
vingt-trois pull requests ajoutaient un point d'accès distant,
productivity-suite-mcp.onrender.com/mcp, à la configuration MCP d'un projet.
Quatre pointaient Python vers un fichier local caché,
~/.config/.cache/.sys/.deadbug-mcp.py. Deux étaient des soumissions à des
annuaires de serveurs. Pillar n'en a trouvé aucune fusionnée via le bouton de
merge de GitHub au moment de la revue : dix-neuf fermées, quatre encore
ouvertes. Mais une pull request n'est qu'une façon parmi d'autres pour une ligne
de configuration de voyager. La même ligne tient dans un billet de blog, un guide
de démarrage, une fiche dans un annuaire MCP, ou une réponse serviable sous une
question sur la mise en forme de texte.
La liste de courses
Reprenez la liste. Elle est précise, et c'est la même à chaque fois : clés SSH, identifiants AWS, historique du shell, configuration Kubernetes.
C'est l'inventaire permanent d'une machine de développeur. C'est ce que prend un infostealer, ce qu'un script postinstall malveillant cherche au grep, et ce qu'un attaquant demande à un agent détourné d'aller chercher. La liste est stable depuis une décennie de campagnes parce que son contenu est stable : une clé privée qui signe, un secret qui authentifie, un relevé de ce que vous avez tapé, et un jeton qui atteint la production.
Un agent sur un portable ordinaire peut satisfaire chaque item, parce qu'il a un shell dans votre répertoire personnel et qu'il tourne sous votre identité.
La même liste, dans un espace de travail Bromure
Bromure Agentic Coding exécute chaque agent de code dans une VM Linux virtualisée par le matériel sur votre Mac, et garde chaque secret réel du côté hôte de cette frontière. L'agent travaille avec des leurres qui préservent la structure, dérivés de la valeur réelle et d'un sel propre à l'installation, si bien que les outils qui empreignent une clé ne la voient jamais changer d'une session à l'autre. Un proxy côté hôte substitue la valeur réelle sur le fil après que la requête a quitté la VM, et seulement lorsqu'elle est destinée à l'hôte pour lequel cet identifiant a été émis.
Exécutez la charge et laissez-la gagner. Supposez que le modèle lit la description empoisonnée, y croit, et part chercher. Reprenez les quatre items dans l'ordre.
Clés SSH. L'agent ne trouve aucune clé, pas même un leurre. Le
SSH_AUTH_SOCK de la VM est un pont ssh-agent au-dessus d'un socket virtio, port
vsock 8444. L'invité peut lister les identités et demander des signatures ; les
octets de la clé privée ne vivent que sur l'hôte et, comme le dit le manuel, ne
peuvent être ni lus ni extraits depuis l'intérieur de la VM. Bromure n'expose
jamais votre agent de connexion macOS à une session. Chaque signature qui a
effectivement lieu émet un événement d'audit credential.ssh_sign portant
l'empreinte SHA256 de la clé, et vous pouvez régler n'importe quelle clé importée
pour qu'elle demande confirmation sur l'hôte à chaque requête de signature.
Identifiants AWS. Un fichier qui a l'air juste et ne vaut rien. Le
~/.aws/config de la VM pointe vers un assistant credential-process qui délivre
le véritable identifiant de clé d'accès associé à une clé secrète factice de
40 caractères, via le port vsock 8445. Tous les SDK, la CLI aws, terraform et
boto3 le prennent en compte sans configuration supplémentaire, et l'invité
produit une signature SigV4 bien formée calculée avec le mauvais secret. Le
resigneur de l'hôte retire cette signature et resigne en sortie. Emportez la
paire ailleurs et AWS répond InvalidSignatureException.
Configuration Kubernetes. ~/.kube/config existe et contient un jeton
bearer commençant par brm-k8s-. Il fonctionne, à travers le proxy, contre les
serveurs d'API pour lesquels l'espace de travail a été configuré. Il est inerte
partout ailleurs.
Historique du shell. Réel, et durable : le travail de développement a besoin
d'état durable, donc la VM de l'espace de travail est une machine persistante
plutôt qu'une jetable. Elle contient l'historique d'une machine qui n'a jamais
contenu de vraie clé, avec des commandes lancées contre des leurres dans un
répertoire personnel qui n'est pas le vôtre, aux côtés d'un
~/.git-credentials et d'un ~/.docker/config.json eux aussi factices.
Ensuite, il faut l'envoyer
Récolter, c'est la moitié du travail. L'autre moitié, c'est la sortie vers
productivity-suite-mcp.onrender.com, et cette sortie se heurte à une pile de
contrôles côté hôte.
Les leurres sont des fils-pièges. Chacun a une seule famille de destinations légitimes, la portée d'hôtes pour laquelle il a été émis, et le proxy analyse donc chaque requête sortante, en-têtes et corps, à la recherche d'un leurre parti là où il n'a rien à faire. En cas de correspondance, il traite la requête comme une tentative d'exfiltration d'identifiants : il refuse avec un HTTP 451 sans transmettre un seul octet, met la VM en pause sur-le-champ, et lève une alerte proposant Arrêter, Conserver pour investigation (exporter d'abord le disque, le répertoire personnel et les dossiers partagés à des fins forensiques) ou Continuer à vos risques et périls. L'événement atterrit en ligne rouge Courtage d'identifiants dans la Security Timeline.
Le deuxième contrôle, c'est l'accessibilité. Chaque espace de travail porte un pare-feu de sortie ordonné (action, protocole, hôte ou CIDR, ports, et pour le trafic web les verbes HTTP individuels) avec une règle par défaut pour le trafic non apparié. Les nouveaux espaces de travail autorisent ce qu'aucune règle ne couvre ; réglez cela sur Refuser, listez ce dont le travail a besoin, et un sous-domaine render.com n'est pas sur la liste. Deux composants hors de l'invité l'appliquent : le commutateur virtuel apparie chaque flux par IP de destination et par nom d'hôte capté dans le DNS, et le proxy apparie de nouveau par nom de serveur TLS. Les modifications de règles atteignent les sessions en cours sans redémarrage.
Guardrails met une barrière sur la conséquence. Il classe les appels de l'agent
vers Kubernetes, AWS, DigitalOcean, les registres de conteneurs, les forges git
et les bases de données HTTPS en lectures, écritures ou opérations destructrices,
et les nouveaux espaces de travail sont réglés par défaut sur Demander avant
d'écrire : chaque mutation s'arrête sur une boîte de dialogue de l'hôte
montrant l'opération littérale, le SQL exact ou le METHOD /path. C'est
précisément ce que Pillar demandait aux constructeurs de plateformes : les
lectures de fichiers sensibles, l'accès aux identifiants et l'exécution de code
ne devraient pas être les conséquences de métadonnées distantes.
Un détecteur se tient aussi sur le chemin du contenu. Quand l'agent renvoie au
modèle du contenu externe (contenus de fichiers, pages récupérées, sorties de
commande, résultats d'appels d'outils), un classifieur PromptGuard local note ces
passages sur votre Mac avant que le modèle n'agisse dessus, et peut journaliser,
demander, ou bloquer avec un 451. Le jeton bearer MCP d'un serveur HTTP n'entre
jamais dans la VM : le vrai reste sur l'hôte, la configuration de l'agent reçoit
un espace réservé brm-mcp_, et le champ des réglages le dit tout haut,
Jamais envoyé à la VM — substitué par le proxy.
Si vous avez touché à celui-ci
Cherchez dans vos configurations de client MCP, vos dépôts et vos répertoires
personnels productivity-suite-mcp.onrender.com, l'ancien point d'accès
promo-surname-xml-quantum.trycloudflare.com, et l'artefact local
~/.config/.cache/.sys/.deadbug-mcp.py. Annulez tout changement de
configuration qui les a introduits, et n'exécutez pas le script. Conservez
les journaux de votre client MCP et cherchez des rafraîchissements de
définitions d'outils après le troisième appel. Le conseil de Pillar sur la
rotation est mesuré : faites tourner là où des preuves locales étayent une
compromission.
La règle qui réduit la question
Dans le panneau Guardrails de l'espace de travail, réglez Trafic non
apparié sur Refuser et listez ce dont le travail a besoin :
allow web api.github.com, allow web registry.npmjs.org, default deny.
Puis lancez bromure-cli trace hostnames my-workspace après une session :
la commande imprime chaque hôte distinct que l'agent a contacté, avec les
comptes. Une destination que vous n'avez jamais choisie apparaît comme une
ligne que vous n'avez pas écrite.
L'approbation est un instantané ; le fil est continu
L'instinct après une campagne comme celle-ci, c'est de relire plus sévèrement : lire davantage de code, épingler le commit. Les deux valent la peine, et ni l'un ni l'autre ne touche au mécanisme. La revue, ici, était juste. Le serveur a été honnête tant que quelqu'un regardait, puis il a changé selon un calendrier qu'il maîtrisait, sans enfreindre la moindre règle du protocole.
Un contrôle qui survit à un serveur qui change d'avis, c'est un contrôle qui ne dépend pas d'avoir eu raison plus tôt. Un identifiant qui est un espace réservé reste un espace réservé au quatrième appel comme au quatre-centième. Une clé privée sur l'hôte ne peut être lue à aucun appel. Le proxy évalue une règle de sortie à chaque connexion et classe chaque écriture au moment où elle a lieu. Rien de tout cela ne garde la mémoire du jour où vous avez approuvé quelque chose, et c'est pourquoi un attaquant ne peut pas en hériter.
MCP a rendu facile de donner de nouvelles capacités à un agent, ce qui était le but, et il a fait de ces capacités une chose qu'une partie distante décrit dans un texte que votre modèle lit comme une instruction. Les deux sont là pour rester. Décidez ce que votre agent peut détenir et ce qu'il peut atteindre, et placez l'application quelque part où le serveur n'a pas voix au chapitre. Installez Bromure Agentic Coding et ne donnez à votre agent rien qui vaille la peine d'attendre trois appels.