{"slug": "miasma-worm-infects-multiple-leoplatform-npm-packages", "title": "Miasma Worm Infects Multiple LeoPlatform npm Packages", "summary": "A Miasma worm variant infected 20 npm packages under the LeoPlatform organization on June 24, 2026, after an attacker compromised a single maintainer's tokens. The malicious payload, a polymorphic credential stealer and self-propagating worm, was delivered via a binding.gyp file that bypasses lifecycle script scanners. Combined weekly downloads of the affected packages total approximately 13,600.", "body_md": "# Miasma Worm Infects Multiple LeoPlatform npm Packages\n\n### Table of Contents\n\nA Miasma worm variant hit the LeoPlatform npm ecosystem on June 24, 2026. The attacker compromised a single maintainer’s npm and GitHub tokens and used them to publish infected versions of 20 packages in a 3-second burst. The same tokens pushed weaponized GitHub Actions workflows, disguised as Dependabot, to at least three repos. The payload matches the [Miasma supply chain attack toolkit](/inside-the-miasma-supply-chain-attack-toolkit) documented in our earlier source code analysis: a polymorphically packed Bun-based credential stealer and self-propagating worm that targets npm, PyPI, RubyGems, GitHub, AWS, Kubernetes, HashiCorp Vault, and AI coding tool configurations.\n\nPaste or upload a lockfile, parsed locally against 20 packages.\n\n## TL;DR\n\n**20 npm packages** under the LeoPlatform / LeoInsights org received malicious updates at`2026-06-24T23:04:55Z`\n\n- Every infected package contains a\n`binding.gyp`\n\nthat triggers the payload during`npm install`\n\n, bypassing lifecycle script scanners - The payload is identical across all 20 packages after decryption (same SHA256), packed with per-package ROT cipher values and AES-128-GCM keys\n- The compromised maintainer account (\n`czirker`\n\n) also pushed orphan`snapshot-*`\n\nbranches to three GitHub repos, each carrying a 5.2 MB worm payload and a fake “Dependabot Updates” workflow - Combined weekly download count across the 20 packages is roughly 13,600\n\n## The 20 infected packages\n\nAll 20 packages were published within the same 3-second window. The npm registry `time`\n\nmetadata confirms they share a single automated publish run:\n\n| Ecosystem | Package | Version | |\n|---|---|---|---|\n| 1 | npm | rstreams-shard-util | 1.0.1 |\n| 2 | npm | leo-logger | 1.0.8 |\n| 3 | npm | rstreams-metrics | 2.0.2 |\n| 4 | npm | leo-cdk-lib | 0.0.2 |\n| 5 | npm | leo-auth | 4.0.6 |\n| 6 | npm | leo-streams | 2.0.1 |\n| 7 | npm | serverless-convention | 2.0.4 |\n| 8 | npm | leo-cache | 1.0.2 |\n| 9 | npm | leo-connector-elasticsearch | 2.0.6 |\n| 10 | npm | leo-connector-mysql | 3.0.3 |\n| 11 | npm | leo-connector-redshift | 3.0.6 |\n| 12 | npm | leo-connector-mongo | 3.0.8 |\n| 13 | npm | leo-sdk | 6.0.19 |\n| 14 | npm | serverless-leo | 3.0.14 |\n| 15 | npm | leo-cli | 3.0.3 |\n| 16 | npm | leo-config | 1.1.1 |\n| 17 | npm | leo-cron | 2.0.2 |\n| 18 | npm | leo-aws | 2.0.4 |\n| 19 | npm | leo-connector-oracle | 2.0.1 |\n| 20 | npm | solo-nav | 1.0.1 |\n| No matching rows |\n\nThe highest-traffic targets are `leo-logger`\n\n(3,140 weekly downloads), `leo-sdk`\n\n(1,830), `leo-aws`\n\n(1,730), `leo-config`\n\n(1,709), and `leo-streams`\n\n(1,497). Four packages under the same maintainers were not infected: `leo-connector-common`\n\n, `leo-connector-entity-table`\n\n, `leo-connector-postgres`\n\n, and `leo-connector-sqlserver`\n\n. All four have their npm `latest`\n\ndist-tag pointing to a prerelease version (`-rc`\n\nor `-beta`\n\n). The worm likely skips packages where the latest tag is not a stable release.\n\n## How the infection works\n\nEvery infected package received the same three modifications compared to its previous clean version.\n\n**1. A new binding.gyp file.** The file contains a single node-gyp target that uses command expansion to run\n\n`node index.js`\n\nduring `npm install`\n\n:The `<!(...)`\n\nsyntax is a [GYP command expansion](https://gyp.gsrc.io/docs/InputFormatReference.md#Command-Expansions-(!)) that runs a shell command during project generation. npm automatically invokes `node-gyp rebuild`\n\nwhen a `binding.gyp`\n\nis present, regardless of whether the `package.json`\n\ndefines any `install`\n\nor `postinstall`\n\nscript. This bypasses tools that only inspect lifecycle scripts.\n\n**2. A replaced index.js.** The original module code is wiped and replaced with a single-line obfuscated payload of roughly 5.2 MB. The obfuscation has three layers:\n\nEach package uses a different ROT value (5, 8, 19, or 23) and a different set of AES-128-GCM keys. After decryption, every package yields the same two blobs:\n\n| Blob | Purpose | Decrypted SHA256 |\n|---|---|---|\n`_b` | Bun runtime bootstrapper (907 bytes) | `ceff7c51d70832...ea154108` |\n`_p` | Worm payload (781,580 bytes) | `9f93d77d328338...9a6db015` |\n\nThe `_b`\n\nblob downloads [Bun 1.3.13](https://bun.sh) from GitHub releases, caches the binary in a temp directory, and exposes a global `getBunPath()`\n\nfunction. The `_p`\n\nblob (the worm) is written to `/tmp/p<random>.js`\n\nand executed via `bun run`\n\n. The temp file is deleted after execution.\n\n**3. A new bun dependency.** Every infected\n\n`package.json`\n\nadds `\"bun\": \"^1.3.13\"`\n\n. This is the [npm Bun installer package](https://www.npmjs.com/package/bun), likely included as a fallback path for environments where the bootstrapper’s\n\n`curl`\n\ndownload fails.## Root cause: one compromised maintainer\n\nThe npm account `czirker`\n\n(Clint Zirker,\n\n) is the only maintainer present on all 20 infected packages. Other maintainers like [[email protected]](/cdn-cgi/l/email-protection)`leoinsights`\n\n, `jgrantr`\n\n, and `elsmob`\n\nappear on subsets, but `czirker`\n\nis the common denominator. The worm used this account’s npm token for the mass publish and its GitHub token for the repo-level attacks.\n\nA registry metadata query confirms the maintainer list:\n\nThe jump from `1.0.0`\n\n(November 2024) to `1.0.1`\n\n(June 24, 2026) is the infected version. This pattern repeats across all 20 packages: a long-dormant legitimate package suddenly receives a new version with a 5 MB `index.js`\n\nand a `binding.gyp`\n\n.\n\n## GitHub repo poisoning\n\nThe worm did not stop at npm. GitHub event logs for three LeoPlatform repositories show `czirker`\n\ncreating orphan branches named `snapshot-<hex>`\n\nat 22:50 UTC, roughly 14 minutes before the npm publishes:\n\nThe commit on the `snapshot-f121a878`\n\nbranch of `LeoPlatform/Nodejs`\n\ntells the story. It is an orphan commit (no parent) authored as `czirker`\n\n, with the message “chore: update dependencies”. It adds two files:\n\nThe `_index.js`\n\nis the same 5.2 MB worm payload. The workflow is a weaponized GitHub Actions pipeline:\n\nThree things stand out. The workflow triggers on every `push`\n\nto any branch. It requests `id-token: write`\n\n, which grants access to a GitHub OIDC token that can be exchanged for npm publish credentials via [npm’s trusted publishing](https://docs.npmjs.com/generating-provenance-statements). And it is named “Dependabot Updates” to blend in with legitimate dependency PRs.\n\nA follow-up commit, this time impersonating `dependabot[bot]`\n\n, replaced the OIDC parameters with a direct `NPM_TOKEN: ${{ secrets.NPM_TOKEN }}`\n\nreference, suggesting the worm tries multiple publish strategies. Both the `actions/checkout`\n\nand `oven-sh/setup-bun`\n\nSHAs point to legitimate releases (a January 2026 checkout fix and Bun setup v2.2.0, respectively).\n\nThe master branch of `LeoPlatform/Nodejs`\n\nis clean. The weaponized workflow lives only on the orphan snapshot branch, where it would execute if merged or if a CI configuration runs workflows from all branches.\n\n## The worm payload\n\nThe inner code uses the standard javascript-obfuscator pattern: a `_0x66ee`\n\nstring lookup table with 2,588 entries, a `_0x42e6`\n\ndecoder function, and a secondary runtime-constructed decoder (`fb12914b2`\n\n) called 519 times to decrypt environment variable names and API endpoints.\n\nStatic string analysis of the decrypted payload reveals the same capability set documented in our [Miasma source code analysis](/inside-the-miasma-supply-chain-attack-toolkit):\n\n**Credential theft** across npm, GitHub (PATs, OIDC, JWTs), PyPI, RubyGems, Kubernetes service account tokens, HashiCorp Vault, AWS (IAM keys, STS, IMDS, Secrets Manager, SSM), 1Password, JFrog Artifactory, and SSH private keys.\n\n**Secret scanning** via regex patterns for auth tokens, private keys, and `.npmrc`\n\ncredentials:\n\n**AI coding tool targeting**, the Miasma family signature: the payload references `claudeSettingsPath`\n\n, `cursorRulesPath`\n\n, `geminiSettingsPath`\n\n, and `vscodeTasksPath`\n\n.\n\n**npm worm propagation** with automated package enumeration (`npmRepos`\n\n, `maxPackages`\n\n), version bumping (`newVersion`\n\n), and publish tracking (`totalPackages`\n\n, `published`\n\n, `failed`\n\n, `publishStepIndex`\n\n).\n\n**GitHub Actions workflow scanning** using regex for `npm publish`\n\nand `yarn publish`\n\nin CI configs, with `hasIdTokenWrite`\n\nchecks for OIDC-based publishing.\n\nFor a complete breakdown of each module, see [Inside the Miasma Software Supply Chain Attack Toolkit](/inside-the-miasma-supply-chain-attack-toolkit).\n\n## Indicators of compromise\n\n| Type | Indicator | Context | |\n|---|---|---|---|\n| 1 | File | binding.gyp | Install-time trigger, added to every infected package |\n| 2 | File Pattern | index.js (~5.2 MB single line) | ROT-N + AES-128-GCM obfuscated worm payload replacing original |\n| 3 | npm Dependency | bun@^1.3.13 | Added to all infected packages to bootstrap Bun runtime |\n| 4 | SHA256 (Bun bootstrapper) | ceff7c51d70832c3ec8dd2744b606a23b3c924ef664ae23439b9b742ea154108 | Decrypted _b blob (identical across all 20 packages) |\n| 5 | SHA256 (worm payload) | 9f93d77d32833a515bc406c46da477142bb1ac2babeecb6aa42f98669a6db015 | Decrypted _p blob (identical across all 20 packages) |\n| 6 | SHA1 (leo-logger-1.0.8.tgz) | 24a0d9e496ec07ca978fab602d5f5e0b39fa03a0 | Infected tarball |\n| 7 | SHA1 (serverless-convention-2.0.4.tgz) | 5e75c14b8acd5752819ab7a10874ddd6389f5238 | Infected tarball |\n| 8 | SHA1 (leo-cache-1.0.2.tgz) | e973173fb757d2dab9c6424b440dd9f7cbe4f14a | Infected tarball |\n| 9 | SHA1 (rstreams-shard-util-1.0.1.tgz) | a8cb86b78ca56befe90dc466642cb04b98079909 | Infected tarball |\n| 10 | GitHub Branch Pattern | snapshot-<8 hex chars> | Orphan branches created by the worm on compromised repos |\n| 11 | GitHub Commit Author | dependabot[bot] | Impersonated author on worm commits |\n| 12 | GitHub File | _index.js (~5.2 MB) | Worm payload dropped into GitHub repos |\n| 13 | GitHub Workflow Name | Dependabot Updates | Weaponized workflow disguised as Dependabot |\n| 14 | GYP Command | <!(node index.js > /dev/null 2>&1 && echo stub.c) | node-gyp command expansion trigger in binding.gyp |\n| 15 | Bun Download URL | github.com/oven-sh/bun/releases/download/bun-v1.3.13/ | Runtime downloaded by the Bun bootstrapper |\n| 16 | Temp File Pattern | /tmp/p<random>.js | Worm payload written to disk before Bun execution |\n| No matching rows |\n\n**Quick detection check.** Any npm package that added a `binding.gyp`\n\ncontaining `<!(node index.js`\n\nin a recent version bump, combined with a new `\"bun\"`\n\ndependency and an `index.js`\n\nthat grew to several megabytes, should be treated as infected.\n\n## Infected packages (CSV)\n\n| Ecosystem | Package | Version | |\n|---|---|---|---|\n| 1 | npm | rstreams-shard-util | 1.0.1 |\n| 2 | npm | leo-logger | 1.0.8 |\n| 3 | npm | rstreams-metrics | 2.0.2 |\n| 4 | npm | leo-cdk-lib | 0.0.2 |\n| 5 | npm | leo-auth | 4.0.6 |\n| 6 | npm | leo-streams | 2.0.1 |\n| 7 | npm | serverless-convention | 2.0.4 |\n| 8 | npm | leo-cache | 1.0.2 |\n| 9 | npm | leo-connector-elasticsearch | 2.0.6 |\n| 10 | npm | leo-connector-mysql | 3.0.3 |\n| 11 | npm | leo-connector-redshift | 3.0.6 |\n| 12 | npm | leo-connector-mongo | 3.0.8 |\n| 13 | npm | leo-sdk | 6.0.19 |\n| 14 | npm | serverless-leo | 3.0.14 |\n| 15 | npm | leo-cli | 3.0.3 |\n| 16 | npm | leo-config | 1.1.1 |\n| 17 | npm | leo-cron | 2.0.2 |\n| 18 | npm | leo-aws | 2.0.4 |\n| 19 | npm | leo-connector-oracle | 2.0.1 |\n| 20 | npm | solo-nav | 1.0.1 |\n| No matching rows |\n\n## Related posts\n\n[Inside the Miasma Software Supply Chain Attack Toolkit](/inside-the-miasma-supply-chain-attack-toolkit), source code analysis of the Miasma worm[Miasma Worm Targets AI Coding Agents via GitHub Repos](/miasma-worm-ai-coding-agent-config-injection), the GitHub repo persistence variant[Mini Shai-Hulud Hits @redhat-cloud-services](/redhat-cloud-services-hit-by-mini-shai-hulud-npm-worm), 32 packages compromised via OIDC trusted publishing\n\n- npm\n- oss\n- malware\n- supply-chain\n- shai-hulud\n- ai-coding-agents\n- github\n\n### Author\n\n#### SafeDep Team\n\nsafedep.io\n\n### Share\n\n## The Latest from SafeDep blogs\n\nFollow for the latest updates and insights on open source security & engineering\n\n[The wshu.net npm Campaign Delivers a Multi-Stage Infostealer](/wshu-net-npm-credential-stealer-campaign)\n\nOne actor seeded 15 npm packages across 13 throwaway scopes in a single morning, each shipping a ~270KB obfuscated downloader behind a postinstall hook. The downloader pulls a Rust infostealer from...\n\n[@withgoogle/stitch-sdk: Scope Squat Harvests Developer Credentials](/withgoogle-stitch-sdk-scope-squat-credential-harvester)\n\nA malicious npm package squats the @withgoogle scope to impersonate Google Stitch, silently harvesting credentials from Claude Code, git, GitHub CLI, SSH keys, npm, and Docker on install.\n\n[MYRA: A Full Linux RAT Distributed via npm](/malicious-apintergrationpost-npm-myra-rat)\n\nThe npm package apintergrationpost is a red team RAT called MYRA with native C rootkit, triple persistence, fileless execution, live screen streaming, and process masquerade. This analysis documents...\n\n[Five npm Packages That Hide a Windows Binary Dropper](/procwire-npm-windows-dropper-campaign)\n\nFive npm packages published in a 12-minute burst split a Windows binary dropper across a fake utility toolkit. The loader hides in a preinstall hook, decodes its C2 from a helper package, and fetches...\n\n## Ship Code.\n\n## Not Malware.\n\nStart free with open source tools on your machine. Scale to a unified platform for your organization.", "url": "https://wpnews.pro/news/miasma-worm-infects-multiple-leoplatform-npm-packages", "canonical_source": "https://safedep.io/miasma-worm-hits-leoplatform-20-npm-packages", "published_at": "2026-06-25 10:00:00+00:00", "updated_at": "2026-06-25 04:23:29.485923+00:00", "lang": "en", "topics": ["ai-safety", "ai-policy", "ai-infrastructure"], "entities": ["LeoPlatform", "npm", "GitHub", "Miasma", "czirker", "Dependabot", "AWS", "Kubernetes"], "alternates": {"html": "https://wpnews.pro/news/miasma-worm-infects-multiple-leoplatform-npm-packages", "markdown": "https://wpnews.pro/news/miasma-worm-infects-multiple-leoplatform-npm-packages.md", "text": "https://wpnews.pro/news/miasma-worm-infects-multiple-leoplatform-npm-packages.txt", "jsonld": "https://wpnews.pro/news/miasma-worm-infects-multiple-leoplatform-npm-packages.jsonld"}}