Table of Contents
A 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 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.
Paste or upload a lockfile, parsed locally against 20 packages.
TL;DR #
20 npm packages under the LeoPlatform / LeoInsights org received malicious updates at2026-06-24T23:04:55Z
- Every infected package contains a
binding.gyp
that triggers the payload duringnpm install
, 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
- The compromised maintainer account (
czirker
) also pushed orphansnapshot-*
branches 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
The 20 infected packages #
All 20 packages were published within the same 3-second window. The npm registry time
metadata confirms they share a single automated publish run:
| Ecosystem | Package | Version | |
|---|---|---|---|
| 1 | npm | rstreams-shard-util | 1.0.1 |
| 2 | npm | leo-logger | 1.0.8 |
| 3 | npm | rstreams-metrics | 2.0.2 |
| 4 | npm | leo-cdk-lib | 0.0.2 |
| 5 | npm | leo-auth | 4.0.6 |
| 6 | npm | leo-streams | 2.0.1 |
| 7 | npm | serverless-convention | 2.0.4 |
| 8 | npm | leo-cache | 1.0.2 |
| 9 | npm | leo-connector-elasticsearch | 2.0.6 |
| 10 | npm | leo-connector-mysql | 3.0.3 |
| 11 | npm | leo-connector-redshift | 3.0.6 |
| 12 | npm | leo-connector-mongo | 3.0.8 |
| 13 | npm | leo-sdk | 6.0.19 |
| 14 | npm | serverless-leo | 3.0.14 |
| 15 | npm | leo-cli | 3.0.3 |
| 16 | npm | leo-config | 1.1.1 |
| 17 | npm | leo-cron | 2.0.2 |
| 18 | npm | leo-aws | 2.0.4 |
| 19 | npm | leo-connector-oracle | 2.0.1 |
| 20 | npm | solo-nav | 1.0.1 |
| No matching rows |
The highest-traffic targets are leo-logger
(3,140 weekly downloads), `leo-sdk`
(1,830), `leo-aws`
(1,730), `leo-config`
(1,709), and `leo-streams`
(1,497). Four packages under the same maintainers were not infected: `leo-connector-common`
, `leo-connector-entity-table`
, `leo-connector-postgres`
, and `leo-connector-sqlserver`
. All four have their npm latest
dist-tag pointing to a prerelease version (-rc
or -beta
). The worm likely skips packages where the latest tag is not a stable release.
How the infection works #
Every infected package received the same three modifications compared to its previous clean version.
1. A new binding.gyp file. The file contains a single node-gyp target that uses command expansion to run
node index.js
during npm install
:The <!(...)
syntax is a GYP command expansion that runs a shell command during project generation. npm automatically invokes node-gyp rebuild
when a binding.gyp
is present, regardless of whether the package.json
defines any install
or postinstall
script. This bypasses tools that only inspect lifecycle scripts.
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:
Each 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:
| Blob | Purpose | Decrypted SHA256 |
|---|---|---|
_b |
Bun runtime bootstrapper (907 bytes) | ceff7c51d70832...ea154108 |
_p |
Worm payload (781,580 bytes) | 9f93d77d328338...9a6db015 |
The _b
blob downloads Bun 1.3.13 from GitHub releases, caches the binary in a temp directory, and exposes a global getBunPath()
function. The _p
blob (the worm) is written to /tmp/p<random>.js
and executed via bun run
. The temp file is deleted after execution.
3. A new bun dependency. Every infected
package.json
adds "bun": "^1.3.13"
. This is the npm Bun installer package, likely included as a fallback path for environments where the bootstrapper’s
curl
download fails.## Root cause: one compromised maintainer
The npm account czirker
(Clint Zirker,
) is the only maintainer present on all 20 infected packages. Other maintainers like [email protected]leoinsights
, jgrantr
, and elsmob
appear on subsets, but czirker
is the common denominator. The worm used this account’s npm token for the mass publish and its GitHub token for the repo-level attacks.
A registry metadata query confirms the maintainer list:
The jump from 1.0.0
(November 2024) to 1.0.1
(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
and a binding.gyp
.
GitHub repo poisoning #
The worm did not stop at npm. GitHub event logs for three LeoPlatform repositories show czirker
creating orphan branches named snapshot-<hex>
at 22:50 UTC, roughly 14 minutes before the npm publishes:
The commit on the snapshot-f121a878
branch of LeoPlatform/Nodejs
tells the story. It is an orphan commit (no parent) authored as czirker
, with the message “chore: update dependencies”. It adds two files:
The _index.js
is the same 5.2 MB worm payload. The workflow is a weaponized GitHub Actions pipeline:
Three things stand out. The workflow triggers on every push
to any branch. It requests id-token: write
, which grants access to a GitHub OIDC token that can be exchanged for npm publish credentials via npm’s trusted publishing. And it is named “Dependabot Updates” to blend in with legitimate dependency PRs.
A follow-up commit, this time impersonating dependabot[bot]
, replaced the OIDC parameters with a direct NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
reference, suggesting the worm tries multiple publish strategies. Both the actions/checkout
and oven-sh/setup-bun
SHAs point to legitimate releases (a January 2026 checkout fix and Bun setup v2.2.0, respectively).
The master branch of LeoPlatform/Nodejs
is 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.
The worm payload #
The inner code uses the standard javascript-obfuscator pattern: a _0x66ee
string lookup table with 2,588 entries, a _0x42e6
decoder function, and a secondary runtime-constructed decoder (fb12914b2
) called 519 times to decrypt environment variable names and API endpoints.
Static string analysis of the decrypted payload reveals the same capability set documented in our Miasma source code analysis: 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.
Secret scanning via regex patterns for auth tokens, private keys, and .npmrc
credentials:
AI coding tool targeting, the Miasma family signature: the payload references claudeSettingsPath
, cursorRulesPath
, geminiSettingsPath
, and vscodeTasksPath
.
npm worm propagation with automated package enumeration (npmRepos
, maxPackages
), version bumping (newVersion
), and publish tracking (totalPackages
, published
, failed
, publishStepIndex
).
GitHub Actions workflow scanning using regex for npm publish
and yarn publish
in CI configs, with hasIdTokenWrite
checks for OIDC-based publishing.
For a complete breakdown of each module, see Inside the Miasma Software Supply Chain Attack Toolkit.
Indicators of compromise #
| Type | Indicator | Context | |
|---|---|---|---|
| 1 | File | binding.gyp | Install-time trigger, added to every infected package |
| 2 | File Pattern | index.js (~5.2 MB single line) | ROT-N + AES-128-GCM obfuscated worm payload replacing original |
| 3 | npm Dependency | bun@^1.3.13 | Added to all infected packages to bootstrap Bun runtime |
| 4 | SHA256 (Bun bootstrapper) | ceff7c51d70832c3ec8dd2744b606a23b3c924ef664ae23439b9b742ea154108 | Decrypted _b blob (identical across all 20 packages) |
| 5 | SHA256 (worm payload) | 9f93d77d32833a515bc406c46da477142bb1ac2babeecb6aa42f98669a6db015 | Decrypted _p blob (identical across all 20 packages) |
| 6 | SHA1 (leo-logger-1.0.8.tgz) | 24a0d9e496ec07ca978fab602d5f5e0b39fa03a0 | Infected tarball |
| 7 | SHA1 (serverless-convention-2.0.4.tgz) | 5e75c14b8acd5752819ab7a10874ddd6389f5238 | Infected tarball |
| 8 | SHA1 (leo-cache-1.0.2.tgz) | e973173fb757d2dab9c6424b440dd9f7cbe4f14a | Infected tarball |
| 9 | SHA1 (rstreams-shard-util-1.0.1.tgz) | a8cb86b78ca56befe90dc466642cb04b98079909 | Infected tarball |
| 10 | GitHub Branch Pattern | snapshot-<8 hex chars> | Orphan branches created by the worm on compromised repos |
| 11 | GitHub Commit Author | dependabot[bot] | Impersonated author on worm commits |
| 12 | GitHub File | _index.js (~5.2 MB) | Worm payload dropped into GitHub repos |
| 13 | GitHub Workflow Name | Dependabot Updates | Weaponized workflow disguised as Dependabot |
| 14 | GYP Command | <!(node index.js > /dev/null 2>&1 && echo stub.c) | node-gyp command expansion trigger in binding.gyp |
| 15 | Bun Download URL | github.com/oven-sh/bun/releases/download/bun-v1.3.13/ | Runtime downloaded by the Bun bootstrapper |
| 16 | Temp File Pattern | /tmp/p<random>.js | Worm payload written to disk before Bun execution |
| No matching rows |
Quick detection check. Any npm package that added a binding.gyp
containing <!(node index.js
in a recent version bump, combined with a new "bun"
dependency and an index.js
that grew to several megabytes, should be treated as infected.
Infected packages (CSV) #
| Ecosystem | Package | Version | |
|---|---|---|---|
| 1 | npm | rstreams-shard-util | 1.0.1 |
| 2 | npm | leo-logger | 1.0.8 |
| 3 | npm | rstreams-metrics | 2.0.2 |
| 4 | npm | leo-cdk-lib | 0.0.2 |
| 5 | npm | leo-auth | 4.0.6 |
| 6 | npm | leo-streams | 2.0.1 |
| 7 | npm | serverless-convention | 2.0.4 |
| 8 | npm | leo-cache | 1.0.2 |
| 9 | npm | leo-connector-elasticsearch | 2.0.6 |
| 10 | npm | leo-connector-mysql | 3.0.3 |
| 11 | npm | leo-connector-redshift | 3.0.6 |
| 12 | npm | leo-connector-mongo | 3.0.8 |
| 13 | npm | leo-sdk | 6.0.19 |
| 14 | npm | serverless-leo | 3.0.14 |
| 15 | npm | leo-cli | 3.0.3 |
| 16 | npm | leo-config | 1.1.1 |
| 17 | npm | leo-cron | 2.0.2 |
| 18 | npm | leo-aws | 2.0.4 |
| 19 | npm | leo-connector-oracle | 2.0.1 |
| 20 | npm | solo-nav | 1.0.1 |
| No matching rows |
Related posts #
Inside the Miasma Software Supply Chain Attack Toolkit, source code analysis of the Miasma wormMiasma Worm Targets AI Coding Agents via GitHub Repos, the GitHub repo persistence variantMini Shai-Hulud Hits @redhat-cloud-services, 32 packages compromised via OIDC trusted publishing
- npm
- oss
- malware
- supply-chain
- shai-hulud
- ai-coding-agents
- github
Author
SafeDep Team
safedep.io
Share
The Latest from SafeDep blogs #
Follow for the latest updates and insights on open source security & engineering
The wshu.net npm Campaign Delivers a Multi-Stage Infostealer One actor seeded 15 npm packages across 13 throwaway scopes in a single morning, each shipping a ~270KB obfuscated down behind a postinstall hook. The down pulls a Rust infostealer from...
@withgoogle/stitch-sdk: Scope Squat Harvests Developer Credentials A 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.
MYRA: A Full Linux RAT Distributed via npm The 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...
Five npm Packages That Hide a Windows Binary Dropper Five npm packages published in a 12-minute burst split a Windows binary dropper across a fake utility toolkit. The hides in a preinstall hook, decodes its C2 from a helper package, and fetches...
Ship Code. #
Not Malware. #
Start free with open source tools on your machine. Scale to a unified platform for your organization.