Miasma skipped npm and hijacked your AI agents' startup hooks

73 Microsoft repos down on June 5 via AI coding agent hooks — no npm. Attack vector, payload anatomy, and what to do now.

Miasma skipped npm and hijacked your AI agents' startup hooks
Share

It took 105 seconds for 73 of Microsoft's GitHub repositories to go dark. By the time anyone could file an issue, the self-replicating "Miasma" worm had already forced GitHub's hand — and broken CI/CD pipelines for developers who never installed a single tainted package.

How GitHub Disabled 73 Microsoft Repositories in Under Two Minutes

On June 5, 2026, GitHub's automated abuse detection disabled 73 Microsoft-owned repositories after the Miasma supply-chain worm compromised them, completing the takedowns in roughly 105 seconds across two waves between 16:00:50 and 16:02:35 UTC . This was machine-speed enforcement, not a manual security review — the system pulled the repositories offline faster than any human team could triage them, which is part of why the downstream breakage caught so many developers off guard.

Quick Answer: On June 5, 2026, GitHub's automated abuse detection disabled 73 Microsoft-owned repositories across four organizations in about 105 seconds after the Miasma worm compromised them. The hardest-hit org was Azure with 49 repositories, including the official Azure/functions-action deployment Action .

The damage spanned four organizations. Azure absorbed the bulk with 49 repositories disabled, followed by Azure-Samples (13), microsoft (10), and MicrosoftDocs (1) . The Azure-Samples set is notable for who it touches: it skews heavily toward AI demo content, with repositories such as rag-postgres-openai-python and llm-fine-tuning — exactly the example projects an AI developer would clone to bootstrap a retrieval or fine-tuning stack .

OrganizationRepos disabledNotable contents
Azure49Azure/functions-action and functions-container-action (official deploy Actions); Durable Task siblings
Azure-Samples13AI demo projects: rag-postgres-openai-python, llm-fine-tuning
microsoft10Microsoft-org tooling and samples
MicrosoftDocs1Documentation repository
Total73Across four organizations, disabled in ~105 seconds

Microsoft's own framing is narrower than the researcher tallies. Spokesperson Ben Hope told TechCrunch that the company "temporarily removed some repositories" while investigating potential malicious content, that some were restored after review while others could stay offline, and that a "small number" of customers who may have pulled content were notified . That language — "some repositories," "small number" — is conspicuously vaguer than the precise 73-repo, four-org count documented by StepSecurity and devops.com, and at the time of research Microsoft had published no root-cause report confirming that Miasma infected every disabled repo versus some being swept up by GitHub's automated enforcement .

The "72 other repositories" framing some readers encountered counts functions-action plus 72 others — the same 73 total, just anchored on the most operationally painful casualty. Sections below trace why that single Action going dark broke deployments worldwide.

No Package Manager: How Miasma Hid Its Fuse in Project Files

Miasma skipped npm and hijacked your AI agents' startup hooks

Miasma's break from prior supply-chain worms is the trigger: it abandons the package-manager install hook entirely and instead weaponizes the auto-execution configuration files that AI coding agents and editors read on project open. A single malicious commit to Azure's durabletask repository — pushed through a previously compromised contributor account — planted five parallel startup triggers, each pointing at the same dropper at .github/setup.js . The payload fires the moment a developer opens the cloned folder, before a single manual command is run.

Two of those triggers exploit session-start hooks. Claude Code reads a SessionStart hook from a committed .claude/settings.json, and Gemini CLI reads the equivalent from .gemini/settings.json; both execute the referenced command automatically when the project is opened . That behavior is by design — hooks let an agent prep an environment on entry — but because the configuration lives in version control, anyone who clones an infected repo inherits the attacker's hook as if it were their own.

The remaining triggers cover editor and toolchain surfaces. A Cursor project rule at .cursor/rules/setup.mdc sets alwaysApply: true, a VS Code task in .vscode/tasks.json uses runOn: "folderOpen", and SafeDep documents a fifth path: an npm test-script hijack that fires under standard CI invocations . The redundancy is the point — whatever tool a victim happens to use to inspect the repo becomes the ignition source.

TriggerConfig pathExecution conditionTool affected
SessionStart hook.claude/settings.jsonAuto-runs on project openClaude Code
SessionStart hook.gemini/settings.jsonAuto-runs on project openGemini CLI
Project rule.cursor/rules/setup.mdcalwaysApply: trueCursor
Editor task.vscode/tasks.jsonrunOn: "folderOpen"VS Code
Test-script hijackpackage.json (npm)Runs on npm test / CInpm / CI runners

Every one of these resolves to .github/setup.js, so the five entry points share one detonation path. SafeDep, which traced the campaign's lineage across waves, calls it "the same worm with a different mouth" — a description that fits a payload that swapped its install-hook delivery for config injection while keeping its core intact .

The practical consequence is a shift in the threat model. A developer who never runs npm install or pip install is still exposed the instant they open an infected clone in an agent-enabled editor . Agent and editor configuration files — .claude/, .gemini/, .cursor/, and .vscode/tasks.json — now have to be read as executable code, reviewed before the folder is ever opened.

Inside the Payload: 4.6 MB Bun Dropper and 90+ Cloud Targets

The file those triggers point at is not a small script. StepSecurity measured the committed .github/setup.js dropper at 4,643,745 bytes — roughly 4.6 MB of obfuscated JavaScript. SafeDep's teardown describes it as a staged loader: the dropper pins Bun v1.3.13 as its runtime, then decrypts a bootstrap installer and a roughly 667 KB credential harvester on first execution . The bulk of the 4.6 MB is staging and obfuscation; the working harvester it unpacks is a fraction of that size.

Once running, the harvester sweeps the host for secrets. SafeDep documents it scanning for AWS, Azure, GCP, HashiCorp Vault, Kubernetes, npm, and GitHub tokens, while StepSecurity counts credential collectors targeting 90+ developer tools. The collected material is dead-dropped — written out to attacker-controlled public GitHub repositories rather than exfiltrated to a private command-and-control server . Using GitHub as the drop point keeps the traffic indistinguishable from normal developer activity.

Self-propagation is what turned a single poisoned commit into an estate-wide event. After harvesting tokens, the worm reuses them to commit copies of itself into every repository the compromised account can write to . That mechanism explains how the infection reached the Durable Task siblings and the Functions actions in the same window rather than one repo at a time — each set of harvested credentials becomes the launch point for the next wave.

Cloudsmith flags the detail that makes signature-based defense brittle: Miasma generates a uniquely encrypted payload for each infection, rotating AES keys and cipher shifts between waves . The decryption harness and Bun pinning stay constant, but the bytes on disk differ every time, so hash-based indicators of compromise expire the moment a new variant ships. SafeDep observed the same pattern in the cipher rotation it traced.

"This is the same worm with a different mouth — identical AES-128-GCM decryption harness and Bun pinning, but rotated Caesar-cipher and AES keys point to per-wave recompilation rather than copy-paste," — SafeDep research team (source: SafeDep teardown).

The practical takeaway for defenders: matching a known SHA-256 of the dropper will catch yesterday's wave and miss today's. Detection has to key on behavior and structure — a multi-megabyte .github/setup.js, a pinned Bun runtime, and outbound writes to unfamiliar public repos — not on a static fingerprint.

TeamPCP's Fingerprint: Matching Bun Pins and Exfil Domains

Miasma skipped npm and hijacked your AI agents' startup hooks

That structural signature is also what lets researchers attribute Miasma. SafeDep tracks the worm to the threat group TeamPCP and frames it as an evolution of an earlier campaign called Mini Shai-Hulud — not a fresh codebase, but the same operator reusing a proven engine. The evidence is internal consistency across waves: an identical AES-128-GCM decryption harness and a Bun runtime pinned to v1.3.13 , paired with shared exfiltration infrastructure that ties otherwise-separate intrusions to one actor.

"The same worm with a different mouth," — SafeDep research team, describing Miasma's relationship to Mini Shai-Hulud (source: SafeDep teardown).

The "different mouth" matters for detection. SafeDep found that while the decryption harness and Bun pin stayed constant, the cosmetic layer rotated between waves — the Caesar-cipher shifted from ROT-9 to ROT-4 and the AES keys changed — which points to active per-wave recompilation rather than copy-paste reuse . The infrastructure, by contrast, is the durable thread: shared exfiltration domains check.git-service[.]com and t.m-kosche[.]com recur across infections and anchor the campaign to TeamPCP . For defenders, that inverts the usual hierarchy — the network indicators outlive the file hashes.

The group's playbook predates the Microsoft hit. Cloudsmith reports Miasma first surfaced in the @redhat-cloud-services npm namespace, entering through a compromised Red Hat employee GitHub account. The attacker injected a minimal workflow that requested GitHub OIDC tokens, then published 32 malicious npm package versions — each carrying valid SLSA provenance attestations . That last detail is the crux of the technique: the releases were signed by a legitimate, trusted account, so the provenance was genuine. Scanners that treat SLSA attestation as a positive trust signal waved the packages through.

This is provenance abuse as a deliberate strategy, and it lines up with TeamPCP's reported history. Prior targets attributed to the group include:

  • TanStack — the open-source data/state libraries widely used in React and TypeScript apps .
  • Mistral AI — the LLM vendor, a high-value identity in the AI tooling supply chain .
  • The @antv ecosystem — the visualization libraries maintained under that npm scope .
  • LiteLLM — the popular LLM-gateway proxy used across agent stacks .
  • Checkmarx — notably, an application-security vendor .

The common thread is not a single vulnerability but a pattern: compromise a legitimate maintainer credential, produce a correctly-signed release, and let the trust machinery do the distribution. The recompiled payload and rotated keys keep static signatures stale, while the OIDC-and-provenance route keeps the artifacts looking routine. That combination — fresh binaries over a reused engine, riding genuine attestations — is what makes TeamPCP harder to fingerprint with the tooling most teams already run.

From Package Poison to Repository Takeover: A 17-Day Escalation

Miasma's reach into Microsoft's Durable Task project was not a single event but a repeat hit roughly 17 days apart, and the two strikes show the campaign escalating from package substitution to direct repository takeover. The first blow landed on May 19, 2026, when a compromised Azure/durabletask contributor account uploaded three malicious PyPI versions — 1.4.1, 1.4.2, and 1.4.3 — in about a 35-minute window using a stolen publishing token . The bad releases were yanked within hours .

PyPI's current durabletask page reflects that cleanup. It lists 1.4.0 (Apr 8, 2026), 1.5.0 (Jun 5, 2026), and 1.6.0 (Jun 18, 2026) with Microsoft as the verified maintainer, and no longer visibly shows 1.4.1 through 1.4.3 — consistent with a yank, though absence alone is not proof . For a developer pinning durabletask, the practical read is simple: any resolve that pulled 1.4.1–1.4.3 between May 19 and the yank should be treated as exposed.

June 5 looks like a re-compromise rather than a fresh foothold. Researchers note that the attack scope widened from PyPI package substitution to direct repository compromise, and GitHub's sweep disabled the entire Durable Task family alongside the Functions repositories: durabletask-dotnet, -go, -java, -js, -mssql, and -netherite all went dark together . That pattern — every sibling repo disabled at once — is what you would expect from automated enforcement covering the full set of repositories a single compromised account holds write access to, not a surgical removal of known-bad files.

The attribution chain ties the two incidents together. OpenSourceMalware reports that Wiz, Endor Labs, and StepSecurity linked the May event to stolen GitHub Actions secrets and the TeamPCP threat group . The June recurrence, driven by the same or a related stolen account, is the uncomfortable part: it suggests the initial credential rotation after May was incomplete or bypassed. If every token tied to that contributor identity had been revoked and reissued, a second push through the same access should not have been possible.

The lesson for maintainers is that yanking poisoned artifacts closes the symptom, not the breach. Until the compromised identity, its publishing tokens, and any harvested CI/CD secrets are fully rotated and the access path audited, the same door stays open — which is exactly what a 17-day gap between near-identical attacks demonstrates.

When Azure/functions-action Went Dark: Global CI/CD Impact

Miasma skipped npm and hijacked your AI agents' startup hooks

The most operationally damaging casualty of the June 5 takedowns was not a payload at all — it was an absence. Azure/functions-action is Microsoft's official GitHub Action for deploying packaged code to Azure Function Apps, referenced across enterprise and indie CI/CD pipelines worldwide. When GitHub's abuse detection disabled it alongside 72 other Microsoft repositories on June 5, 2026, every workflow pointing at its mutable @v1 tag suddenly failed to resolve — breaking deployments globally for developers who never installed a poisoned npm package or opened a compromised clone .

That is the part worth sitting with. The harvester scraped credentials only from machines that executed it. The functions-action outage hit a much larger blast radius: anyone whose pipeline contained uses: Azure/functions-action@v1 got a hard failure on their next deploy, regardless of their own hygiene. The sibling Azure/functions-container-action went dark in the same wave, taking container-based function deploys with it . For teams shipping continuously, a broken release path during an active security incident is its own second-order outage — one they could neither predict nor patch.

The repository is reachable again at the time of writing , but Microsoft has been careful not to overstate the all-clear. Spokesperson Ben Hope framed the response as deliberately reversible:

"We temporarily removed some repositories while investigating potential malicious content; some were restored after review, while others could remain offline." — Ben Hope, Microsoft spokesperson (source: via TechCrunch reporting)

Strip away the worm and the takedown mechanics, and the functions-action breakage exposes a structural fault that predates Miasma: mutable tag references are single points of failure entirely outside the downstream developer's control. A tag like @v1 or @main is a moving pointer — whoever controls the upstream repository can repoint it, and whoever disables that repository can erase it. When the action vanished, so did every pipeline that trusted the tag to keep meaning the same thing. The mitigation is unglamorous and well established: pin Actions to an immutable commit SHA rather than a floating tag, so an upstream takedown or a malicious retag cannot silently rewrite what your CI executes .

Re-clone, Rotate, Pin SHA: What to Do After Miasma

If you cloned any affected repository around June 5, 2026, do not open the folder in Claude Code, Gemini CLI, Cursor, or VS Code until you have inspected it from the terminal. The session-start hooks and folder-open tasks that Miasma weaponizes fire on project open, so the safe order is grep first, open later. Run a one-line dropper check and scan for the agent config paths the worm planted before trusting any clone.

test -f .github/setup.js && echo DROPPER PRESENT
ls -la .claude/ .gemini/ .cursor/rules/ .vscode/tasks.json 2>/dev/null

If .github/setup.js exists, or if you find .claude/, .gemini/, .cursor/rules/, or a .vscode/tasks.json in a repository you did not author, treat it as potentially infected: delete the working copy, then re-clone from a commit that predates the injection rather than patching in place. Researchers recommend exactly this sequence — grep, delete, re-clone from a pre-injection commit — because the trigger lives in committed configuration, not in an installed package .

Then rotate anything that could have been scraped since early June. The harvester collects credentials for AWS, Azure, Google Cloud, Kubernetes and more than 90 additional developer tools, so a conservative reset list includes:

  • GitHub personal access tokens and SSH keys
  • CI/CD pipeline secrets and signing keys
  • Azure and GCP service principals
  • npm and PyPI publishing tokens

Miasma exfiltrates by dead-dropping stolen secrets into attacker-controlled public GitHub repositories before self-propagating with harvested tokens . That is also its weakness: a worm that publishes evidence leaves a trail. Audit your account and org for unexpected public repositories or JSON secret dumps you did not create, and check whether harvested credentials spawned any rogue commits.

Finally, close the structural gaps the campaign exploited. Pin every GitHub Action to a full commit SHA instead of a mutable tag like @v1, so an upstream takedown or malicious retag cannot silently rewrite what your CI runs. Adopt PyPI Trusted Publishing via OIDC in place of long-lived publishing tokens — the same short-lived-credential model that the durabletask PyPI compromise of stolen tokens argued for . Enforce branch protection with mandatory PR review on every repository that holds publish rights, since the malicious commit reached Azure/durabletask through a compromised contributor account .

The concrete takeaway: your AI agent and editor config files are now executable surface area. Review .claude/, .gemini/, .cursor/, and .vscode/tasks.json before opening any clone, rotate credentials from early June forward, and pin your dependencies to immutable references. None of it is novel hardening — it is the baseline supply-chain hygiene this worm was built to find missing .

Frequently asked questions

If I didn't install anything from npm or PyPI, am I still exposed?

Yes. Miasma's primary spread vector is not a package install — it is the auto-execution config of AI coding agents committed inside project files. Anyone who cloned an infected repository and opened it in VS Code, Cursor, Claude Code, or Gemini CLI may have run the .github/setup.js dropper automatically, with no explicit npm install or pip install step. That is why the June 5, 2026 takedown of 73 Microsoft repositories broke CI/CD for developers who never touched a registry. Treat "I didn't install anything" as no protection here.

Which AI coding agent files does Miasma target?

Miasma plants five parallel triggers, all pointing at the same .github/setup.js dropper :

  • .claude/settings.json — Claude Code SessionStart hook
  • .gemini/settings.json — Gemini CLI SessionStart hook
  • .cursor/rules/setup.mdc — Cursor project rule with alwaysApply: true
  • .vscode/tasks.json — VS Code task with runOn: "folderOpen"
  • package.json — an npm test-script hijack (the fifth trigger noted by SafeDep)

Any of the first four fires the moment you open the project folder — no command, no install, no prompt.

How do I check if a cloned repository contains the dropper?

From the repository root, run test -f .github/setup.js && echo DROPPER PRESENT before opening the folder in any agent or editor . Also look for unexpected .claude/, .gemini/, or .cursor/rules/ directories in the project root. If you find these in any repo cloned around June 5, 2026, delete the clone and re-clone from a commit that predates the malicious push, then rotate any credentials exposed since early June.

Is Azure/functions-action safe to reference now?

The repository has been restored and is reachable again at the time checked . The lasting fix is structural: pin the action to a specific commit SHA rather than the mutable @v1 tag. During the outage, every workflow referencing @v1 stopped resolving and broke downstream Azure Function App deployments . A SHA pin means a future takedown or content substitution cannot silently alter or break your pipeline.

How many repositories did Miasma actually hit across the full campaign?

The confirmed Microsoft figure is 73 repositories across four GitHub organizations. Campaign-wide estimates diverge sharply and depend on the counting window: 113+ repos (StepSecurity-aligned), 123+ repositories plus 50+ npm packages totaling 408,000+ monthly downloads (SafeDep), and 813+ repositories cited in a Wordfence vlog (video: Wordfence Security News Clip). Treat the broad campaign count as a moving, unconfirmed range reflecting different snapshots of an actively spreading worm; the 73-repo Microsoft figure is the firm anchor.