Miasma Worm Targets AI Coding Agents via GitHub Repos On June 3, 2026, attackers pushed malicious commits to the GitHub repository `icflorescu/mantine-datatable` and four sibling repos, planting a 4.3 MB payload from the Miasma worm family. The commit added configuration files that automatically execute the payload when a developer opens the cloned repository in AI coding agents including Claude Code, Gemini CLI, Cursor, or VS Code, or runs `npm test`. The attack targets developers who clone the affected repos and open them in AI-assisted development tools, with the same worm fingerprint appearing across at least four accounts and more than a dozen repositories. Miasma Worm Targets AI Coding Agents via GitHub Repos Table of Contents On June 3, 2026, the Miasma worm hit two surfaces simultaneously. The npm registry arm published 57 malicious packages across 286+ versions, hiding the payload trigger in binding.gyp files to evade lifecycle script scanners — covered in depth by StepSecurity https://www.stepsecurity.io/blog/binding-gyp-npm-supply-chain-attack-spreads-like-worm and JFrog https://research.jfrog.com/post/shai-hulud-miasma-redhat-cloud-services/ . This post documents the other arm: a parallel run of the same worm that skipped the registry entirely and pushed directly to GitHub source repositories. An attacker pushed a commit titled chore: update dependencies skip ci to icflorescu/mantine-datatable and four sibling repos. The commit added no dependencies. It planted a 4.3 MB payload runner and wired it to execute automatically through five developer tools: Claude Code, Gemini CLI, Cursor, VS Code, and the npm test script. The attack detonates when a developer clones one of the affected repos and opens it in an AI coding agent. The dropper is the same staged Bun loader, here repurposed for GitHub source-repo persistence rather than registry poisoning. icflorescu was not the only maintainer hit. The same fingerprint appears across at least four accounts and more than a dozen repos, with the dropper recompiled per wave. The maintainer’s account was suspended during the incident, and his wife posted the disclosure https://github.com/icflorescu/mantine-datatable/discussions/813 on his behalf. The loader is a byte-level match for the Miasma family. The commit The malicious commit on mantine-datatable f72462d9e5fa90a483062a83e9ffcb2edc57bf7e https://github.com/icflorescu/mantine-datatable/commit/f72462d9e5fa90a483062a83e9ffcb2edc57bf7e is unsigned, authored as github-actions < email protected /cdn-cgi/l/email-protection , and adds six files:Five of those six files exist to launch the sixth. .github/setup.js is the payload. Everything else is a trigger pointed at it, one per tool. Five triggers, one payload The cleverness here is the trigger surface. Each config file abuses a legitimate auto-run feature of a different developer tool. Claude Code and Gemini CLI both use a SessionStart hook that runs a shell command when an agent session opens in the project: Cursor uses an always-applied project rule that instructs the agent to run the file, social-engineering the assistant into executing it: VS Code uses a task configured to run on folder open, so no agent is even required: The package.json change hijacks the test script, so CI and any developer running the project’s tests also detonate it: Cloning the repo is safe. Opening it is not. A developer who clones mantine-datatable to debug an issue and opens the folder in VS Code, or starts Claude Code in it, runs the payload with no further interaction. The dropper .github/setup.js is one statement wrapped in a try/catch . It builds a string from a character-code array, applies a Caesar shift, and passes the result to eval : Decoding it statically shift of 4, never executing it yields an async loader. It pulls node:crypto and AES-128-GCM decrypts two hardcoded blobs: p is the worm. b is a bootstrap. The loader writes p to a random temp file and runs it under Bun, falling back to downloading Bun if the host does not have it: b defines getBunPath , which fetches a pinned Bun release straight from the official GitHub mirror and marks it executable: Running under Bun keeps the worm off the victim’s Node install. Bun ships its own TypeScript runtime, fetch, crypto, and shell, so the payload needs nothing from the host beyond the downloaded binary. The decrypted p SHA256 633c8410…1df5b64 , 667 KB is the same Bun stealer family documented in the Miasma analysis /redhat-cloud-services-hit-by-mini-shai-hulud-npm-worm : a multi-cloud credential harvester that scans for AWS, Azure, GCP, Vault, Kubernetes, npm, and GitHub secrets, exfiltrates to attacker-created public GitHub repos, and self-propagates with stolen tokens. We did not re-run the full payload analysis on this wave. Blast radius: one worm, five repos, 49 seconds The same commit landed in five icflorescu repos inside a 49-second window. The dropper is byte-identical across all five SHA256 d630397d…873fdb8e , 4,348,254 bytes : | Repo | Stars | Pushed UTC | HEAD commit | |---|---|---|---| | mantine-datatable | 1,225 | 22:38:51 | f72462d9 | | mantine-contextmenu | 170 | 22:38:59 | 9ef8b396 | | next-server-actions-parallel | 56 | 22:39:19 | 01e00e78 | | mantine-datatable-v6 | 3 | 22:39:29 | 6592194 | | mantine-contextmenu-v6 | 5 | 22:39:40 | 5aa0201b | The five repos carry 1,459 GitHub stars between them, mantine-datatable alone accounting for 1,225. Stars are a rough proxy for how many developers have the source checked out locally, which is the population this attack targets. Every commit: unsigned, github-actions identity, chore: update dependencies skip ci , the same six-file footprint. A 49-second sweep across five repos is automation, not a human committing. This matches Shai-Hulud self-propagation: harvest a GitHub token with write access from a prior infection, then push the persistence payload into every repo the token can reach. Beyond one maintainer icflorescu is one node. A GitHub code search for the launcher string node .github/setup.js returns the same injection across other accounts. The matches GitHub has indexed so far: | Account | Repos hit indexed | setup.js build | Notes | |---|---|---|---| | icflorescu | 5 | d630397d… | mantine-datatable et al | | taxepfa | taxepfa.github.io | d630397d… identical | same github-actions / skip ci commit | | jagreehal | 7 ai-sdk-guardrails, ai-sdk-ollama, autotel, effect-analyzer, es-temp-action, jagreehal-claude-skills, stencil-how-to-test-components | fec7d585… | distinct build | | mhar-andal | 2 MyBlok, stock-forum-ethereum | 0ecf3e7b… | injected a Gemfile , so not npm-only | Three distinct setup.js hashes across four accounts means the dropper is recompiled per victim or per wave, not copied verbatim. The launchers and the staged-loader architecture are constant; the Caesar shift, the AES keys, and the resulting file hash rotate. Code search only covers indexed default branches and skips files over roughly 384 KB, so this is a floor, not a ceiling. The 4.3 MB setup.js itself never indexes; the small launcher files are what give the campaign away. For jagreehal the impact extends beyond source repos. StepSecurity’s analysis of the June 3 npm arm confirms the same account had 50+ npm packages compromised — ai-sdk-ollama , autotel , awaitly , executable-stories , node-env-resolver , and others — with 408,000+ monthly download packages like @vapi-ai/server-sdk also hit in the same wave. The source-repo injection and the npm package poisoning ran in parallel off the same stolen token. The exfiltration side The worm exfiltrates stolen credentials to attacker-created public GitHub repositories. StepSecurity identified the primary exfil account for the npm arm as liuende501 , holding 236 dead-drop repos — 34 described Miasma - The Spreading Blight and 195 carrying the reversed string niagA oG eW ereH :duluH-iahS decoding to “Shai-Hulud: Here We Go Again” . In our analysis of the source-repo arm, we found two additional exfil accounts: windy629 200+ repos and HerGomUli , both using the same Miasma - The Spreading Blight description. The existence of multiple exfil accounts points to either rotating infrastructure or parallel campaign nodes, not a single operator running one bucket. The timing ties the two arms together: the dead-drop windy629/savage-styx-88946 was created at 22:38:26Z, roughly 25 seconds before the first push to mantine-datatable at 22:38:51Z. Steal the token, dump the loot to a fresh dead-drop, then turn the same token on the victim’s own repos. Attribution: Miasma The loader is the Miasma /redhat-cloud-services-hit-by-mini-shai-hulud-npm-worm staged Bun loader, matched feature for feature: | Trait | Miasma RedHat sample | mantine wave | |---|---|---| | Outer cipher | eval function s,n {...replace / a-zA-Z /g...} | identical harness | | Caesar shift | ROT-9 | ROT-4 | | Loader | d= k,i,a,c = createDecipheriv "aes-128-gcm" , two blobs | identical | | Bun pin | bun-v1.3.13 from oven-sh | identical URL | | Temp artifacts | /tmp/p