# [CRIT] Miasma Malware Targets npm Packages and GitHub Actions in Supply Chain Attack

**Source:** The Hacker News
**Published:** 2026-06-26
**Article:** https://thehackernews.com/2026/06/miasma-malware-targets-npm-packages-and.html

## Threat Profile

Miasma Malware Targets npm Packages and GitHub Actions in Supply Chain Attack 
 Ravie Lakshmanan  Jun 26, 2026 Supply Chain Attack / Developer Security 
Cybersecurity researchers have flagged yet another evolution of the supply chain attack linked to the Mini Shai-Hulud, Miasma, and Hades malware family that has compromised a new set of npm packages, even as it has propagated to the Go ecosystem.
"The latest activity includes malicious npm releases affecting LeoPlatform and RStreams packages, …

## Indicators of Compromise (high-fidelity only)

- **SHA1:** `6b9501e1889cc45c91726729610cf69c2442b8c5`

## MITRE ATT&CK Techniques

- **T1539** — Steal Web Session Cookie
- **T1555.003** — Credentials from Web Browsers
- **T1195.002** — Compromise Software Supply Chain
- **T1027** — Obfuscated Files or Information
- **T1059.007** — Command and Scripting Interpreter: JavaScript
- **T1204.003** — User Execution: Malicious Image/Package
- **T1119** — Automated Collection
- **T1552.007** — Unsecured Credentials: Container API / CI Secrets
- **T1102.002** — Web Service: Bidirectional Communication
- **T1071.001** — Application Layer Protocol: Web Protocols
- **T1568** — Dynamic Resolution
- **T1027.013** — Obfuscated Files or Information: Encrypted/Encoded File

## Kill chain phases observed

_(none detected from narrative keywords)_

## Recommended hunts

### Miasma/Shai-Hulud binding.gyp install-time exec spawning Bun stealer

`UC_4_3` · phase: **exploit** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime from datamodel=Endpoint.Processes where (Processes.process_name IN ("bun.exe","bun")) AND (Processes.parent_process_name IN ("node.exe","node","node-gyp.exe","npm.exe","npm-cli.js","sh","bash","cmd.exe")) by Processes.dest Processes.user Processes.process_name Processes.parent_process_name Processes.process Processes.parent_process
| `drop_dm_object_name(Processes)`
| eval temp_payload=if(match(process,"(?i)/tmp/p[a-z0-9]+\.js"),1,0), gyp_parent=if(match(parent_process,"(?i)binding\.gyp|node-gyp"),1,0)
| convert ctime(firstTime) ctime(lastTime)
| table firstTime lastTime dest user process_name parent_process_name process parent_process temp_payload gyp_parent count
```

**Defender KQL:**
```kql
DeviceProcessEvents
| where Timestamp > ago(14d)
| where AccountName !endswith "$"
| where (FileName in~ ("bun.exe","bun") and InitiatingProcessFileName in~ ("node.exe","node","node-gyp.exe","npm.exe","npm-cli.js","sh","bash","cmd.exe"))
    or (InitiatingProcessCommandLine has_any ("binding.gyp","node-gyp rebuild","node-gyp build") and FileName in~ ("bun.exe","bun","node.exe","node"))
    or (FileName in~ ("bun.exe","bun") and ProcessCommandLine matches regex @"(?i)/tmp/p[a-z0-9]+\.js")
| project Timestamp, DeviceName, AccountName, FileName, FolderPath, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine, InitiatingProcessFolderPath, SHA1
| order by Timestamp desc
```

### Trojanized LeoPlatform/RStreams npm package landing via binding.gyp drop

`UC_4_4` · phase: **delivery** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime from datamodel=Endpoint.Filesystem where Filesystem.file_name="binding.gyp" AND Filesystem.file_path="*node_modules*" by Filesystem.dest Filesystem.file_name Filesystem.file_path
| `drop_dm_object_name(Filesystem)`
| where match(file_path,"(?i)node_modules[\\/](leo-auth|leo-aws|leo-cache|leo-cdk-lib|leo-cli|leo-config|leo-connector-|leo-cron|leo-logger|leo-sdk|leo-streams|prism-silq|rstreams-metrics|rstreams-shard-util|serverless-convention|serverless-leo|solo-nav|hexo-deployer-wrangler|hexo-shoka-swiper)")
| convert ctime(firstTime) ctime(lastTime)
| table firstTime lastTime dest file_name file_path count
```

**Defender KQL:**
```kql
DeviceFileEvents
| where Timestamp > ago(14d)
| where ActionType in ("FileCreated","FileModified")
| where FileName =~ "binding.gyp"
| where FolderPath has "node_modules"
| where FolderPath has_any ("leo-auth","leo-aws","leo-cache","leo-cdk-lib","leo-cli","leo-config","leo-connector-","leo-cron","leo-logger","leo-sdk","leo-streams","prism-silq","rstreams-metrics","rstreams-shard-util","serverless-convention","serverless-leo","solo-nav","hexo-deployer-wrangler","hexo-shoka-swiper")
| where InitiatingProcessAccountName !endswith "$"
| project Timestamp, DeviceName, InitiatingProcessAccountName, FileName, FolderPath, InitiatingProcessFileName, InitiatingProcessCommandLine, SHA1
| order by Timestamp desc
```

### Miasma 'Run Copilot' GitHub Actions workflow dropped by node/bun

`UC_4_5` · phase: **install** · confidence: **Medium** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime from datamodel=Endpoint.Filesystem where (Filesystem.file_name="*.yml" OR Filesystem.file_name="*.yaml") AND Filesystem.file_path="*workflows*" by Filesystem.dest Filesystem.file_name Filesystem.file_path Filesystem.process_name
| `drop_dm_object_name(Filesystem)`
| where match(file_path,"(?i)\.github[\\/]workflows") AND (process_name IN ("node.exe","node","bun.exe","bun","npm.exe"))
| convert ctime(firstTime) ctime(lastTime)
| table firstTime lastTime dest process_name file_name file_path count
```

**Defender KQL:**
```kql
DeviceFileEvents
| where Timestamp > ago(14d)
| where ActionType in ("FileCreated","FileModified")
| where FolderPath has_any (@"\.github\workflows\", "/.github/workflows/")
| where FileName endswith ".yml" or FileName endswith ".yaml"
| where InitiatingProcessFileName in~ ("node.exe","node","bun.exe","bun","npm.exe","npm-cli.js")
| where InitiatingProcessAccountName !endswith "$"
| project Timestamp, DeviceName, InitiatingProcessAccountName, FileName, FolderPath, InitiatingProcessFileName, InitiatingProcessCommandLine, SHA1
| order by Timestamp desc
```

### Miasma GitHub dead-drop C2 hourly beaconing from node/bun

`UC_4_6` · phase: **c2** · confidence: **Medium** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count from datamodel=Network_Resolution where (Network_Resolution.query="api.github.com" OR Network_Resolution.query="github.com" OR Network_Resolution.query="codeload.github.com" OR Network_Resolution.query="raw.githubusercontent.com") by Network_Resolution.src Network_Resolution.query _time span=1h
| `drop_dm_object_name(Network_Resolution)`
| stats dc(_time) as hour_buckets sum(count) as total min(_time) as firstTime max(_time) as lastTime by src query
| where hour_buckets >= 6
| convert ctime(firstTime) ctime(lastTime)
| sort - hour_buckets
```

**Defender KQL:**
```kql
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where RemoteUrl has_any ("api.github.com","github.com","codeload.github.com","raw.githubusercontent.com")
| where InitiatingProcessFileName in~ ("node.exe","node","bun.exe","bun")
| summarize ConnCount = count(), HourBuckets = dcount(bin(Timestamp, 1h)), FirstSeen = min(Timestamp), LastSeen = max(Timestamp), SampleCmd = any(InitiatingProcessCommandLine) by DeviceName, InitiatingProcessFileName, RemoteUrl
| where HourBuckets >= 6   // regular polling across >=6 distinct hours = beaconing cadence (worm polls hourly for 'firedalazer')
| order by HourBuckets desc
```

### Shai-Hulud/Miasma unique dead-drop marker strings & payload hash on endpoint

`UC_4_7` · phase: **c2** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime from datamodel=Endpoint.Processes where (Processes.process="*RevokeAndItGoesKaboom*" OR Processes.process="*firedalazer*" OR Processes.process="*Alright Lets See If This Works*" OR Processes.process="*IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner*") by Processes.dest Processes.user Processes.process_name Processes.parent_process_name Processes.process
| `drop_dm_object_name(Processes)`
| convert ctime(firstTime) ctime(lastTime)
| table firstTime lastTime dest user process_name parent_process_name process count
```

**Defender KQL:**
```kql
union
(DeviceProcessEvents
  | where Timestamp > ago(30d)
  | where ProcessCommandLine has_any ("RevokeAndItGoesKaboom","firedalazer","Alright Lets See If This Works","IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner")
        or SHA1 == "6b9501e1889cc45c91726729610cf69c2442b8c5"
        or InitiatingProcessSHA1 == "6b9501e1889cc45c91726729610cf69c2442b8c5"
  | project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, SHA1, InitiatingProcessFileName, InitiatingProcessCommandLine),
(DeviceFileEvents
  | where Timestamp > ago(30d)
  | where SHA1 == "6b9501e1889cc45c91726729610cf69c2442b8c5"
  | project Timestamp, DeviceName, AccountName = InitiatingProcessAccountName, FileName, ProcessCommandLine = InitiatingProcessCommandLine, SHA1, InitiatingProcessFileName, InitiatingProcessCommandLine)
| order by Timestamp desc
```

### Infostealer — non-browser process accessing browser cookie/login DBs

`UC_BROWSER_STEALER` · phase: **actions** · confidence: **High**

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime
    from datamodel=Endpoint.Filesystem
    where (Filesystem.file_path="*\Google\Chrome\User Data\*\Login Data*"
        OR Filesystem.file_path="*\Google\Chrome\User Data\*\Cookies*"
        OR Filesystem.file_path="*\Microsoft\Edge\User Data\*\Login Data*"
        OR Filesystem.file_path="*\Mozilla\Firefox\Profiles\*\logins.json*"
        OR Filesystem.file_path="*\Mozilla\Firefox\Profiles\*\cookies.sqlite*")
      AND NOT Filesystem.process_name IN ("chrome.exe","msedge.exe","firefox.exe","brave.exe","opera.exe")
    by Filesystem.dest, Filesystem.process_name, Filesystem.file_path, Filesystem.user
| `drop_dm_object_name(Filesystem)`
```

**Defender KQL:**
```kql
DeviceFileEvents
| where Timestamp > ago(7d)
| where InitiatingProcessAccountName !endswith "$"
| where FolderPath has_any (@"\Google\Chrome\User Data\", @"\Microsoft\Edge\User Data\", @"\Mozilla\Firefox\Profiles\")
| where FileName in~ ("Login Data","Cookies","logins.json","cookies.sqlite")
| where InitiatingProcessFileName !in~ ("chrome.exe","msedge.exe","firefox.exe","brave.exe","opera.exe")
| project Timestamp, DeviceName, InitiatingProcessAccountName, InitiatingProcessFileName, FolderPath, FileName, ActionType
```

### Trusted vendor binary / installer launching unusual children

`UC_SUPPLY_CHAIN` · phase: **exploit** · confidence: **Medium**

**Splunk SPL (CIM):**
```spl
| tstats `summariesonly` count min(_time) as firstTime max(_time) as lastTime
    from datamodel=Endpoint.Processes
    where Processes.parent_process_name IN ("setup.exe","installer.exe","update.exe")
      AND Processes.process_name IN ("powershell.exe","cmd.exe","rundll32.exe","regsvr32.exe","mshta.exe","wscript.exe","cscript.exe","wmic.exe","bitsadmin.exe")
    by Processes.dest, Processes.user, Processes.parent_process_name, Processes.process_name, Processes.process
| `drop_dm_object_name(Processes)`
```

**Defender KQL:**
```kql
DeviceProcessEvents
| where Timestamp > ago(7d)
| where AccountName !endswith "$"
| where InitiatingProcessFileName in~ ("setup.exe","installer.exe","update.exe")
| where FileName in~ ("powershell.exe","cmd.exe","rundll32.exe","regsvr32.exe","mshta.exe","wscript.exe","cscript.exe","wmic.exe","bitsadmin.exe")
| project Timestamp, DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessCommandLine
```

### IOC-driven hunts (use shared templates)

These are standard IOC-substitution hunts — the canonical SPL and KQL live once in [`_TEMPLATES.md`](../_TEMPLATES.md), so we don't repeat the same boilerplate on every CVE / hash / network-IOC briefing.

- **File hash IOCs — endpoint file/process match** ([template](../_TEMPLATES.md#hash-ioc)) — phase: **install**, confidence: **High**
  - file hash IOC(s): `6b9501e1889cc45c91726729610cf69c2442b8c5`


## Why this matters

Severity classified as **CRIT** based on: IOCs present, 8 use case(s) fired, 12 technique(s) inferred. Read the full article for actor attribution, tooling details, and any defanged IOCs in the body that aren't visible in the RSS summary.
