Back to Blog
September 21, 2026

Inside Miasma: How One Compromised npm Account Turned Into a Multi-Ecosystem Supply Chain Attack

Viktor Bulanek
Founder & CTO, Penetrify
MSc IT Security · 20+ years in security · 4x Ex-CTO

In June 2026, researchers uncovered another evolution of the Mini Shai-Hulud malware family, dubbed Miasma. Unlike traditional npm malware that targets a handful of packages, this campaign showed how compromising a single piece of developer infrastructure can rapidly spread across multiple ecosystems: npm, GitHub Actions, and even Go modules.

The attackers didn't target end users. They targeted publishers, CI/CD pipelines, and the trust mechanisms that let software move from a developer's laptop to production without anyone re-checking it along the way. No CVE exists for any of it, because nothing about the affected code was ever flawed. The entry point was a stolen credential, not a vulnerability.

One thing worth flagging up front: on May 12, 2026, the group originally behind this worm family, TeamPCP, publicly released the full Shai-Hulud/Mini Shai-Hulud source code on GitHub under an MIT license. That changes how this story should be read. The four waves that followed share the same toolkit and techniques, but shared tooling doesn't mean a shared operator anymore. Once the framework is public, any technically capable actor can run a campaign that looks identical to TeamPCP's own. What follows should be read as overlapping tactics, techniques, and procedures (TTPs), not as confirmed attribution to a single group.

Affected packages (npm and Go)

PackageCompromised version
hexo-deployer-wrangler1.0.4
hexo-shoka-swiper0.1.10
leo-auth4.0.6
leo-aws2.0.4
leo-cache1.0.2
leo-cdk-lib0.0.2
leo-cli3.0.3
leo-config1.1.1
leo-connector-elasticsearch2.0.6
leo-connector-mongo3.0.8
leo-connector-mysql3.0.3
leo-connector-oracle2.0.1
leo-connector-redshift3.0.6
leo-cron2.0.2
leo-logger1.0.8
leo-sdk6.0.19
leo-streams2.0.1
prism-silq1.0.1
rstreams-metrics2.0.2
rstreams-shard-util1.0.1
serverless-convention2.0.4
serverless-leo3.0.14
solo-nav1.0.1
github.com/verana-labs/verana-blockchain (Go)v0.10.1-dev.20

Socket is continuing the investigation; new findings are being added to their Miasma tracking page.

If your dependency tree includes any of these at the listed versions (or earlier compromised waves from the same campaign in early June), treat the environment as potentially compromised and follow the immediate remediation steps below.

Timeline

June 24, 2026
   ↓
LeoPlatform maintainer account compromised
   ↓
23 malicious package versions published (seconds apart)
   ↓
binding.gyp payload executes on install
   ↓
Developer + CI/CD credentials stolen
   ↓
GitHub Actions workflow ("Run Copilot") deployed
   ↓
Stolen secrets exfiltrated to public GitHub repos
   ↓
Go module (Verana Blockchain) compromised via a related technique
   ↓
Researchers at Socket, Wiz, Sonatype, and others identify and disclose the campaign

This is wave four of the same campaign family in under a month: it first surfaced against Red Hat's npm namespace on June 1, then a 57-package wave on June 3, then this LeoPlatform/Go wave on June 24–25. As noted above, TeamPCP open-sourced the underlying worm framework on May 12, so "same campaign family" reflects shared TTPs across these waves rather than confirmed proof of a single operator behind all four.

What actually happened

  • A maintainer of the LeoPlatform npm ecosystem had their account credentials or npm token stolen, most likely by an infostealer.
  • The attacker used that stolen access to publish trojanized versions of packages the maintainer already legitimately owned. Nothing needed to be "hacked" in the traditional sense, because the attacker already had a trusted publishing identity.
  • Anyone who installed one of those versions ran a hidden payload that stole their own credentials.
  • Some of those stolen credentials belonged to GitHub Actions environments, which let the attacker plant a malicious workflow and steal CI/CD secrets directly out of the runner.
  • Separately, a Go module was compromised using a related but distinct technique, showing the campaign isn't tied to any one language or package manager.
  • The whole thing was discovered because multiple independent security research teams were watching for exactly this pattern from earlier waves of the same campaign.

Technical breakdown

Stage 1: Compromised maintainer

The npm account czirker, associated with LeoPlatform, was compromised, most likely through leaked credentials or a token lifted by an infostealer already running on the maintainer's machine.

Stage 2: Malicious packages published

Using the stolen token, the attacker published 23 malicious package versions across the LeoPlatform and RStreams ecosystems in a six-second window. That speed points to a scripted, automated publishing tool, not a person manually uploading files.

The most concrete detail in the whole campaign sits inside this stage: the attacker didn't just publish malicious versions, they also repointed leo-sdk's latest dist-tag to the poisoned 6.0.19, while the clean 7.1.21 release remained available only under the less-discoverable awsv3 tag. In practice, that means any environment that ran npm install leo-sdk without an explicit semver pin was actively steered toward the worm, even though a clean release existed the entire time. The practical takeaway is blunt: pinning the version, not the tag, was the actual difference between a clean install and a compromise.

Stage 3: Install-time execution

This is the clever part. Most malicious npm packages get caught because scanners watch the preinstall and postinstall script fields in package.json. Miasma skips those entirely and instead adds a binding.gyp file, the configuration Node.js uses to compile native modules during install. Because binding.gyp is a completely normal part of building native add-ons, it runs code during npm install without ever touching the fields most tooling is watching.

This technique had two real mitigations, and it's worth being precise about both. The first already existed: npm install --ignore-scripts does suppress the implicit node-gyp rebuild hook that a binding.gyp file triggers, according to npm's own documentation. It's not a complete defense on its own, since a malicious package can still be fetched and unpacked, and a later rebuild without that flag can still trigger it, but it does block this specific execution path. The second mitigation arrived three weeks too late: npm v12, which shipped in July 2026, closes this vector by default, treating the implicit node-gyp rebuild triggered by binding.gyp exactly like any other install script, blocked unless the package is explicitly allow-listed. Miasma's "Phantom Gyp" technique was, in effect, a direct exploit of the exact gap npm v12 was built to close.

Stage 4: Secrets collection

Once running, the payload harvests a broad set of credentials, including:

  • npm and PyPI tokens
  • GitHub Personal Access Tokens and GitHub OIDC tokens
  • AWS, Azure, and GCP credentials
  • SSH keys
  • Configuration and credential paths for Claude Code, Cursor, and other AI coding assistants
  • OpenAI API keys
  • Slack and Twilio tokens, Docker auth files, Kubernetes configs, Vault data, and shell history

Stage 5: GitHub abuse

The malware drops a GitHub Actions workflow (seen under the name "Run Copilot") that pulls secrets directly out of CI runner memory during execution. Stolen data is encrypted and pushed to newly created public GitHub repositories acting as dead drops; at the time of writing, 550+ repositories carry the campaign's exfiltration marker. A related incident on the same day saw an attacker force-push a malicious commit to a widely used GitHub Action and redirect version tags to point at it, meaning any workflow that ran against those tags after that point executed the attacker's payload directly inside the GitHub Actions runner.

Stage 6: Propagation

Developer machine compromised
   ↓
Developer's GitHub credentials stolen
   ↓
CI/CD pipeline secrets harvested from the runner
   ↓
Stolen npm token used to publish more malicious packages
   ↓
More developers install the compromised packages
   ↓
(repeat)

Each successful compromise becomes the launchpad for the next one. That's what makes this a self-propagating campaign rather than a single incident.

Why this attack is different

Old supply-chain attacksMiasma
Single packageEntire ecosystem
Cryptocurrency theftBroad credential theft
One languageMultiple languages (npm, now Go)
One maintainerDeveloper infrastructure as a whole
Static malwareSelf-propagating, evolving between waves

Earlier npm supply chain incidents were largely smash-and-grab: compromise a package, steal wallet keys or session tokens, move on. Miasma behaves more like a worm with a business model. It treats every successful infection as fuel for the next wave, and it's demonstrably still evolving three-plus weeks and four separate waves in.

Why GitHub Actions became a target

GitHub Actions sits at a uniquely valuable point in the software supply chain: it holds OIDC tokens used for trusted, credential-less publishing to cloud providers and registries, it stores long-lived CI secrets, and it has automated, unattended execution by design. Compromising a CI runner doesn't just expose one developer's laptop, it exposes every deployment pipeline, registry publish, and cloud credential that workflow touches. That's a far bigger blast radius than any individual developer's local environment, which is exactly why the campaign built dedicated tooling to specifically target it.

Why expanding into Go matters

Most coverage of this campaign focused on npm, since that's where the majority of the packages live. But the Go module compromise is arguably the most important detail in the entire story, and it's the one most write-ups skip past.

The Go sample doesn't use binding.gyp. It doesn't rely on Go's module resolution or build mechanisms at all. Instead, a developer who simply clones the repository or opens it in a trusted IDE or AI coding assistant can trigger the payload through project configuration alone.

That tells you something important: the attackers aren't fundamentally interested in JavaScript, or npm, or any specific package manager's mechanics. They're interested in developers, wherever developers place trust in automated tooling, whether that's an install script, a build step, or an IDE opening a folder. Language doesn't matter. Developer credentials do.

The bigger lesson

Old thinking:

We scan dependencies
   ↓
We patch known CVEs
   ↓
We're safe

Modern reality:

A developer is compromised
   ↓
CI/CD is compromised
   ↓
A legitimate package is published with malicious code
   ↓
Every downstream consumer installs it
   ↓
No CVE is ever required at any step

Dependency scanning answers the question "does this code have a known flaw." Miasma exposes a completely different question that most security programs aren't set up to answer at all: "do I trust the humans and automated systems that are allowed to publish code into my environment, right now, today."

Detection opportunities

Rather than "how to stop it," it's more useful to ask where a defender realistically had a chance to notice something was wrong. This is where most coverage of Miasma stops short, so it's worth walking through in more depth.

Publish velocity. 23 package versions published in a six-second window is not something a human does. Any organization running its own npm registry mirror, or watching public registry events for packages it depends on, could flag publish bursts like this automatically. A single version bump is normal. Two dozen in seconds is a signal on its own, independent of what the package actually contains.

Dist-tag changes without a version bump users asked for. The leo-sdk hijack worked specifically because latest moved to a lower, older-looking version number (6.0.19) while a newer clean release (7.1.21) sat under a different tag. Any tooling that diffs a package's dist-tags over time, not just its version list, would have caught latest pointing backward in the version sequence, which is almost never legitimate behavior.

Build artifacts that don't match source. The compromised leo-sdk package ballooned from a 10 KB index.js in the clean version to a 5 MB index.js in the poisoned one, with a binding.gyp file that doesn't exist anywhere in the project's actual GitHub repository. Comparing what's published to the registry against what's committed to source control, package by package, would have surfaced this immediately. Most organizations never run this comparison at all.

Unexpected native builds on pure-JavaScript packages. leo-sdk is a JavaScript SDK with no legitimate need for native compilation. A binding.gyp appearing on a package that has never shipped one before, or that has no plausible reason to need one, is a strong anomaly signal on its own, well before anyone inspects what the file actually does.

New workflow files appearing without a corresponding pull request. A workflow file named "Run Copilot" showing up in .github/workflows/ with no linked PR, no review, and no author anyone recognizes is exactly the kind of change branch protection and required reviews exist to catch, if they're actually enforced on workflow files specifically.

Outbound traffic to freshly created GitHub repositories. Because exfiltration relied on newly created public repos acting as dead drops, CI runners or developer machines making outbound API calls to create new repositories, or pushing to repositories that didn't exist an hour earlier, is a detectable and fairly unusual behavior pattern.

OIDC and token usage outside its normal pattern. A GitHub OIDC token or npm publish token being used from an IP range, workflow, or time window it's never been used from before is a classic identity-anomaly signal, and one that doesn't require knowing anything about this specific campaign to catch.

None of these require a CVE database or prior knowledge of Miasma specifically. They require actually watching how trust, publishing behavior, and identity move through your environment over time, not just what's listed in a manifest file at a single point in time.

What organizations should do

Immediate

  • Rotate credentials for any environment that installed an affected package, but only after removing the malicious version and confirming persistence is gone, not before.
  • Update or remove affected package versions across lockfiles, caches, containers, and CI runners, not just the top-level manifest.
  • Revoke and reissue any GitHub tokens, OIDC configurations, or npm tokens that may have been exposed.
  • Pin GitHub Actions by commit hash instead of mutable version tags.

Medium-term

  • Introduce package allowlists for what's permitted in CI/CD and production builds.
  • Require package provenance and signing where available.
  • Maintain a real Software Bill of Materials (SBOM), not a point-in-time snapshot.
  • Add a cooldown window before newly published package versions are trusted in a build.

Long-term

  • Continuous validation of what's actually running in your environment, not periodic audits.
  • Repository and CI/CD monitoring for unexpected workflow changes or publishing behavior.
  • Ongoing attack path analysis: not just "is this package vulnerable" but "what could someone reach if this package were malicious."
  • Developer identity monitoring, since in this campaign the identity was the actual attack surface.

Why this matters to Penetrify

Miasma wasn't an npm problem. It was an identity problem. A CI/CD problem. A trust problem. An attack-path problem.

The attackers didn't discover a vulnerability. They discovered a path from a single developer's workstation to thousands of downstream systems, and that path existed entirely inside relationships that most security programs never actually monitor: which humans can publish code, which tokens can trigger a deployment, which workflows run unattended with real access to production.

npm v12 closes the specific execution vector Miasma relied on. It does nothing about the identity problem underneath it. As OX Security's Moshe Siman Tov Bustan put it in coverage of the npm v12 rollout: once an attacker controls a legitimate maintainer's credentials, blocking install scripts doesn't help, because the malicious code ships as a trusted, signed release.

That's the whole argument this article has been making, stated more simply than we could. Modern software supply chain security has to move beyond dependency scanning and toward continuously understanding how trust relationships evolve across repositories, build pipelines, identities, and production environments, not just what CVEs exist in a manifest file.

Sources

Frequently Asked Questions

What types of vulnerabilities does Penetrify detect?

Penetrify detects all OWASP Top 10 vulnerability categories including SQL injection, XSS, CSRF, IDOR, broken authentication, security misconfigurations, and sensitive data exposure. It also tests API security, session management, and common misconfigurations in Supabase, Firebase, and Bubble.

How long does an AI penetration test take?

A quick scan completes in 15–30 minutes. A standard scan runs 1–2 hours with broader coverage. A deep scan can run several hours for complex applications.

What does a Penetrify report include?

Every report includes an executive summary, overall security score, severity-classified findings (Critical, High, Medium, Low), step-by-step reproduction steps, and concrete remediation guidance written for developers — not compliance officers.

Related articles

Multi-Framework Compliance Testing: One Engagement, Multiple Auditors
Subject to SOC 2, PCI DSS, and HIPAA simultaneously? Here's how to avoid redundant testing and satisfy all auditors from a single programme.
What Is a Pen Test? A Step-by-Step Guide to How It Works
Is your web application truly secure? The thought of a single, hidden vulnerability leading to a catastrophic data breach is enough to keep any founder up at night. You know you need to take action, but the world of cybersecurity can feel like an intimidating maze of confusing jargon and high-priced…
PCI DSS Compliance Scanning: The 2026 Guide to Automated Security
On March 14, 2025, a Tier 1 retailer discovered that a single misconfigured firewall rule during a Friday afternoon push invalidated three months of compliance prep in under six minutes. You likely already know that traditional quarterly pci dss compliance scanning feels like checking your speedomet…

Explore more