Kai Ole Hartwig — Blog
18 min read
High
By

Miasma moves from the package manager into the editor: a clone + “open folder” in Claude Code, Cursor, Gemini CLI or VS Code is enough — and it hit Azure/durabletask too

6 June 2026. SafeDep has documented a new detonation surface of the Miasma worm family: instead of publishing an npm package, the worm commits six files directly into GitHub source repos — five of them merely triggers that launch the sixth (.github/setup.js, a 4.3 MB Bun dropper) via auto-run features of real tools: SessionStart hooks in .claude//.gemini/, an always-apply rule in .cursor/rules/, a folderOpen task in .vscode/ and a hijacked test script. Cloning is safe, opening is not — whoever opens an affected repo in VS Code or starts Claude Code in it harvests a multi-cloud credential stealer. The fingerprint was found in 113 repos across dozens of accounts, including the official Azure/durabletask (stolen PAT of a real contributor, commit backdated to 2020).

TL;DR — 90 seconds

Affected?

Developers who, after 2 June, cloned one of the affected GitHub source repos and opened it in an AI coding agent/editor (Claude Code, Gemini CLI, Cursor, VS Code) or ran npm test in it. SafeDep found the fingerprint in 113 repos across dozens of accounts, incl. Azure/durabletask, Azure-Samples/llm-fine-tuning, metersphere/helm-chart. Aggravating: machines with reachable cloud/CI/AI credentials.

Risk?

Local code run on opening the repo (not on cloning, not via the npm package — the published packages of these projects are clean), followed by a multi-cloud credential sweep (AWS, Azure, GCP, Vault, Kubernetes, npm, GitHub) and self-propagation with stolen tokens.

Immediate action?

Do not open suspicious working copies in an editor/agent and do not run npm test; grep a local clone before opening (test -f .github/setup.js); delete affected clones and re-clone from a clean commit; on a hit, rotate exposed credentials.

Recommendation?

Mid-market and enterprise: treat .claude/, .gemini/, .cursor/, .vscode/ in the diff as a supply-chain signal (not “editor noise”); configure editor/agent auto-run restrictively for cloned third-party repos; bring AI coding agents into the threat model as executing supply-chain actors.

Criticality?

high (references the hero badge — active, self-replicating campaign; review within the 48h window).

What is the problem?

On 3 June 2026, per SafeDep, Miasma hit two surfaces at once. The npm arm published 57 malicious packages across 286+ versions and hid the trigger in binding.gyp files to evade lifecycle-script scanners — I covered that arm on 4 June (the Phantom Gyp/binding.gyp post, sources StepSecurity/JFrog). This post documents the other arm: a parallel run of the same worm that skips the registry entirely and pushes directly into GitHub source repos.

An attacker pushed a commit chore: update dependencies [skip ci] to icflorescu/mantine-datatable and four sibling repos. The commit added no dependency. It planted a 4.3 MB payload runner and wired it to launch automatically via five developer tools. The file list gives away the logic:

 

.claude/settings.json    | 15 ++++++
.cursor/rules/setup.mdc  |  8 +++
.gemini/settings.json    | 15 ++++++
.github/setup.js         |  1 +
.vscode/tasks.json       | 13 +++++
package.json             |  2 +-

 

Five of the six files exist only to launch the sixth. .github/setup.js is the payload; everything else is a trigger, one per tool. The clever part is the trigger surface: each config file abuses a legitimate auto-run feature of a different tool. Cloning the repo is harmless — opening it is not. Whoever clones mantine-datatable to debug and opens the folder in VS Code or starts Claude Code in it runs the payload with no further interaction.

The dropper itself (setup.js) is a single eval statement: a char-code array, a Caesar shift (here ROT-4), the result passed to eval. Statically decoded it is an async loader that pulls node:crypto and AES-128-GCM-decrypts two hardcoded blobs — a bootstrap component and the actual worm payload (_p, 667 KB). The loader writes _p to a random temp file and runs it under Bun (downloading Bun on demand from the official oven-sh release URL). Bun brings its own runtime, fetch, crypto and shell — the payload needs nothing from the host beyond the downloaded binary, keeping it off the victim's Node install.

Who is affected?

AffectedNot affectedConditions / aggravating
Developers who, after 2 June, cloned an affected source repo and opened it in an AI agent/editor (Claude Code, Gemini CLI, Cursor, VS Code)Anyone who only cloned an affected repo but did not open it and did not run npm testTrigger = git clone + opening the folder / starting an agent session / npm test; not the clone alone
Projects/CI that run the test script of an affected repoAnyone who only uses the published npm package of these projectsSafeDep: “the published npm packages of these projects are clean” — the risk is local and survives npm uninstall
113 repos indexed by GitHub code search across dozens of accounts, incl. Azure/durabletask, Azure-Samples/llm-fine-tuning, metersphere/helm-chartRepos without the .github/setup.js dropper or without the trigger config filesCode search only covers indexed default branches and skips files > ~384 KB → a floor, not a complete list
Machines with reachable cloud/CI/AI credentialsPure read environments without editor auto-run and without reachable secretsPayload harvests AWS, Azure, GCP, Vault, Kubernetes, npm, GitHub; exfiltrates to attacker-created public GitHub repos; propagates with stolen tokens

Particularly sensitive is the Azure/durabletask hit (1,718 stars): here the PAT of a real Microsoft contributor was stolen, the commit backdated to 2020-03-09 and hidden in a dormant branch — commit message Switched DataConverter to OrchestrationContext [skip ci]. THN also reports a GitHub sweep that disabled 73 Microsoft repos across four organisations (Azure, Azure-Samples, Microsoft, MicrosoftDocs) in roughly 105 seconds. Important on the source's honest uncertainty: SafeDep names 113 indexed repos as a floor; the 4.3 MB setup.js itself never indexes, the campaign is given away by the small launcher files. Three different setup.js hashes across four accounts show: the dropper is recompiled per wave, not copied verbatim.

Impact

The core is not “yet another worm” but the change of vector. Supply-chain malware historically lived off the install hook: preinstall, postinstall, setup.py or the binding.gyp trick. This wave skips the registry entirely and bets on the editor. “Cloning a repo to read its source” always felt safe. AI coding agents and IDE auto-run features quietly changed that — and attackers noticed before most defenders did.

Concretely, every trigger hangs on a legitimate feature: Claude Code and Gemini CLI run a shell command via a SessionStart hook when an agent session opens in the project (.claude/settings.json/.gemini/settings.json with "command": "node .github/setup.js"). Cursor uses an always-applied project rule (.cursor/rules/setup.mdc, alwaysApply: true) that instructs the agent to run the file — social engineering against the assistant, a prompt injection that ships in the repo. VS Code uses a task with "runOn": "folderOpen" — no agent is even needed, merely opening the folder suffices. And the package.jsontest hijack detonates in CI and for anyone who runs the tests.

The reach: the worm deliberately keeps off the victim's Node install via Bun, harvests credentials across AWS/Azure/GCP/Vault/Kubernetes/npm/GitHub, exfiltrates to fresh public GitHub dead-drop repos (for this arm SafeDep additionally found the accounts windy629 with 200+ repos and HerGomUli, all with the description “Miasma - The Spreading Blight”) and propagates onward with the stolen tokens. There is no CVE number — it is an ongoing supply-chain incident, not a single product flaw. The operational severity is high nonetheless, because the trigger is exactly the action that feels safest: opening a third-party repo to read it.

Mitigation / immediate steps

Note: the following steps are my operational recommendation based on the technique documented by SafeDep — not a vendor-certified guide. The authoritative, continuously maintained repo/IoC list is at SafeDep.

Operational Decision Block

Step 1 — Check before opening (clone is safe, opening is not)

 

# quick check for the dropper, BEFORE an editor/agent opens the repo
test -f .github/setup.js && echo "DROPPER PRESENT — do NOT open this repo in an editor"

# find the campaign's auto-run triggers
ls -la .claude/settings.json .gemini/settings.json .cursor/rules/setup.mdc .vscode/tasks.json 2>/dev/null
grep -RIn "node .github/setup.js" .claude .gemini .cursor .vscode package.json 2>/dev/null

 

Step 2 — Remove affected clones

 

- do NOT open the working copy in VS Code/Cursor/Claude Code/Gemini, no npm test
- delete the clone and re-clone from a commit BEFORE the injection
  (or wait for the maintainer revert)
- reconcile the IoC list at the SafeDep source (floor, not a complete list)

 

Step 3 — Rotate credentials (on a hit)

 

If an affected repo was opened/tested, treat the environment as compromised:
- rotate GitHub (incl. PATs), npm, cloud tokens (AWS/Azure/GCP), Vault, Kubernetes tokens
- assume local credential/auth caches are exposed (cloud CLI caches, AI-tool auth)
- check outbound connections/Bun download (oven-sh release) and /tmp/p<rand>.js, /tmp/b-<rand>/bun</rand></rand>

 

Step 4 — Contain editor/agent auto-run

 

- configure auto-run features restrictively for cloned THIRD-PARTY repos:
  * VS Code: disable automatic tasks / "runOn": "folderOpen" for untrusted folders
    (use Workspace Trust; "Restricted Mode" for foreign folders)
  * Claude Code / Gemini CLI: do not adopt SessionStart hooks from the repo unchecked
  * Cursor: review project-shipped "alwaysApply" rules before the first agent run
- treat .claude/ .gemini/ .cursor/ .vscode/ in code review like executable code

Detection / verification

IOCs taken directly from the technique documented by SafeDep; the running list is at the source.

Planted files and triggers

 

# campaign footprint in the working tree (six files, five triggers + dropper)
find . \( -path '*/.github/setup.js' -o -path '*/.claude/settings.json' \
  -o -path '*/.gemini/settings.json' -o -path '*/.cursor/rules/setup.mdc' \
  -o -path '*/.vscode/tasks.json' \) -print 2>/dev/null

# SessionStart/folderOpen/test hooks that launch setup.js
grep -RIn "SessionStart\|runOn.*folderOpen\|node .github/setup.js" \
  .claude .gemini .cursor .vscode package.json 2>/dev/null

 

Suspicious commits

 

# campaign-typical commit signatures (unsigned)
git log --all --pretty='%H %an <%ae> %s' | grep -iE \
  "chore: update dependencies \[skip ci\]|\[skip ci\]"

# backdated commits (Azure wave: author date 2020 on a fresh commit)
git log --all --format='%ci | %ai | %an | %s' \
  | awk -F' \\| ' '{ if (substr($1,1,4) != substr($2,1,4)) print }'

 

Known IoC hashes (excerpt, floor)

 

setup.js (icflorescu/taxepfa wave): d630397de8b01af0f6f5cf4463da91b17f28195a2c50c8f3f38ad9f7873fdb8e
setup.js (Azure/durabletask, amdeel): 3a9db5ba0c8cd4c91e91717df6b1a141fc1e0fbc0558b5a78d7f5c23f5b2a150
_p payload (icflorescu wave):         633c8410ee0413ca4b090a19c30b20c03f31598c25247c484846fa34c1df5b64
Dead-drop accounts: windy629 (200+), HerGomUli, liuende501 (npm arm, 236)
Bun download: github.com/oven-sh/bun/releases/download/bun-v1.3.13/

Operator guidance

Mid-market

Scope first, then rotate. Determine who on the team cloned third-party repos in recent days and opened them in an AI coding agent or VS Code — that is the exposed population, not “who installed an npm package”. Grep suspicious clones before opening (test -f .github/setup.js). Where a repo was opened or tested, treat the reachable tokens as compromised. The most important organisational measure: make it clear that “just cloning and opening a repo to read it” is no longer automatically safe — that single habit is the trigger.

Enterprise

Additionally: bring .claude/, .gemini/, .cursor/, .vscode/ into code review and diff policy as executable supply-chain artifacts (required reviews, alert on new auto-run hooks) — most review workflows ignore these directories as “editor noise”. Configure editor/agent auto-run centrally: VS Code Workspace Trust / Restricted Mode for foreign folders, no unchecked SessionStart hooks from cloned repos, review of project-shipped Cursor alwaysApply rules. Bring AI coding agents into the threat model as executing actors — a .cursor/rules file is a prompt injection that ships in the repo.

Kubernetes / containers

Build/CI runners that check out repos and run npm test or editor tasks belong in minimally privileged, ephemeral environments without broadly reachable cloud/Kubernetes credentials. Scope service-account tokens tightly, limit secret access per workload, restrict outbound network access from build runners (the dropper downloads Bun and exfiltrates via HTTPS to GitHub dead-drops). That way a single opened clone does not become a cluster-wide credential leak.

Declarative stacks (NixOS / Talos / Flatcar)

The lever here is provability and the reproducible dev environment: if editor/agent configuration is managed centrally and declaratively, repo-shipped .claude//.cursor/ overrides stand out because they deviate from the known-good configuration. Reproducible, ephemeral dev containers for third-party repos are the clean approach: an opened untrusted repo then runs in a throwaway environment without access to production credentials.

What I actually did

For weeks I have treated AI coding agents as what they are in security terms: executing actors with repo-shipped configuration. On this occasion, for managed stacks, that meant: ran a sweep over local and CI clones for the six-file footprint (.github/setup.js plus the four auto-run configs plus the package.jsontest hijack), grepped suspicious clones before opening, reconciled the SafeDep IoC list as a floor, and contained editor/agent auto-run for third-party repos (VS Code Workspace Trust, no unchecked SessionStart hooks, review of project-shipped Cursor rules).

The honest lesson connects directly to my npm posts. In the binding.gyp wave (4 June) the defence was --ignore-scripts and short-lived CI credentials; with IronWorm (6 June) workflow integrity and Trusted-Publishing scope were added. This wave shows the next door: even if you disable install hooks completely, you are not protected when the trigger sits in the editor. --ignore-scripts does not catch a SessionStart hook. So I extended the control: the four agent/editor configuration directories are now part of my diff policy (review-required, alert on new auto-run entries), and untrusted third-party repos are opened in ephemeral dev containers without production credentials.

A closer look: why “opening” is now the dangerous step

The mechanism is so effective because every single building block is a legitimate feature turned against the user. A SessionStart hook is exactly what it is meant to be — project setup at the start of an agent session; only here “setup” is a credential stealer. An alwaysApply Cursor rule is exactly what it is meant to be — giving the agent project-wide instructions; only the instruction is “run node .github/setup.js”. A VS Code folderOpen task is exactly what it is meant to be — convenience on opening; only it fires without any agent. SafeDep's phrasing nails it: “A .cursor/rules file that instructs an agent to run a script is a prompt injection that ships in the repo. A SessionStart hook is a postinstall for your editor.” The consequence for the threat model is uncomfortable but clear: the mental default “cloning and reading is safe” no longer holds once an AI coding agent or an IDE with auto-run is involved. Treat opening a third-party repo like running its code — because that is exactly what it can be.

Frequently asked questions about the Miasma editor wave

Am I affected if I only cloned a repo but did not open it?+

No. Per SafeDep, cloning is safe, opening is not. The trigger is git cloneplus opening the folder in VS Code/Cursor or starting a Claude Code/Gemini session — or npm test. Check a clone before opening with test -f .github/setup.js; if the file is there, do not open the repo in an editor — delete it and re-clone from a clean commit.

Does --ignore-scripts protect against this Miasma wave?+

No — and that is the crux. --ignore-scripts stops npm lifecycle hooks (preinstall/postinstall), but this wave publishes no package at all. The trigger sits in the editor: a SessionStart hook in .claude//.gemini/, an alwaysApply rule in .cursor/, a folderOpen task in .vscode/. The right control is to contain editor/agent auto-run (VS Code Workspace Trust/Restricted Mode) and review these directories as executable code.

Are the npm packages of mantine-datatable & co. compromised?+

For this (source-repo) arm, no: SafeDep states explicitly that the published npm packages of these projects are clean — the risk is local and survives npm uninstall. Note the distinction: for individual accounts (e.g. jagreehal) an npm arm with compromised packages ran in parallel (per StepSecurity) — that is the separate wave we covered on 4 June, not this source-repo vector.

How did malicious code get into the official Azure/durabletask repo?+

Per SafeDep, via a stolen personal access token of a real Microsoft contributor; the commit was backdated to 2020-03-09 and hidden in a dormant branch (message Switched DataConverter to OrchestrationContext [skip ci]). THN reports a GitHub sweep that disabled 73 Microsoft repos across four organisations in roughly 105 seconds. This fits Miasma's pattern: steal the token, then push into every reachable repo with write access.

Is this the same thing as the binding.gyp worm or IronWorm?+

Same family (Miasma/Shai-Hulud), different detonation surface. The binding.gyp/“Phantom Gyp” post (4 June) covers the npm install vector; IronWorm (6 June) is its own Rust campaign with Trusted-Publishing abuse and an eBPF rootkit. This wave is the third vector: direct source-repo injection with editor/AI-agent auto-run as the trigger. Shared loader (Bun stager), different ignition mechanism.

Is there a CVE or a patch?+

No. This is an ongoing supply-chain campaign, not a single product flaw — no CVE, no vendor patch. The “fix” is operational: avoid/delete affected clones (IoC list at SafeDep), grep before opening, rotate exposed credentials, contain editor/agent auto-run for third-party repos, and review .claude//.gemini//.cursor//.vscode/ as executable code.

Conclusion

Miasma is familiar at the fundamentals — stolen token, credential sweep, self-propagation are known from Shai-Hulud, the binding.gyp wave and IronWorm. What's new is the mouth: the worm followed the developer from the package manager into the editor. The unsettling part is not the dropper's sophistication but how banal the trigger is — opening a third-party repo to read it, the safest gesture in a developer's day. For me the lesson continues the last weeks: one layer of protection is not enough, and --ignore-scripts catches no SessionStart hook. Anyone using AI coding agents must treat them as executing supply-chain actors, review their configuration directories and contain auto-run for third-party repos. Because the incident is ongoing, the SafeDep source governs the repo list, not a snapshot. Don't dramatise — but check today who opened what.

Sources

Before the next opened repo harvests your cloud keys — let's talk about your agent hygiene.

I check, mitigate and validate your AI-coding-agent and editor supply chain — from the clone-hygiene check to auto-run hardening.

Sweep over local and CI clones for the Miasma footprint, clone-hygiene checks before opening, rotation of exposed cloud/CI/AI credentials, containment of editor/agent auto-run (VS Code Workspace Trust, SessionStart hook policy, Cursor rule review) and inclusion of .claude//.gemini//.cursor//.vscode/ in your diff and review policy.

Platform operations instead of advice-on-paper: I check, mitigate and validate production developer workflows — from clone hygiene through auto-run hardening to credential rotation.

Book an appointment directly

About the author

[Translate to English:] Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freelance DevSecOps consultant · OnlyOle Consulting

Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.

npm, binding.gyp, node-gyp, supply chain, worm, credential harvesting, CI/CD, GitHub Actions, Bun, AES-128-GCM, dangling commit, autotel, awaitly, node-env-resolver, StepSecurity, ignore-scripts, DevSecOps

binding.gyp npm worm (node-gyp)

Ongoing npm supply chain incident: a self-replicating worm uses binding.gyp/node-gyp instead of postinstall, downloads the Bun runtime, harvests cloud/registry credentials, injects setup-bun into GitHub Actions workflows and poisons further packages of the victim. With mitigation, detection IOCs and operator guidance — package list delegated to the primary source.

IronWorm, npm, supply chain, worm, Rust, eBPF, rootkit, Trusted Publishing, OIDC, GitHub Actions, credential harvesting, CI/CD, Shai-Hulud, JFrog, Kubernetes, Docker, Bun, DevSecOps

IronWorm (Rust npm worm)

The Rust npm worm IronWorm runs via a preinstall hook, harvests 86 environment variables including AI provider keys and ~/.claude credentials, self-publishes in CI via npm Trusted Publishing, exfiltrates without C2 through swapped GitHub Actions workflows, and hides behind an eBPF rootkit (which fails under kernel lockdown). With mitigation, detection IOCs, a root-cause deep dive and operator guidance; package list delegated to JFrog.

npm, supply chain, Mini Shai-Hulud, Miasma, TeamPCP, @redhat-cloud-services, preinstall, GitHub Actions, OIDC, CI/CD, credential theft, cloud identity, AWS, GCP, Azure, Kubernetes, MCP, dead man switch, DevSecOps, NIS-2, GDPR, Mittelstand

Miasma npm worm (@redhat-cloud-services)

Incident analysis of the Miasma wave: 32 compromised @redhat-cloud-services npm packages, a variant of TeamPCP's open-sourced Mini Shai-Hulud. Patient zero was a compromised Red Hat employee GitHub account with orphan commits to two RedHatInsights repos; publishing ran via GitHub Actions OIDC trusted publishing including Sigstore. A preinstall hook runs a 4.2 MB obfuscated loader, harvests GitHub/AWS/GCP/Azure/Kubernetes/Vault/npm/SSH/Docker credentials, newly also collects GCP and Azure cloud identities, installs kitty-monitor persistence and a destructive gh-token-monitor dead-man switch. Operational assessment: critical. Order: isolate, remove persistence, then rotate.