Miasma — Red Hat npm Supply-Chain Credential Worm
Executive Summary
Miasma ("The Spreading Blight") is a self-propagating npm supply-chain worm in the Shai-Hulud lineage. On 1 June 2026 it backdoored 32 packages (~96 versions) in the @redhat-cloud-services npm scope — roughly 116,991 weekly downloads — after an attacker hijacked a Red Hat maintainer's GitHub account and pushed orphan commits carrying valid OIDC/SLSA provenance. By 5 June, 57+ packages and 300+ malicious versions had been identified across the wider npm ecosystem.
The worm runs at install time, before any application code, through two mechanisms: a classic preinstall hook ("preinstall": "node index.js") and — in the second wave — "Phantom Gyp": a weaponized binding.gyp whose sources array embeds a node-gyp command-substitution that silently runs index.js during native-module compilation, bypassing detections that only watch npm lifecycle hooks. The dropper (a ~4 MB Caesar-cipher/AES-128-GCM/obfuscator.io payload) downloads a legitimate Bun v1.3.13 binary to /tmp/b-<random>/bun and harvests credentials for GitHub, npm, AWS, Azure, GCP, HashiCorp Vault and Kubernetes from environment variables, cloud metadata services (IMDSv2 169.254.169.254, ECS 169.254.170.2, Vault 127.0.0.1:8200) and on-disk secret files. When it finds publish-capable npm tokens it re-publishes infected packages to propagate. It also plants AI-coding-agent persistence (~/.claude/settings.json SessionStart hooks, .vscode/tasks.json folderOpen tasks) that fire a harvester when a repo is opened in Claude Code / Cursor / VS Code / Gemini CLI, and crossed into PyPI (37 malicious wheels / 19 packages using .pth startup hooks).
Defender priority: This worm executes during dependency install in CI/CD and dev workstations — not at app runtime. The single highest-value detection is node / npm / node-gyp spawning a shell, bun, or a child that touches cloud-metadata IPs or ~/.npmrc/~/.aws/kube/Vault paths during an install. Treat any host that installed an affected @redhat-cloud-services version since 1 June 2026 as credential-compromised: rotate all npm, GitHub, and cloud tokens reachable from that host/runner.
Source Review & Web Hunter Notes
| Tier | Source | Key Finding | Carry |
|---|---|---|---|
| 1 | Microsoft Security Blog (02 Jun 2026) | Per-package SHA256s; preinstall chain node→shell→bun→payload; IMDS/ECS/Vault/K8s harvest; GitHub dead-drop repos described "Miasma: The Spreading Blight"; .github/setup.js injection; runner-memory OIDC token scrape; sudoers write at /mnt/runner. | yes |
| 1 | Mend.io (MSC-2026-6085) | "Phantom Gyp" binding.gyp signature; loader + binding.gyp SHA256; bun-v1.3.13 download URL; AES IVs; full metadata-endpoint + env-var literal list; manifest-mismatch detection signal. | yes |
| 1 | Harness.io | 32 packages / 96 versions; orphan-commit + OIDC/SLSA abuse; "every infection gets its own encryption" (hash evasion); gh auth token harvest. | yes |
| 1 | Phoenix.security | Decrypted helper/main payload SHA256; AI-agent hook paths (Claude/VS Code/Cursor/Gemini); 73 Microsoft repos auto-disabled in 105s; PyPI Hades wave (37 wheels / 19 pkgs / .pth). | yes |
| 1 | Upwind.io | Concrete affected versions (chrome/frontend-components/types/rule-components/rbac-client); .github/setup.js; spoofed User-Agents. | yes |
| 2 | The Hacker News | "Miasma: The Spreading Blight" first seen 29 May 2026; api.anthropic.com spoof channel; VS Code runOn: folderOpen; TeamPCP open-sourced Shai-Hulud tooling (attribution caveat). | partial |
| 2 | Unit 42 (npm supply-chain monitoring) | Ecosystem context: install-script abuse trend, second-wave package count (57+ / 300+ versions). | partial |
Provenance note: api.anthropic.com/v1/api appears as a spoofed / dormant exfil channel (Microsoft: noop:true, disabled). It is a legitimate Anthropic host — do not blocklist it. It is carried only as an enrich/context indicator. GitHub, npm registry, Sigstore, and cloud-metadata endpoints are all legitimate services abused as living-off-the-land exfil; detection is behavioral, not domain-blocking.
Hunt Brief & Attack Chain
Hunt hypotheses (fidelity-ordered)
- H1 (high): A
node/npm/node-gypprocess spawnsbun(or a binary under/tmp/b-*,/var/folders/*/b-*) during dependency install — the Miasma runtime drop. T1059 / T1195.002 - H2 (high): A package-manager install tree makes outbound connections to cloud metadata IPs (
169.254.169.254,169.254.170.2) or to local Vault (127.0.0.1:8200) — credential harvest during install. T1552.005 / T1552.001 - H3 (high):
binding.gypwritten/seen on disk whose contents embed a node-gyp command-substitution (<!(node …)) — the Phantom Gyp signature. T1195.002 - H4 (med): An install-time child process reads
~/.npmrc,~/.aws/credentials,kubeconfig,~/.config/gcloud, SSH keys, or~/.claude.json. T1552.001 - H5 (med): Creation/modification of AI-agent persistence:
~/.claude/settings.json(SessionStart hook) or.vscode/tasks.json(folderOpen) shortly after a clone/install. T1546 - H6 (med): An install-time process runs
gh auth token,npm whoami/npm token, or queriesregistry.npmjs.org/-/npm/v1/tokens— token theft / republish staging. T1552.004 / T1195.002 - H7 (med): Sudoers tamper on a CI runner: write to
/mnt/runneror a sudoers file containingNOPASSWD:ALLforrunner. T1548.003 - H8 (low): EDR-fingerprinting file reads (
/opt/CrowdStrike,/Library/CS/falcon,/opt/sentinelone) by an install-time child. T1518.001
Attack chain
| # | Step | Telemetry | Hunt Angle |
|---|---|---|---|
| 1 | Maintainer GitHub account hijack; orphan commit republished with OIDC/SLSA provenance | (off-endpoint) npm/GitHub audit | npm dist-tag/publish events; new versions of pinned deps |
| 2 | npm install resolves an infected version; preinstall or binding.gyp fires node index.js | ProcessRollup2 | node-gyp / npm parent → node/sh child at install (Q1, Q3) |
| 3 | Dropper de-obfuscates, downloads Bun v1.3.13 → /tmp/b-<rand>/bun | ProcessRollup2 / NetworkConnectIP4 | bun spawned from node; download from oven-sh/bun release (Q1, Q2) |
| 4 | Harvest env vars + IMDS/ECS/Vault/K8s + on-disk secrets | NetworkConnectIP4 / FileOpen | install tree → 169.254.169.254 / 127.0.0.1:8200; secret-file reads (Q4, Q5) |
| 5 | Token theft: gh auth token, npm token endpoints | ProcessRollup2 | gh/npm token commands under install (Q6) |
| 6 | Exfil via GitHub dead-drop repo "Miasma: The Spreading Blight" / .github/setup.js | NetworkConnectIP4 (api.github.com) | anomalous repo creation; correlate with cred-access hosts |
| 7 | Propagate: republish infected packages using stolen publish tokens | (off-endpoint) npm audit | unexpected publish from CI identity |
| 8 | Persistence: AI-agent hooks; sudoers write on runner | FileWritten / ProcessRollup2 | ~/.claude/settings.json, tasks.json, /mnt/runner (Q5, Q7) |
Consolidated IOC Table
Every atomic IOC below traces to a cited Tier-1 source (§18). Per-infection re-encryption means file hashes are low-durability — favor the behavioral indicators (Phantom Gyp signature, bun-from-node, metadata-IP touches). Package versions and the binding.gyp/loader hashes are the most reliable atomic anchors.
| Type | Value | Conf | Action | Context / Source |
|---|---|---|---|---|
| npm pkg/ver | @redhat-cloud-services/chrome 2.3.1 / 2.3.2 / 2.3.4 | high | hunt | Infected v1 wave (Upwind, MS) |
| npm pkg/ver | @redhat-cloud-services/frontend-components 7.7.2 / 7.7.3 / 7.7.5 | high | hunt | Infected (Upwind, MS) |
| npm pkg/ver | @redhat-cloud-services/types 3.6.1 / 3.6.2 / 3.6.4 | high | hunt | Infected (Upwind, MS) |
| npm pkg/ver | @redhat-cloud-services/rbac-client 9.0.3 / 9.0.4 / 9.0.6 | high | hunt | Infected (Upwind, MS) |
| npm pkg/ver | @redhat-cloud-services/rule-components 4.7.2 / 4.7.3 | high | hunt | Infected (Upwind) |
| npm pkg/ver | @redhat-cloud-services/remediations-client 4.0.4 / 4.0.5 / 4.0.7 | high | hunt | Infected (MS) |
| npm pkg/ver | @redhat-cloud-services/insights-client 4.0.4 / 4.0.5 / 4.0.7 | high | hunt | Infected (MS) |
| npm pkg/ver | @redhat-cloud-services/host-inventory-client 5.0.3 / 5.0.4 / 5.0.6 | high | hunt | Infected (MS) |
| npm pkg/ver | @redhat-cloud-services/javascript-clients-shared 2.0.8 / 2.0.9 / 2.0.11 | high | hunt | Infected (MS) |
| npm pkg/ver | @redhat-cloud-services/notifications 6.9.3 | high | hunt | Infected (MS hash list) |
| npm pkg/ver | @redhat-cloud-services/frontend-components-config-utilities 4.11.3 | high | hunt | Infected (Phoenix) |
| npm pkg/ver | @redhat-cloud-services/tsc-transform-imports 1.2.2 / 1.2.4 | high | hunt | Infected (Phoenix, Harness) |
| npm pkg/ver | @vapi-ai/server-sdk, ai-sdk-ollama | high | hunt | Second wave, 3-4 Jun (Mend) |
| SHA256 (binding.gyp) | ef641e956f91d501b748085996303c96a64d67f63bfeef0dda175e5aa19cca90 | high | detect | Phantom Gyp file, 157 B (Mend) |
| SHA256 (loader) | c2a60face766f69f82c972375f35f8ebaa45d6c464176974e631d9a78d6bea0a | high | detect | Stage-1 loader, 1 Jun wave (Mend); MD5 8cd0b0fbd4232face584c66d9754bf1e |
| SHA256 (payload) | 396cac9e457ec54ff6d3f6311cb5cc1da8054d019ce3ffa1de5741506c7a4ea4 | high | detect | remediations-client (MS) |
| SHA256 (payload) | d8d170af3de17bb9b217c52aaaffdf9395f35ef015a57ef676e406c121e5e223 | high | detect | advisor-components 3.8.2 (MS) |
| SHA256 (payload) | f0641e053e81f0d01fa46db35a83e0a34494886503086866d956d14e81fd3e1c | high | detect | hcc-kessel-mcp 0.3.4 (MS) |
| SHA256 (payload) | d5a97614d5319ce9c8e01fa0b4eb06fb5b9e54fa13b23d718174a1546444123b | high | detect | frontend-components-testing 1.2.4 (MS) |
| SHA256 (payload) | f88258e21592084a2f93a572ade8f9b91c0cd0e242f5cf6121ed7bad0f7bdd1f | high | detect | notifications 6.9.3 (MS) |
| SHA256 (payload) | 25e121e3b7d300c0d0075b33e5eca39a3e6a659fb9cfee52b70ef71686628f1b | high | detect | chrome 2.3.4 (MS) |
| SHA256 (decrypted) | ac2a2208e1726e008be6c73dc0872d9bba163319259dff1b62055ac933ca46b6 | med | hunt | Decrypted Bun helper (Phoenix) |
| SHA256 (decrypted) | 0dc06ecdaa63fe24859cfd955053c23245c536e4733480239d14bebf12688e35 | med | hunt | Decrypted main payload (Phoenix) |
| Behavioral str | "sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"] | high | detect | Phantom Gyp signature in binding.gyp (Mend) |
| Behavioral str | "preinstall": "node index.js" | high | hunt | v1-wave preinstall hook (multiple) |
| String marker | Miasma: The Spreading Blight | high | hunt | Dead-drop GitHub repo description (MS, Mend, Harness) |
| String marker | IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner | high | hunt | Honeytoken/tripwire literal (MS, Mend, Upwind) |
| File path | /tmp/b-<rand>/bun · /var/folders/<…>/b-<rand>/bun · bun.exe | high | hunt | Dropped Bun runtime (Mend) |
| File path | /tmp/p<base36>.js | med | hunt | Temp payload file (Mend) |
| URL | github.com/oven-sh/bun/releases/download/bun-v1.3.13/bun-<os>-<arch>.zip | high | enrich | Legit Bun release abused as delivery (Mend) |
| IP | 169.254.169.254 · 169.254.170.2 | med | hunt | AWS IMDSv2 / ECS metadata harvest (Mend) |
| IP:port | 127.0.0.1:8200 | med | hunt | HashiCorp Vault probe (MS, Mend) |
| User-Agent | python-requests/2.31.0 · google-api-nodejs-client/7.0.0 | med | enrich | Spoofed UA on exfil (MS, Upwind) |
| Domain (spoof) | api.anthropic.com/v1/api | low | enrich | Dormant noop:true channel — legit host, do NOT block (MS) |
| Advisory | MSC-2026-6085 | high | enrich | Mend.io tracking ID |
Affected Surface & Telemetry Matrix
| Surface | Required Telemetry | Priority | Gap Risk |
|---|---|---|---|
CI/CD build runners (GitHub Actions / GitLab / Jenkins) running npm install | ProcessRollup2, NetworkConnectIP4, FileOpen on Linux runner sensors | Critical | High — ephemeral runners often lack a sensor |
| Developer workstations (macOS/Linux/Windows) installing Node deps | ProcessRollup2, NetworkConnectIP4, FileWritten | High | Med — covered if Falcon deployed to dev fleet |
| npm registry identity / publish tokens | npm audit logs (off-endpoint) | High | High — not Falcon telemetry; needs npm-side audit |
| Cloud control planes (AWS/Azure/GCP) reachable from build creds | CloudTrail / Azure AD / GCP audit (off-endpoint) | High | High — correlate post-harvest cloud API use |
| AI coding agents (Claude Code / Cursor / VS Code / Gemini) | FileWritten on settings.json / tasks.json, ProcessRollup2 | Medium | Med — config-write detection is endpoint-visible |
| Kubernetes service-account tokens / Vault | NetworkConnectIP4 to 127.0.0.1:8200, FileOpen on SA token paths | Medium | Med |
| PyPI / Python dev hosts (Hades wave) | FileWritten on *.pth in site-packages, ProcessRollup2 | Medium | Med |
ATT&CK Mapping
| Tactic | Technique | Observed Behavior | Query / Control |
|---|---|---|---|
| Initial Access | T1195.002 — Compromise Software Supply Chain | Infected @redhat-cloud-services versions republished to npm | Q1, Q3 / H-pin deps |
| Execution | T1059.007 / T1059.004 — JS / Unix Shell | preinstall + binding.gyp run node index.js → shell → bun | Q1, Q3 |
| Execution | T1059 — Command & Scripting (node-gyp substitution) | Phantom Gyp <!(node …) in sources array | Q3 |
| Credential Access | T1552.005 — Cloud Instance Metadata API | Install tree hits IMDSv2 / ECS metadata | Q2, Q4 |
| Credential Access | T1552.001 — Credentials in Files | Reads ~/.npmrc, ~/.aws, kubeconfig, SSH, GCP creds | Q4 |
| Credential Access | T1552.004 — Private Keys / T1528 — Steal App Token | gh auth token, npm token endpoints, OIDC scrape | Q6 |
| Persistence | T1546 — Event-Triggered Execution | AI-agent SessionStart / folderOpen hooks; PyPI .pth | Q5 |
| Privilege Escalation | T1548.003 — Sudo & Sudo Caching | Sudoers write runner ALL=(ALL) NOPASSWD:ALL at /mnt/runner | Q7 |
| Defense Evasion | T1518.001 — Security Software Discovery | Reads /opt/CrowdStrike, /Library/CS/falcon, etc. | Q8 |
| Exfiltration | T1567 — Exfil Over Web Service | GitHub dead-drop repos / GraphQL commit; spoofed UA | Native §7 |
| Impact / Propagation | T1080 — Taint Shared Content | Republish infected packages via stolen tokens | npm audit §7 |
Native Audit-Log Hunts (non-CQL)
npm registry / package side
- Audit your lockfiles (
package-lock.json,yarn.lock,pnpm-lock.yaml) for any resolved@redhat-cloud-services/*version published on/after 1 Jun 2026; cross-check against §4. - npm org: review publish events and token creation for the past 30 days; revoke all granular + legacy automation tokens and require 2FA/trusted-publishing.
- Grep extracted node_modules for the Phantom Gyp signature and markers:
<!(node index.js,Miasma: The Spreading Blight,IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner. - Flag any
binding.gypwhosesourcesarray contains a<!(...)command-substitution invokingnode/sh— legitimate gyp sources are file names, not commands.
GitHub / SCM side
- Search org for newly created repos with description
Miasma: The Spreading Blightor files atresults/<timestamp>-<counter>.json/.github/setup.js/ injectedcodeql.ymlon branches prefixedoidc-orchore/add-codeql-static-analysis. - Review Actions runs that used
id-token: writein the window; rotate any OIDC-derived cloud roles.
Cloud control plane
- AWS CloudTrail:
GetCallerIdentity/ Secrets Manager / SSM calls from a build identity immediately after an npm install, especially from new IPs/UApython-requests/2.31.0. - Azure sign-in logs / GCP audit: token use originating from CI egress IPs shortly after package install.
These are off-endpoint checks the EDR cannot see. Run them in parallel with the CQL hunts (§8) and treat any correlated hit as confirmed compromise.
CrowdStrike LogScale CQL Hunt Queries
Looks for: a bun binary (or a binary under a /tmp/b-* drop path) whose parent is a Node/npm/node-gyp process — the core Miasma runtime drop. Highest-fidelity signal in the pack.
// HUNT: Miasma runtime drop — bun spawned by node/npm/node-gyp // MITRE: T1195.002, T1059.007 | CONF: high FP: low COST: low // REQUIRES: ProcessRollup2 (Linux/macOS/Windows) // FALSE POSITIVES: legitimate Bun-based projects that run bun from node tooling #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ImageFileName=/(\/|\\)(bun|bun\.exe)$/i | ParentBaseFileName=/^(node|npm|npx|node-gyp|sh|bash|zsh)$/i | table([ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine, SHA256HashData, aid], limit=200)
Looks for: a Node/bun/package-manager process connecting to IMDS (169.254.169.254), ECS metadata (169.254.170.2), or local Vault (127.0.0.1:8200) — credential harvest during install. FP: SDKs and IaC tooling legitimately read IMDS on cloud hosts; scope to install context and exclude known IaC service accounts.
// HUNT: Miasma credential harvest — metadata/Vault from install runtime // MITRE: T1552.005, T1552.001 | CONF: high FP: medium COST: low // REQUIRES: NetworkConnectIP4 // FALSE POSITIVES: cloud SDKs / IaC agents legitimately querying IMDS // TUNING: exclude approved IaC hosts (terraform/ansible runners) by aid or ComputerName #event_simpleName=NetworkConnectIP4 | ContextBaseFileName=/^(node|bun|npm|node-gyp|python|python3)$/i | in(RemoteAddressIP4, values=["169.254.169.254","169.254.170.2","127.0.0.1"]) | (RemoteAddressIP4!="127.0.0.1" OR RemotePort=8200) | table([ComputerName, UserName, ContextBaseFileName, RemoteAddressIP4, RemotePort, aid], limit=200)
Looks for: node-gyp (or a gyp build) spawning a node index.js / shell child — the install-time effect of a weaponized binding.gyp command-substitution. FP: native modules with legitimate gyp build steps; pivot on the child reading secrets (Q4) or spawning bun (Q1) to confirm.
// HUNT: Phantom Gyp — node-gyp build executes node/shell child (binding.gyp abuse) // MITRE: T1195.002, T1059 | CONF: high FP: medium COST: low // REQUIRES: ProcessRollup2 // FALSE POSITIVES: legit native-module compiles that invoke node during gyp // TUNING: combine with Q1/Q2 hit on same aid; exclude known native-build images #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ParentBaseFileName=/^(node-gyp|gyp|node)$/i | CommandLine=/(index\.js|\bsh\b|\bbash\b|-e\s+|child_process)/i | ImageFileName=/(\/|\\)(node|sh|bash|zsh|bun)$/i | table([ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine, aid], limit=200)
Looks for: a Node/bun/python child touching ~/.npmrc, ~/.aws/credentials, kubeconfig, ~/.config/gcloud, SSH keys, or ~/.claude.json. FP: CLIs legitimately read their own config; scope to install-tree parents and high-value secret paths.
// HUNT: Miasma secret-file access by install-time runtime // MITRE: T1552.001 | CONF: medium FP: medium COST: medium // REQUIRES: FileOpenInfo / FileOpen (file-access telemetry enabled) // FALSE POSITIVES: legit CLI config reads (aws/gcloud/kubectl) // TUNING: require ContextBaseFileName in node/bun set AND a high-value path #event_simpleName=/FileOpen|FileOpenInfo/ | ContextBaseFileName=/^(node|bun|npm|python|python3)$/i | TargetFileName=/(\.npmrc|\.aws(\/|\\)credentials|kubeconfig|gcloud(\/|\\)|\.ssh(\/|\\)id_|\.claude\.json|VAULT_TOKEN)/i | table([ComputerName, UserName, ContextBaseFileName, TargetFileName, aid], limit=200)
Looks for: a Node/bun process writing ~/.claude/settings.json, .vscode/tasks.json, or a PyPI .pth in site-packages — the AI-agent / Python startup persistence. FP: legitimate editor/agent config saves; scope writer to the install-runtime set and alert on first-write by a node/bun child.
// HUNT: Miasma persistence — AI-agent hook / .pth written by install runtime // MITRE: T1546 | CONF: high FP: medium COST: low // REQUIRES: FileWritten / NewExecutableWritten // FALSE POSITIVES: user-driven editor/agent config saves // TUNING: require writer ContextBaseFileName in node/bun/npm; exclude IDE binaries #event_simpleName=/FileWritten|NewScriptWritten/ | ContextBaseFileName=/^(node|bun|npm|python|python3)$/i | TargetFileName=/(\.claude(\/|\\)settings\.json|\.vscode(\/|\\)tasks\.json|site-packages(\/|\\).*\.pth)$/i | table([ComputerName, UserName, ContextBaseFileName, TargetFileName, aid], limit=200)
Looks for: gh auth token, npm token, or npm whoami executed under a package-install parent — publish-token staging for propagation. FP: CI pipelines legitimately call gh auth/npm whoami; key on these running as a child of an install rather than top-level pipeline steps.
// HUNT: Miasma token theft / republish staging // MITRE: T1552.004, T1528, T1195.002 | CONF: medium FP: medium COST: low // REQUIRES: ProcessRollup2 (command line visibility) // FALSE POSITIVES: CI steps that legitimately run gh/npm auth // TUNING: require ParentBaseFileName in node/npm/node-gyp/sh install context #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | ParentBaseFileName=/^(node|npm|npx|node-gyp|bun|sh|bash)$/i | CommandLine=/(gh\s+auth\s+token|npm\s+token|npm\s+whoami|registry\.npmjs\.org\/-\/npm\/v1\/tokens)/i | table([ComputerName, UserName, ParentBaseFileName, ImageFileName, CommandLine, aid], limit=200)
Looks for: a process writing a sudoers entry granting runner NOPASSWD:ALL, or any write to /mnt/runner — Miasma's runner privilege escalation.
// HUNT: Miasma privilege escalation — sudoers NOPASSWD write on runner // MITRE: T1548.003 | CONF: high FP: low COST: low // REQUIRES: ProcessRollup2 OR FileWritten // FALSE POSITIVES: rare; legit sudoers automation should be allowlisted #event_simpleName=/ProcessRollup2|SyntheticProcessRollup2/ | CommandLine=/(NOPASSWD:\s*ALL|(\/|\\)mnt(\/|\\)runner|(\/|\\)etc(\/|\\)sudoers)/i | CommandLine=/(echo|tee|sed|cat|printf|mount)/i | table([ComputerName, UserName, ImageFileName, CommandLine, aid], limit=200)
Looks for: a Node/bun child enumerating EDR install paths (/opt/CrowdStrike, /Library/CS/falcon, /opt/sentinelone, /opt/carbonblack) — Miasma's environment fingerprinting. FP: inventory/asset agents read these paths; scope to node/bun parentage during install.
// HUNT: Miasma security-software discovery // MITRE: T1518.001 | CONF: medium FP: medium COST: medium // REQUIRES: FileOpen/FileOpenInfo OR ProcessRollup2 // FALSE POSITIVES: inventory/asset agents enumerating security tooling // TUNING: require ContextBaseFileName node/bun and exclude asset-mgmt agents #event_simpleName=/FileOpen|FileOpenInfo/ | ContextBaseFileName=/^(node|bun|python|python3)$/i | TargetFileName=/(\/opt\/CrowdStrike|\/Library\/CS\/falcon|\/opt\/sentinelone|\/opt\/carbonblack)/i | table([ComputerName, UserName, ContextBaseFileName, TargetFileName, aid], limit=200)
Tuning order: Q1 and Q7 are near-zero-FP — promote first. Q2/Q3 fire on credential harvest and Phantom Gyp; pair them on the same aid for a high-confidence install-time compromise. Q4/Q5/Q6/Q8 are hunt-grade pivots — investigate hits, don't alert blindly.
CrowdStrike Custom IOA Recommendations
| IOA Name | Pattern | Exclusions | Action |
|---|---|---|---|
| Miasma — Bun dropped by Node tooling | Process create: image bun/bun.exe with parent node|npm|node-gyp|npx (from Q1) | Allowlist repos that legitimately ship Bun in their build | Block / Detect-Critical |
| Miasma — Sudoers NOPASSWD on runner | Command line matches NOPASSWD:\s*ALL targeting runner or write to /mnt/runner (Q7) | Allowlist sanctioned sudoers automation | Block |
| Miasma — Install runtime hits IMDS/Vault | Network connect from node|bun to 169.254.169.254/169.254.170.2/127.0.0.1:8200 (Q2) | Exclude approved IaC runner aids | Detect-High |
| Miasma — AI-agent hook write by node/bun | File write to .claude/settings.json or .vscode/tasks.json by node|bun (Q5) | Exclude IDE/agent self-writes | Detect-High |
Promote Q1 and Q7 to blocking Custom IOAs on CI runner host groups first (lowest FP, highest impact). Run Q2/Q5 as Detect-only for 1–2 weeks to baseline before any block.
Machine-Readable IOC Appendix
One-click blocks for Falcon IOC Management and your detection backlog. Hashes are sourced (§4/§18) but low-durability (per-infection re-encryption) — prefer the behavioral signatures.
type,value,action,severity,expiration,description,tags sha256,ef641e956f91d501b748085996303c96a64d67f63bfeef0dda175e5aa19cca90,detect,critical,2026-12-21,Miasma Phantom Gyp binding.gyp,campaign:Miasma sha256,c2a60face766f69f82c972375f35f8ebaa45d6c464176974e631d9a78d6bea0a,detect,critical,2026-12-21,Miasma stage-1 loader,campaign:Miasma sha256,396cac9e457ec54ff6d3f6311cb5cc1da8054d019ce3ffa1de5741506c7a4ea4,detect,high,2026-12-21,Miasma payload remediations-client,campaign:Miasma sha256,d8d170af3de17bb9b217c52aaaffdf9395f35ef015a57ef676e406c121e5e223,detect,high,2026-12-21,Miasma payload advisor-components,campaign:Miasma sha256,f0641e053e81f0d01fa46db35a83e0a34494886503086866d956d14e81fd3e1c,detect,high,2026-12-21,Miasma payload hcc-kessel-mcp,campaign:Miasma sha256,d5a97614d5319ce9c8e01fa0b4eb06fb5b9e54fa13b23d718174a1546444123b,detect,high,2026-12-21,Miasma payload fe-components-testing,campaign:Miasma sha256,f88258e21592084a2f93a572ade8f9b91c0cd0e242f5cf6121ed7bad0f7bdd1f,detect,high,2026-12-21,Miasma payload notifications,campaign:Miasma sha256,25e121e3b7d300c0d0075b33e5eca39a3e6a659fb9cfee52b70ef71686628f1b,detect,high,2026-12-21,Miasma payload chrome,campaign:Miasma md5,8cd0b0fbd4232face584c66d9754bf1e,detect,high,2026-12-21,Miasma stage-1 loader (MD5),campaign:Miasma
# Phantom Gyp — weaponized binding.gyp sources array "sources": ["<!(node index.js > /dev/null 2>&1 && echo stub.c)"] # v1-wave preinstall hook "preinstall": "node index.js" # Dead-drop / tripwire string markers Miasma: The Spreading Blight IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner # Dropped Bun runtime paths /tmp/b-<rand>/bun /var/folders/<...>/b-<rand>/bun bun.exe /tmp/p<base36>.js # Behavioral chains node|npm|node-gyp -> bun (or /tmp/b-*/bun) node|bun -> 169.254.169.254 | 169.254.170.2 | 127.0.0.1:8200 node|bun writes ~/.claude/settings.json | .vscode/tasks.json | site-packages/*.pth
# @redhat-cloud-services scope — published on/after 2026-06-01 @redhat-cloud-services/chrome@2.3.1 @2.3.2 @2.3.4 @redhat-cloud-services/frontend-components@7.7.2 @7.7.3 @7.7.5 @redhat-cloud-services/types@3.6.1 @3.6.2 @3.6.4 @redhat-cloud-services/rbac-client@9.0.3 @9.0.4 @9.0.6 @redhat-cloud-services/rule-components@4.7.2 @4.7.3 @redhat-cloud-services/remediations-client@4.0.4 @4.0.5 @4.0.7 @redhat-cloud-services/insights-client@4.0.4 @4.0.5 @4.0.7 @redhat-cloud-services/host-inventory-client@5.0.3 @5.0.4 @5.0.6 @redhat-cloud-services/javascript-clients-shared@2.0.8 @2.0.9 @2.0.11 @redhat-cloud-services/notifications@6.9.3 @redhat-cloud-services/frontend-components-config-utilities@4.11.3 @redhat-cloud-services/tsc-transform-imports@1.2.2 @1.2.4 # Second wave (2026-06-03/04) @vapi-ai/server-sdk ai-sdk-ollama # Action: pin to last-known-good PRE-2026-06-01 version; rotate all reachable secrets
# Legitimate services abused as LOLBin exfil/delivery — DO NOT blocklist api.anthropic.com/v1/api # dormant noop:true spoof channel (legit Anthropic host) github.com/oven-sh/bun/releases/download/bun-v1.3.13/ # legit Bun release abused registry.npmjs.org/-/npm/v1/tokens # token-theft endpoint (legit registry) fulcio.sigstore.dev / rekor.sigstore.dev # provenance abuse (legit) # Spoofed User-Agents (enrich/correlate only) python-requests/2.31.0 google-api-nodejs-client/7.0.0 # Mend advisory: MSC-2026-6085
Detection Validation Gates
| Gate | Check | Pass criteria |
|---|---|---|
| Telemetry ready | ProcessRollup2 + NetworkConnectIP4 present on CI runner & dev host groups; FileOpen/FileWritten enabled for Q4/Q5/Q8 | > 95% of in-scope hosts reporting in last 24h |
| Benign baseline | Run Q1–Q3 over 7 days; catalogue legit Bun projects & IaC IMDS readers | Allowlist authored; residual hits investigable by hand |
| Positive test | In an isolated lab runner, create a benign binding.gyp with a <!(node …) source and a node→bun spawn | Q1 and Q3 both fire; correlate on same aid |
| Cred-access test | Lab: node process curls 169.254.169.254 + reads a dummy ~/.npmrc | Q2 and Q4 fire |
| Promotion | FP rate of Q1/Q7 over baseline window | < 1 FP / 1k installs → promote to blocking IOA |
Hardening — Tiered
- Disable install scripts in CI — set
npm ci --ignore-scripts/npm config set ignore-scripts truefor build pipelines. Kills both the preinstall hook and node-gyp execution for pure-JS deps. M1042 - Pin & freeze — pin every
@redhat-cloud-services/*dep to a known-good PRE-2026-06-01 version; commit lockfiles; enablenpm audit signatures/ provenance verification. M1051 - Rotate now — for any host/runner that installed an affected version since 1 Jun: revoke & reissue npm tokens, GitHub PATs/Actions OIDC roles, and all cloud creds reachable from that identity. M1027
- Block IMDS from build runtimes — enforce IMDSv2 hop-limit=1 and deny container/build access to
169.254.169.254where not required. M1037
- Egress-restrict CI — allowlist build-runner egress to the registry + artifact stores only; deny arbitrary GitHub-release downloads (blocks the Bun fetch). M1037
- Trusted publishing / OIDC — migrate npm publishing to OIDC trusted publishing with required reviewers; remove long-lived automation tokens. M1026 / M1032
- AI-agent hook hygiene — treat
~/.claude/settings.jsonand.vscode/tasks.jsonas sensitive; FIM them and block repo-suppliedfolderOpen/SessionStart auto-run. M1040 - Short-lived runner identities — ephemeral, least-privilege runners; no standing cloud roles attached to build hosts. M1018
- Internal registry / vetting proxy — proxy all npm/PyPI through an internal mirror with version-allowlisting, install-script policy, and provenance gates. M1016
- Build isolation — run installs in network-segmented, no-secret-mounted sandboxes; inject secrets only at deploy, never at
npm install. M1030 - SBOM + continuous provenance — generate SBOMs per build and alert on dependency drift / unsigned republish. M1051
MITRE mitigations cited as the why; the what is anchored to npm/OpenSSF Secure Supply Chain guidance, CIS Software Supply Chain Security, and cloud-provider IMDSv2 hardening (AWS/Azure/GCP) — see §13 for deployable form.
Deployable Playbooks
1 · Harden CI installs (disable scripts + pin)
# .npmrc committed at repo root (and in CI image) ignore-scripts=true audit-signatures=true # CI install step npm ci --ignore-scripts # When a dep genuinely needs a native build, run it explicitly + reviewed: # npm rebuild <allowlisted-pkg>
2 · Audit lockfiles for affected versions (run on every repo)
#!/usr/bin/env bash # Flag any resolved @redhat-cloud-services version present in lockfiles. set -euo pipefail grep -REn '@redhat-cloud-services/' \ --include='package-lock.json' --include='yarn.lock' --include='pnpm-lock.yaml' . \ | grep -Ei 'chrome@2\.3\.|frontend-components@7\.7\.|types@3\.6\.|rbac-client@9\.0\.|rule-components@4\.7\.|remediations-client@4\.0\.|insights-client@4\.0\.|host-inventory-client@5\.0\.|notifications@6\.9\.3|config-utilities@4\.11\.3|tsc-transform-imports@1\.2\.' \ && echo "[!] Affected Miasma version(s) found — pin to pre-2026-06-01 and rotate secrets" \ || echo "[ok] No known-affected versions in lockfiles"
3 · Scan extracted node_modules for Phantom Gyp / markers
#!/usr/bin/env bash
# Detect weaponized binding.gyp and dead-drop markers in installed trees.
set -euo pipefail
ROOT="${1:-.}"
grep -REn --include='binding.gyp' '<!\(.*node ' "$ROOT" \
&& echo "[!] Phantom Gyp command-substitution in binding.gyp"
grep -REn -e 'Miasma: The Spreading Blight' \
-e 'IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner' \
-e '"preinstall": *"node index.js"' "$ROOT" \
&& echo "[!] Miasma marker found" || echo "[ok] clean"
4 · Enforce IMDSv2 hop-limit on AWS build hosts
# Require IMDSv2 and block container hop to metadata (run per instance / in launch template) aws ec2 modify-instance-metadata-options \ --instance-id i-EXAMPLE \ --http-tokens required \ --http-put-response-hop-limit 1 \ --http-endpoint enabled
Containment Runbook
| Phase | Actions | Owner | Evidence |
|---|---|---|---|
| Isolate | Network-contain (Falcon RTR) any host with a Q1/Q2/Q3 hit. Freeze the affected CI pipeline; quarantine build caches and dropped /tmp/b-*/bun. | SOC / Platform | Detection hits, dropped binaries, lockfiles |
| Eradicate | Revoke ALL credentials reachable from affected hosts (npm tokens, GitHub PAT/OIDC roles, AWS/Azure/GCP keys, Vault, kube SA tokens). Delete dead-drop GitHub repos. Remove AI-agent hooks & sudoers writes. Pin deps pre-2026-06-01; rebuild images clean. | IAM / Cloud / Dev | Rotation tickets, repo deletions, diff of hooks |
| Recover | Re-issue least-privilege tokens via trusted publishing; restore pipelines with --ignore-scripts + egress allowlist; re-enable runners only after FIM baseline clean. | Platform / SecEng | Clean rebuild logs, new token inventory |
| Verify | Re-run Q1–Q8 over 72h; confirm no metadata/Vault touches from install runtimes; audit npm publish events for unexpected republish. | SOC | Zero-hit confirmation, npm audit export |
Assume full credential compromise for any identity reachable from a host that installed an affected version since 1 Jun 2026. Token rotation is non-optional — the worm's entire purpose is exfil + republish, so stolen publish tokens enable re-propagation even after you clean the endpoint.
Detection Coverage Map
| Technique | Behavior | CQL | IOA | Coverage |
|---|---|---|---|---|
| T1195.002 / T1059 | Install-time exec (preinstall + Phantom Gyp) → bun | Q1, Q3 | Yes | Good |
| T1552.005 / .001 | Metadata / Vault / secret-file harvest | Q2, Q4 | Partial | Good |
| T1546 | AI-agent / .pth persistence | Q5 | Yes | Partial |
| T1552.004 / T1528 | gh/npm token theft | Q6 | No | Partial |
| T1548.003 | Sudoers NOPASSWD on runner | Q7 | Yes | Good |
| T1518.001 | EDR fingerprinting | Q8 | No | Partial |
| T1567 / T1080 | GitHub dead-drop exfil & package republish | — | No | GAP — off-endpoint |
Known gaps: Exfil-over-GitHub and package republish are not endpoint-visible — covered only by the native npm/GitHub/cloud audit hunts in §7. Ephemeral CI runners without a Falcon sensor are the largest blind spot; prioritize sensor coverage on build infrastructure. Q4/Q5/Q8 depend on file-access telemetry being enabled in the prevention policy. Validation: all gates in §11 must pass before promoting Q1/Q7 to blocking IOAs.
Hunt Summary Ticket
TITLE: Miasma — Red Hat npm Supply-Chain Credential Worm (Shai-Hulud variant)
SEVERITY: Critical — active self-propagating worm, multi-cloud credential theft
SCOPE: CI/CD runners + dev hosts running npm install; npm/GitHub/cloud identities
HYPOTHESIS: Install-time exec (preinstall + Phantom Gyp binding.gyp) drops bun and
harvests GitHub/npm/AWS/Azure/GCP/Vault/K8s secrets, then republishes
infected packages to propagate.
QUERIES: Q1 bun-from-node (high) · Q2 metadata/Vault (high) · Q3 Phantom Gyp (high)
Q4 secret-file reads · Q5 AI-agent hooks · Q6 token theft
Q7 sudoers NOPASSWD (high) · Q8 EDR discovery
DO FIRST: Run Q1 + Q7 across CI runner + dev host groups (lowest FP). Pair Q2+Q3 on
same aid for confirmed install-time compromise.
FINDINGS: <record aid / ComputerName / package version / dropped bun path>
GAPS: Exfil-over-GitHub + republish = off-endpoint (see §7). Sensorless ephemeral
runners = blind spot. Q4/Q5/Q8 need file-access telemetry.
ACTIONS: Isolate hit hosts; rotate ALL reachable npm/GitHub/cloud tokens; pin deps
pre-2026-06-01; npm ci --ignore-scripts; restrict CI egress + IMDSv2.
OWNER: HuntPack
VERSION: v0.1 · 2026-06-21
Changelog
References
| Tier | Source | Used For | Access Date |
|---|---|---|---|
| 1 | Microsoft Security Blog — Preinstall Persistence: Red Hat npm Miasma | Per-package SHA256, exec chain, harvest mechanics, dead-drop, sudoers | 2026-06-21 |
| 1 | Mend.io — Multi-Cloud Credential Stealer (MSC-2026-6085) | Phantom Gyp signature, loader/binding.gyp SHA256, bun URL, metadata endpoints | 2026-06-21 |
| 1 | Harness.io — Inside the Compromise of Red Hat's Packages | Scope (32/96), OIDC/SLSA abuse, hash-evasion, gh token harvest | 2026-06-21 |
| 1 | Phoenix.security — Miasma Shai-Hulud Variant | Decrypted payload SHA256, AI-agent hook paths, PyPI Hades wave, 73-repo auto-disable | 2026-06-21 |
| 1 | Upwind.io — Miasma npm Worm / Red Hat Credential Harvest | Affected versions, .github/setup.js, spoofed User-Agents | 2026-06-21 |
| 2 | The Hacker News — Miasma Supply-Chain Attack | Timeline, AI-agent triggers, attribution (TeamPCP), spoof channel | 2026-06-21 |
| 2 | Unit 42 — Monitoring npm Supply-Chain Attacks | Ecosystem context, second-wave package counts | 2026-06-21 |
Generated 2026-06-21 · HuntPack v0.1 · Defensive hunt/detection/hardening content only.