The repo really was Microsoft's
On June 5–6, 2026, the Miasma worm pushed credential-stealing code into 73 repositories across four of Microsoft's own GitHub organizations — Azure, Azure-Samples, microsoft, MicrosoftDocs — including Azure/functions-action, the official deploy Action, and durabletask, a repo that had already been cleaned once in May. This time the payload did not wait for npm install. It fired the moment a developer opened the repository in Claude Code, Cursor, Gemini CLI, or VS Code. Here is why the trust signal — 'it's a Microsoft repo' — was again the attack surface, and what changes when the agent that opens it lives in a per-profile Bromure VM, behind a credential broker, a read-write guardrail, and a package cooldown.
A week ago the worm rode in on Red Hat's npm scope and fired
through a preinstall hook. This week it rode in on Microsoft's
GitHub and did not need an install at all. Miasma planted
project-scoped configuration into 73 Microsoft-owned repositories —
Azure/functions-action, the official deploy Action, among them —
and the payload executed the moment a developer opened the repo in
Claude Code, Cursor, Gemini CLI, or VS Code. Cloning was not the
trigger. Opening it in your agent was.
A developer who clones Azure/functions-action to debug a failing
deploy does not hesitate, and neither does the agent they point at it.
It is a first-party repository from Microsoft's own Azure
organization — the canonical source of the GitHub Action half the
ecosystem references as Azure/functions-action@v1. There is no
maintainer to vet, because the maintainer is Microsoft. There is no
name to squint at, because the name is exactly what it should be. So
the repo gets opened, and the agent reads the project's configuration
the way every modern coding tool does on folder-open — and one of
those config files points at a command, and the command is a roughly
4.3-megabyte blob that starts reading the filesystem for keys.
We wrote up the npm half of this exact campaign seven days ago. Miasma is the same worm — a variant of the "Mini Shai-Hulud" code TeamPCP released publicly in mid-May — and the uncomfortable fact it demonstrates is the same one, turned one more notch: reputable source is not a security control. It feels like one. Most supply-chain advice leans on it. And on June 5 it bought exactly nothing, twice over: the namespace was Microsoft's, and the execution did not come from a package you chose to install. It came from opening a folder.
What Miasma did to Microsoft's repositories.
On June 5–6, 2026, GitHub disabled 73 repositories across four Microsoft GitHub organizations after malicious commits were pushed into them, per The Hacker News and a detailed teardown from StepSecurity. Redmond Magazine covered it on June 8. The breakdown:
- Azure — 49 repositories, including
Azure/functions-action(the official Functions deployment Action) and the language workers for .NET, Python, Java, Go, and PowerShell. - microsoft — 10 repositories.
- Azure-Samples — 13 repositories.
- MicrosoftDocs — 1 repository.
The shape, stripped to its mechanics:
- The entry point was a previously compromised contributor
account with commit access, used to push malicious commits
directly into the repositories — tagged
[skip ci]so the changes slid past the CI/CD checks that would otherwise have run. - The commit planted project-scoped configuration — the kind of file a coding agent or IDE reads and acts on automatically when you open the folder: an editor task, an agent hook, a project-defined MCP server. This is the same class of trust boundary Adversa AI's TrustFall demonstrated across Claude Code, Cursor CLI, Gemini CLI, and Copilot CLI — all four execute project-defined configuration right after the folder-trust prompt.
- The payload — roughly 4.3 MB of obfuscated code — executed when
the repository was opened in Claude Code, Gemini CLI, Cursor, or
VS Code, or run through an
npm testscript. Not on clone alone. The act of pointing your agent at the cloned tree is what ran it. - On execution it swept the host for GitHub tokens, AWS keys, Azure
service principals, GCP credentials, npm and PyPI publish tokens,
SSH keys, and
.envfiles, then used the stolen access to commit itself onward — which is what makes it a worm rather than a one-shot.
One detail is worth dwelling on: Azure/durabletask was among the
repositories hit — and it had already been compromised in May in
the TeamPCP campaign and cleaned. A repo that was remediated once was
re-poisoned five weeks later. Cleanup is not a state you reach and
keep; it is a state you fall back out of the moment another credential
in the chain is taken.
It is worth being equally precise about what did not happen.
Microsoft's corporate network was not breached. Azure the cloud
service was not breached. No customer data and no production system
was touched. This was an attack on source-code repositories — and
its widest-felt consequence had nothing to do with the malware at all:
the instant GitHub disabled Azure/functions-action, every pipeline
on earth that referenced Azure/functions-action@v1 stopped
resolving. Microsoft was the highest-profile carrier. The people
actually owned were the developers who opened the poisoned repos in an
agent between June 3 and 5, and had their credentials swept off their
own machines.
The same repo, opened inside Bromure Agentic Coding.
Bromure Agentic Coding runs your coding agent
inside a per-profile Linux VM — its own kernel, its own
filesystem, its own network stack, on Apple's Virtualization
framework. A profile is a coherent scope of work: this client,
this internal service, this open-source repo you cloned to debug a
deploy. You clone Azure/functions-action into that profile and you
open it with your agent in there. The folder-open trigger fires
exactly as designed. The payload runs. And then it goes looking for
keys on a host it cannot reach.
Because the credentials are not in the profile. The VM ships with
stubs — fake tokens that look real to git, gh, aws,
kubectl, npm, and anything else expecting an Authorization
header. A proxy on your Mac sits in front of every connection leaving
the sandbox, recognizes the stub, and swaps it for the real secret
at the wire as the request leaves
(the sandbox that held the key
walks through the mechanism). The real GitHub PAT, the real AWS key,
the real Azure principal — none of them touch a file, an environment
variable, or a page of memory the VM can read. SSH keys never leave
the macOS Keychain at all; only the ssh-agent socket is forwarded
in, the way OpenSSH always intended.
So walk Miasma's sweep through that boundary. The payload reads the
environment for $GITHUB_TOKEN and finds a stub. It reads ~/.aws
and finds nothing. It reads ~/.npmrc and finds no publish token. It
reads ~/.ssh and finds no key file — there is a forwarded socket,
not a private key on disk. The 4.3 MB blob runs to completion exactly
as written. It just exfiltrates a box that was never holding your
keys, on the wrong side of a hardware-enforced boundary from
everything that matters.
The propagation step is a write, and writes get a prompt.
Stealing keys is only half of Miasma. The other half is spread: it
used stolen GitHub access to commit itself into the next repository,
and that is what turned a handful of poisoned repos into 73. Even in a
profile that legitimately has push access — say you cloned
functions-action precisely because you intend to open a PR against
it — the worm's propagation step still has to go out through the
proxy, and that is where Guardrails meets it.
Guardrails reads the operation, not just the connection — it tells a
read from a write. A git fetch is a read; a git push is a write.
Set a profile's GitHub credential to ask on write, and the moment
the agent reaches for a state-changing call — the
git-receive-pack the worm needs to commit its config back, a
DELETE against an API, a Terminate* on EC2 — Bromure stops it at
the wire and surfaces a prompt on your Mac that names the verb, the
target, and the profile. The grant you give is time-boxed: fifteen
minutes for a release, single-use for the scary ones, never if the ask
makes no sense. Reads never interrupt you; the agent fetches and
greps and reads all day. It is the mutation that pauses.
This is the difference between "the agent has a token" and "the agent can do whatever it wants with the token." Miasma's whole spreading mechanism is a write the agent never told you it was making — and a write the agent never told you it was making is exactly what the read-write prompt is built to catch. The push that propagates the worm becomes a dialog box you click Don't allow on, the same way "the agent deleted the production database" stops being a postmortem and becomes a prompt you declined.
The version was hours old, and Bromure makes packages age.
There is a second way Miasma — and the broader Mini Shai-Hulud
lineage — reaches a developer: not through a repo you open, but
through a freshly poisoned package the agent installs while doing
its work. The Red Hat half of this campaign was precisely that, a
preinstall hook on 32 packages in a trusted scope. And the brutal
detail of those incidents is timing: a compromised version typically
gets caught and yanked within hours — but those are exactly the
hours during which an autonomous agent, running unattended, might pull
it.
Bromure's Supply Chain layer turns the same boundary proxy into a scanning checkpoint, and it does the two things that actually matter against a same-day compromise:
- It force-scans every fetch against socket.dev as well as OSV. OSV catches known CVEs above the severity threshold you set. socket.dev catches what the vulnerability databases have not caught up to yet — rogue install scripts, behavioral malware, typosquats, the just-published compromise. A flagged release is blocked before the tarball ever lands in the VM. Crucially the scan runs below the agent, at the proxy: however the agent rewrites its own config to route around you, the fetch still leaves through the boundary it cannot cross.
- It enforces a cooldown. Bromure quarantines any release
published in the last two days — tunable — so a version uploaded an
hour ago is simply not installable in that profile while the
ecosystem catches up. Against a worm whose entire window of
opportunity is the gap between publish and yank, a cooldown is
not a heuristic about whether a package looks bad. It is a refusal to
be the first one to find out. Combine it with the install-script
stripping Bromure does on the fly — pulling
postinstallhooks out of the tarball and fixing the metadata hash so the install still verifies — and the package that does land lands inert.
For Miasma specifically, the repo-open vector is the headline. But the
same campaign spreads through packages too, and the cooldown is the
control that would have starved the npm side of it: a fresh
@redhat-cloud-services release, or a freshly poisoned transitive
dependency pulled while debugging that Microsoft repo, sits in
quarantine through the exact hours it is dangerous.
Where this does not save you.
A push you approve is a push that happens.
The read-write guardrail catches the write the agent didn't tell
you about. It does not read the diff. If you are legitimately
pushing to functions-action and you approve the prompt, Bromure
forwards the push — including, in principle, a poisoned workflow
you didn't notice in the diff. Read what you approve. The
session trace tells you
which diffs to read.
The cooldown is a window, not a wall.
Two days is tuned to the observed publish-to-yank gap, but a patient attacker can sit on a compromised version longer than the cooldown and still be installable on day three. The cooldown starves same-day worms; it does not vouch for a package that has merely gotten old. socket.dev and OSV still have to do their part.
The profile is long-lived, so persistence persists.
A Bromure profile is not a disposable disk. A payload that writes itself into a startup path inside the profile can survive into the next session in that profile. What it wakes up to is a guest with no host keys and a broker that only speaks short-lived, prompted, scope-limited tokens — presence in a box with nothing in it — but presence all the same.
Scope the broker on purpose.
If a profile is provisioned to push to a repo today and that profile runs Miasma today, an approved write goes through. Broker grants work because they are narrow. A profile that only needs to read a repo should not be able to write it; a profile that never publishes should hold no publish token. Isolation contains the blast; scoping decides how big it could ever have been.
The next trusted repo is already cloned somewhere.
The lesson of the TanStack worm
was that the lockfile and the signature are not defenses. The lesson
of the Red Hat scope was that
neither is the publisher. Microsoft adds the next corollary: neither
is the repository, and the trigger does not even have to be an
install you chose — it can be a folder your agent opened. The
Azure/functions-action repo did nothing wrong by being trusted.
Being trusted is the whole point of a canonical first-party Action,
and it is exactly what made it worth poisoning — twice, in the case of
durabletask.
You cannot fix that by trusting more carefully, because the trust was never misplaced. You fix it by arranging things so that "which repo is this" and "which scope published this" stop being the questions your keychain depends on. Bromure Agentic Coding is the configuration where the agent opens the repo inside a per-profile VM, the real credentials stay on the host behind a broker, every write the agent makes has to get past a prompt, and a package can't be installed until it has survived a cooldown. The worst a poisoned folder-open can do is exfiltrate a box that was never holding your keys. It is free, open-source, and shipped today.