Le dépôt était bel et bien celui de Microsoft
Les 5 et 6 juin 2026, le ver Miasma a poussé du code voleur de credentials dans 73 dépôts répartis sur quatre des organisations GitHub propres à Microsoft — Azure, Azure-Samples, microsoft, MicrosoftDocs — dont Azure/functions-action, l'Action de déploiement officielle, et durabletask, un dépôt qui avait déjà été nettoyé une fois en mai. Cette fois, la charge utile n'a pas attendu npm install. Elle s'est déclenchée à l'instant où un développeur ouvrait le dépôt dans Claude Code, Cursor, Gemini CLI ou VS Code. Voici pourquoi le signal de confiance — « c'est un dépôt Microsoft » — était de nouveau la surface d'attaque, et ce qui change quand l'agent qui l'ouvre vit dans une VM Bromure par profil, derrière un courtier de credentials, un guardrail lecture/écriture et un cooldown de packages.
Il y a une semaine, le ver est entré par le scope npm de Red Hat et
s'est déclenché via un hook preinstall. Cette semaine, il est entré
par le GitHub de Microsoft et n'a eu besoin d'aucune installation.
Miasma a planté de la configuration à portée projet dans 73 dépôts
appartenant à Microsoft — Azure/functions-action, l'Action de
déploiement officielle, parmi eux — et la charge utile s'est exécutée
à l'instant où un développeur ouvrait le dépôt dans Claude Code,
Cursor, Gemini CLI ou VS Code. Le clonage n'était pas le déclencheur.
L'ouverture dans votre agent l'était.
Un développeur qui clone Azure/functions-action pour déboguer un
déploiement qui échoue n'hésite pas, et l'agent qu'il y pointe non
plus. C'est un dépôt de première partie issu de l'organisation Azure
propre à Microsoft — la source canonique de l'Action GitHub que la
moitié de l'écosystème référence comme Azure/functions-action@v1. Il
n'y a aucun mainteneur à vérifier, parce que le mainteneur est
Microsoft. Il n'y a aucun nom à scruter, parce que le nom est épelé
exactement comme il devrait l'être. Donc le dépôt est ouvert, et
l'agent lit la configuration du projet comme le fait tout outil de
codage moderne à l'ouverture d'un dossier — et l'un de ces fichiers de
config pointe vers une commande, et la commande est un blob d'environ
4,3 mégaoctets qui se met à lire le système de fichiers à la
recherche de clés.
Nous avons décrit la moitié npm de cette campagne exacte il y a sept jours. Miasma est le même ver — une variante du code « Mini Shai-Hulud » que TeamPCP a publié au grand jour à la mi-mai — et le fait inconfortable qu'il démontre est le même, vissé d'un cran de plus : source réputée n'est pas un contrôle de sécurité. On a l'impression que ç'en est un. La plupart des conseils de supply-chain s'appuient dessus. Et le 5 juin, ça n'a strictement rien acheté, doublement : l'espace de noms était celui de Microsoft, et l'exécution n'est pas venue d'un package que vous avez choisi d'installer. Elle est venue de l'ouverture d'un dossier.
Ce que Miasma a fait aux dépôts de Microsoft.
Les 5 et 6 juin 2026, GitHub a désactivé 73 dépôts répartis sur quatre organisations GitHub de Microsoft après que des commits malveillants y aient été poussés, selon The Hacker News et une analyse détaillée de StepSecurity. Redmond Magazine l'a couvert le 8 juin. La répartition :
- Azure — 49 dépôts, dont
Azure/functions-action(l'Action de déploiement officielle de Functions) et les language workers pour .NET, Python, Java, Go et PowerShell. - microsoft — 10 dépôts.
- Azure-Samples — 13 dépôts.
- MicrosoftDocs — 1 dépôt.
La forme, réduite à sa mécanique :
- Le point d'entrée était un compte de contributeur précédemment
compromis disposant d'un accès en écriture, utilisé pour pousser
des commits malveillants directement dans les dépôts — taggés
[skip ci]de sorte que les changements se glissaient devant les contrôles CI/CD qui auraient autrement tourné. - Le commit plantait de la configuration à portée projet — le genre de fichier qu'un agent de codage ou un IDE lit et exécute automatiquement quand vous ouvrez le dossier : une tâche d'éditeur, un hook d'agent, un serveur MCP défini par le projet. C'est la même classe de frontière de confiance que TrustFall d'Adversa AI a démontrée à travers Claude Code, Cursor CLI, Gemini CLI et Copilot CLI — les quatre exécutent la configuration définie par le projet juste après l'invite de confiance du dossier.
- La charge utile — environ 4,3 Mo de code obfusqué — s'exécutait
quand le dépôt était ouvert dans Claude Code, Gemini CLI, Cursor ou
VS Code, ou lancé via un script
npm test. Pas au clonage seul. C'est le fait de pointer votre agent vers l'arborescence clonée qui l'exécutait. - À l'exécution, elle balayait l'hôte à la recherche de tokens
GitHub, clés AWS, service principals Azure, credentials GCP, tokens
de publication npm et PyPI, clés SSH et fichiers
.env, puis utilisait l'accès volé pour se commiter plus loin — ce qui en fait un ver plutôt qu'un coup unique.
Un détail vaut qu'on s'y arrête : Azure/durabletask figurait parmi
les dépôts touchés — et il avait déjà été compromis en mai dans la
campagne TeamPCP, puis nettoyé. Un dépôt qui avait été remédié une fois
a été ré-empoisonné cinq semaines plus tard. Le nettoyage n'est pas un
état que l'on atteint et conserve ; c'est un état d'où l'on retombe à
l'instant où un autre credential de la chaîne est dérobé.
Il vaut la peine d'être tout aussi précis sur ce qui n'a pas eu
lieu. Le réseau corporate de Microsoft n'a pas été percé. Azure, le
service cloud, n'a pas été percé. Aucune donnée client ni aucun système
de production n'a été touché. C'était une attaque sur des dépôts de
code source — et sa conséquence la plus largement ressentie n'avait
rien à voir avec le malware lui-même : à l'instant où GitHub a désactivé
Azure/functions-action, chaque pipeline sur terre qui référençait
Azure/functions-action@v1 a cessé de se résoudre. Microsoft était le
porteur le plus en vue. Les gens réellement compromis étaient les
développeurs qui ont ouvert les dépôts empoisonnés dans un agent entre
le 3 et le 5 juin, et se sont fait balayer leurs credentials de leurs
propres machines.
Le même dépôt, ouvert à l'intérieur de Bromure Agentic Coding.
Bromure Agentic Coding fait tourner votre agent de
codage à l'intérieur d'une VM Linux par profil — son propre noyau,
son propre système de fichiers, sa propre pile réseau, sur le framework
Virtualization d'Apple. Un profil est un périmètre de travail cohérent :
ce client, ce service interne, ce dépôt open-source que vous avez
cloné pour déboguer un déploiement. Vous clonez
Azure/functions-action dans ce profil et vous l'ouvrez avec votre
agent à l'intérieur. Le déclencheur d'ouverture de dossier se déclenche
exactement comme prévu. La charge utile s'exécute. Et ensuite elle part
chercher des clés sur un hôte qu'elle ne peut pas atteindre.
Parce que les credentials ne sont pas dans le profil. La VM est livrée
avec des stubs — de faux tokens qui paraissent réels à git, gh,
aws, kubectl, npm, et à tout ce qui attend un en-tête
Authorization. Un proxy sur votre Mac se tient devant chaque connexion
qui quitte le sandbox, reconnaît le stub, et l'échange contre le vrai
secret au fil au moment où la requête part
(le sandbox qui détenait la clé
parcourt le mécanisme). Le vrai PAT GitHub, la vraie clé AWS, le vrai
principal Azure — aucun d'eux ne touche un fichier, une variable
d'environnement ou une page de mémoire que la VM peut lire. Les clés SSH
ne quittent jamais le Keychain de macOS du tout ; seul le socket
ssh-agent est forwardé à l'intérieur, comme OpenSSH l'a toujours
voulu.
Alors parcourons le balayage de Miasma à travers cette frontière. La
charge utile lit l'environnement à la recherche de $GITHUB_TOKEN et
trouve un stub. Elle lit ~/.aws et ne trouve rien. Elle lit ~/.npmrc
et ne trouve aucun token de publication. Elle lit ~/.ssh et ne trouve
aucun fichier de clé — il y a un socket forwardé, pas une clé privée sur
disque. Le blob de 4,3 Mo s'exécute jusqu'au bout exactement comme
écrit. Il exfiltre juste une boîte qui n'a jamais détenu vos clés, du
mauvais côté d'une frontière imposée par le matériel par rapport à tout
ce qui compte.
L'étape de propagation est une écriture, et les écritures reçoivent une invite.
Voler des clés n'est que la moitié de Miasma. L'autre moitié est la
propagation : il a utilisé l'accès GitHub volé pour se commiter dans
le dépôt suivant, et c'est ce qui a transformé une poignée de dépôts
empoisonnés en 73. Même dans un profil qui dispose légitimement d'un
accès push — disons que vous avez cloné functions-action précisément
parce que vous comptez ouvrir une PR contre lui — l'étape de propagation
du ver doit quand même sortir par le proxy, et c'est là que les
Guardrails la rencontrent.
Les Guardrails lisent l'opération, pas seulement la connexion — ils
distinguent une lecture d'une écriture. Un git fetch est une lecture ;
un git push est une écriture. Réglez le credential GitHub d'un profil
sur demander à l'écriture, et à l'instant où l'agent atteint un
appel qui change l'état — le git-receive-pack dont le ver a besoin
pour recommiter sa config, un DELETE contre une API, un Terminate*
sur EC2 — Bromure l'arrête au fil et fait apparaître une invite sur
votre Mac qui nomme le verbe, la cible et le profil. L'octroi que vous
donnez est borné dans le temps : quinze minutes pour une publication, à
usage unique pour les plus effrayants, jamais si la demande n'a aucun
sens. Les lectures ne vous interrompent jamais ; l'agent fetch, grep et
lit toute la journée. C'est la mutation qui met en pause.
C'est la différence entre « l'agent a un token » et « l'agent peut faire ce qu'il veut avec le token ». Tout le mécanisme de propagation de Miasma est une écriture que l'agent ne vous a jamais dit qu'il faisait — et une écriture que l'agent ne vous a jamais dit qu'il faisait est exactement ce que l'invite lecture/écriture est conçue pour attraper. Le push qui propage le ver devient une boîte de dialogue sur laquelle vous cliquez Refuser, de la même manière que « l'agent a supprimé la base de données de production » cesse d'être un post-mortem et devient une invite que vous avez déclinée.
La version avait quelques heures, et Bromure fait vieillir les packages.
Il existe une seconde voie par laquelle Miasma — et la lignée plus large
Mini Shai-Hulud — atteint un développeur : non pas par un dépôt que vous
ouvrez, mais par un package fraîchement empoisonné que l'agent
installe en faisant son travail. La moitié Red Hat de cette campagne
était précisément cela, un hook preinstall sur 32 packages dans un
scope de confiance. Et le détail brutal de ces incidents est le
timing : une version compromise se fait typiquement attraper et retirer
en quelques heures — mais ce sont exactement les heures durant
lesquelles un agent autonome, tournant sans surveillance, pourrait la
tirer.
La couche Supply Chain de Bromure transforme la même frontière proxy en point de contrôle de scan, et elle fait les deux choses qui comptent réellement contre une compromission le jour même :
- Elle force le scan de chaque récupération contre socket.dev en plus d'OSV. OSV attrape les CVE connues au-dessus du seuil de sévérité que vous fixez. socket.dev attrape ce que les bases de données de vulnérabilités n'ont pas encore rattrapé — scripts d'installation malveillants, malware comportemental, typosquats, la compromission tout juste publiée. Une version signalée est bloquée avant que le tarball n'atterrisse dans la VM. De façon cruciale, le scan tourne sous l'agent, au niveau du proxy : quelle que soit la façon dont l'agent réécrit sa propre config pour vous contourner, la récupération quitte quand même par la frontière qu'il ne peut pas franchir.
- Elle impose un cooldown. Bromure met en quarantaine toute version
publiée au cours des deux derniers jours — ajustable — de sorte
qu'une version uploadée il y a une heure est simplement non
installable dans ce profil le temps que l'écosystème rattrape son
retard. Contre un ver dont toute la fenêtre d'opportunité est l'écart
entre publier et retirer, un cooldown n'est pas une heuristique
sur l'apparence malveillante d'un package. C'est un refus d'être le
premier à le découvrir. Combinez-le avec le retrait des scripts
d'installation que Bromure fait à la volée — en extrayant les hooks
postinstalldu tarball et en corrigeant le hash des métadonnées de sorte que l'installation se vérifie toujours — et le package qui atterrit atterrit inerte.
Pour Miasma spécifiquement, le vecteur d'ouverture de dépôt est le titre
à la une. Mais la même campagne se propage aussi à travers les packages,
et le cooldown est le contrôle qui aurait affamé son côté npm : une
version @redhat-cloud-services fraîche, ou une dépendance transitive
fraîchement empoisonnée tirée en déboguant ce dépôt Microsoft, reste en
quarantaine pendant les heures exactes où elle est dangereuse.
Là où ceci ne vous sauve pas.
Un push que vous approuvez est un push qui a lieu.
Le guardrail lecture/écriture attrape l'écriture dont l'agent ne vous
a pas parlé. Il ne lit pas le diff. Si vous poussez légitimement vers
functions-action et que vous approuvez l'invite, Bromure forwarde
le push — y compris, en principe, un workflow empoisonné que vous
n'avez pas remarqué dans le diff. Lisez ce que vous approuvez. La
trace de session vous dit
quels diffs lire.
Le cooldown est une fenêtre, pas un mur.
Deux jours est calibré sur l'écart publication-retrait observé, mais un attaquant patient peut s'asseoir sur une version compromise plus longtemps que le cooldown et rester installable le troisième jour. Le cooldown affame les vers du jour même ; il ne se porte pas garant d'un package qui a simplement vieilli. socket.dev et OSV doivent encore faire leur part.
Le profil est de longue durée, donc la persistance persiste.
Un profil Bromure n'est pas un disque jetable. Une charge utile qui s'écrit dans un chemin de démarrage à l'intérieur du profil peut survivre jusqu'à la prochaine session dans ce profil. Ce à quoi elle se réveille, c'est un invité sans clés d'hôte et un courtier qui ne parle qu'en tokens à courte durée, sur invite, à portée limitée — une présence dans une boîte qui ne contient rien — mais une présence tout de même.
Scopez le courtier exprès.
Si un profil est provisionné pour pousser vers un dépôt aujourd'hui et que ce profil exécute Miasma aujourd'hui, une écriture approuvée passe. Les octrois du courtier fonctionnent parce qu'ils sont étroits. Un profil qui n'a besoin que de lire un dépôt ne devrait pas pouvoir y écrire ; un profil qui ne publie jamais ne devrait détenir aucun token de publication. L'isolation contient l'explosion ; le scoping décide de la taille qu'elle aurait pu atteindre.
Le prochain dépôt de confiance est déjà cloné quelque part.
La leçon du ver TanStack était que
le lockfile et la signature ne sont pas des défenses. La leçon du
scope Red Hat était que
l'éditeur non plus. Microsoft ajoute le corollaire suivant : le dépôt
non plus, et le déclencheur n'a même pas besoin d'être une installation
que vous avez choisie — ce peut être un dossier que votre agent a
ouvert. Le dépôt Azure/functions-action n'a rien fait de mal en étant
de confiance. Être de confiance est tout le but d'une Action de première
partie canonique, et c'est exactement ce qui l'a rendu digne d'être
empoisonné — deux fois, dans le cas de durabletask.
Vous ne pouvez pas régler cela en faisant confiance plus soigneusement, parce que la confiance n'a jamais été mal placée. Vous le réglez en arrangeant les choses de sorte que « quel dépôt est-ce » et « quel scope a publié ceci » cessent d'être les questions dont dépend votre trousseau. Bromure Agentic Coding est la configuration où l'agent ouvre le dépôt à l'intérieur d'une VM par profil, les vrais credentials restent sur l'hôte derrière un courtier, chaque écriture que l'agent fait doit passer une invite, et un package ne peut pas être installé tant qu'il n'a pas survécu à un cooldown. Le pire qu'une ouverture de dossier empoisonnée puisse faire est d'exfiltrer une boîte qui n'a jamais détenu vos clés. C'est gratuit, open-source, et livré aujourd'hui.