Preinstall to Persistence: Inside the Red Hat npm Miasma Credential-Theft Campaign
Detection stack
- AIDR
- Alert
- ETL
- Query
Summary
Microsoft Defender identified a supply-chain compromise affecting 32 npm packages published under the @redhat-cloud-services namespace. The attackers embedded a pre-install hook that deploys a heavily obfuscated JavaScript loader, which then retrieves the Bun runtime and a second-stage credential stealer. The malware harvests tokens from GitHub, npm, major cloud providers, HashiCorp Vault, and Kubernetes, then republishes poisoned packages to continue spreading. The campaign also includes a destructive safeguard that wipes the victim’s home directory if a decoy token is detected.
Investigation
The investigation traced the initial breach to a compromised CI/CD pipeline for RedHatInsights/javascript-clients, where the attackers abused a legitimate GitHub Actions OIDC workflow to publish trojanized packages with valid provenance signatures. Analysis of the dropper exposed multiple layers of obfuscation, including ROT-based encoding, AES-128-GCM encryption, and a custom cipher, before Bun was executed to launch the second-stage payload. The threat actors also scraped memory from GitHub Actions runners to extract secrets directly from active processes.
Mitigation
Microsoft recommends reviewing dependency trees for the affected @redhat-cloud-services packages, pinning trusted versions, and disabling npm script execution with the --ignore-scripts flag where possible. All compromised npm tokens should be revoked and reissued, and GitHub accounts should be reviewed for unauthorized repository creation or suspicious activity. Additional safeguards have since been implemented within the @redhat-cloud-services namespace to prevent unauthorized publishing.
Response
Defenders should detect suspicious npm pre-install execution and unexpected Bun runtime launches from temporary directories. Monitoring should also cover creation of unknown public GitHub repositories and unusual token usage patterns. Network defenses should block or alert on connections to known Bun download URLs and the api.anthropic.com domain. Any potentially exposed credentials should be rotated immediately, and CI/CD runner memory should be investigated for signs of leaked secrets.
"graph TB %% Class Definitions classDef action fill:#99ccff classDef tool fill:#cccccc classDef malware fill:#ffcc99 classDef process fill:#ff9966 %% Nodes node_sc_001["<b>Action</b> – <b>T1195.001 Supply Chain Compromise</b>: Compromise Software Dependencies and Development Tools<br/>Attacker hijacked the RedHatInsights/javascript-clients CI/CD pipeline and used GitHub Actions OIDC to publish trojanized @redhat-cloud-services npm packages."] class node_sc_001 action tool_github_oidc["<b>Tool</b> – <b>Name</b>: GitHub Actions OIDC<br/><b>Description</b>: OpenID Connect integration that provides shortu2011lived tokens to CI workflows."] class tool_github_oidc tool malicious_preinstall["<b>Malware</b> – <b>T1127.003 Trusted Developer Utilities Proxy Execution</b>: Malicious preu2011install hook in package.json executed automatically during <code>npm install</code>, spawning <code>node index.js</code>."] class malicious_preinstall malware installer_trigger["<b>Action</b> – <b>T1546.016 Event Triggered Execution</b>: Installeru2011package trigger that runs the dropper when the preu2011install script is invoked."] class installer_trigger action obfuscated_payload["<b>Malware</b> – <b>T1027.009 Obfuscated Files or Information</b>: Embedded payload (4.29u202fMB index.js) using multiu2011layer ROT, AESu2011128u2011GCM, stringu2011array and custom PBKDF2 cipher to hide its code."] class obfuscated_payload malware dynamic_resolution["<b>Malware</b> – <b>T1027.007 Obfuscated Files or Information</b>: Dynamic API resolution u2013 runtime deobfuscation resolves URLs for the Bun runtime and C2 endpoints."] class dynamic_resolution malware ingress_transfer["<b>Action</b> – <b>T1105 Ingress Tool Transfer</b>: Dropper downloads the Bun JavaScript runtime from official release URLs before executing the secondu2011stage payload."] class ingress_transfer action tool_bun_runtime["<b>Tool</b> – <b>Name</b>: Bun JavaScript Runtime<br/><b>Description</b>: Highu2011performance JavaScript engine used as the execution environment for the second stage."] class tool_bun_runtime tool metadata_query["<b>Action</b> – <b>T1552.005 Unsecured Credentials</b>: Cloud Instance Metadata API<br/>Queries AWS/ECS, Azure IMDS and GCP metadata services to obtain cloud access tokens."] class metadata_query action file_credential["<b>Action</b> – <b>T1552.001 Unsecured Credentials</b>: Credentials In Files<br/>Scrapes local files for SSH keys, CLI configs, cryptocurrency wallet files and other secret material."] class file_credential action token_harvest["<b>Action</b> – <b>T1528 Steal Application Access Token</b>: Harvests GitHub Actions runner tokens, npm OIDC tokens and other provider tokens."] class token_harvest action browser_discovery["<b>Action</b> – <b>T1217 Browser Information Discovery</b>: Collects browser stores and cryptocurrency wallet files from developer workstations."] class browser_discovery action elevation_control["<b>Action</b> – <b>T1548 Abuse Elevation Control Mechanism</b>: Installs a passwordu2011less sudo rule via a bindu2011mounted <code>/etc/sudoers.d</code> to gain root privileges."] class elevation_control action exfiltration_git["<b>Action</b> – <b>T1048 Exfiltration Over Alternative Protocol</b>: Exfiltrates stolen credentials by creating GitHub repositories under the victimu2019s account, committing JSON files, and also uses an alternate HTTPS endpoint."] class exfiltration_git action destructive_cleanup["<b>Action</b> – <b>T1070.010 Indicator Removal</b>: Relocates malware and, if a decoy token was used, executes <code>rm -rf ~/</code> to wipe the useru2019s home directory."] class destructive_cleanup action node_sc_002["<b>Action</b> – <b>T1195.002 Supply Chain Compromise</b>: Compromise Software Supply Chain u2013 republished poisoned packages with forged SLSA provenance, enabling wormu2011like propagation to downstream projects."] class node_sc_002 action %% Connections node_sc_001 –>|uses| tool_github_oidc tool_github_oidc –>|executes| malicious_preinstall malicious_preinstall –>|triggers| installer_trigger installer_trigger –>|executes| obfuscated_payload obfuscated_payload –>|performs| dynamic_resolution dynamic_resolution –>|downloads| ingress_transfer ingress_transfer –>|installs| tool_bun_runtime tool_bun_runtime –>|queries| metadata_query metadata_query –>|scrapes| file_credential file_credential –>|harvests| token_harvest token_harvest –>|collects| browser_discovery browser_discovery –>|enables| elevation_control elevation_control –>|exfiltrates via| exfiltration_git exfiltration_git –>|may trigger| destructive_cleanup destructive_cleanup –>|facilitates| node_sc_002 node_sc_002 –>|propagates to| node_sc_001 "
Attack Flow
Detections
Remote File Upload / Download via Standard Tools (via cmdline)
View
IOCs (HashSha256) to detect: Preinstall to persistence: Inside the Red Hat npm Miasma credential-stealing campaign
View
Suspicious Node.js Process Behavior with Bun Runtime Execution [Windows Process Creation]
View
Bun Runtime Execution and Passwordless Sudo Rule Detection [Linux Process Creation]
View
GCP Service-Account Token Harvesting by Miasma Campaign [Google Cloud Platform]
View
Detection of Unauthorized Azure IMDS OAuth2 Token Access [Azure Activity Logs]
View
Credential Access via IMDS OAuth2 Token Retrieval and Secrets Manager Access [AWS Cloudtrail]
View
Simulation Execution
Prerequisite: The Telemetry & Baseline Pre‑flight Check must have passed.
Rationale: This section details the precise execution of the adversary technique (TTP) designed to trigger the detection rule. The commands and narrative MUST directly reflect the TTPs identified and aim to generate the exact telemetry expected by the detection logic.
-
Attack Narrative & Commands:
An adversary who has already compromised a GCE VM uses a living‑off‑the‑land approach to harvest the VM’s default service‑account token. The steps are:- Probe the metadata server to verify it is reachable.
- Issue a token request to
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/tokenwith the requiredMetadata-Flavor: Googleheader. - Save the returned JSON (contains
access_token,expires_in,token_type). - Use the token to call a privileged GCP API (e.g., list all buckets) to demonstrate actionable credentials.
This exact sequence generates the audit event
GCPServiceAccountTokenAccess, which matches the Sigma rule’sselection. -
Regression Test Script:
#!/usr/bin/env bash set -euo pipefail # 1. Verify metadata server reachability echo "[*] Probing metadata server..." curl -s -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/id" >/dev/null # 2. Harvest the default service‑account token echo "[*] Requesting service‑account token..." TOKEN_RESPONSE=$(curl -s -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token") echo "[+] Token response received:" echo "${TOKEN_RESPONSE}" | jq . # 3. Extract the access token ACCESS_TOKEN=$(echo "${TOKEN_RESPONSE}" | jq -r '.access_token') # 4. Use the token to list GCS buckets (demonstrates token utility) echo "[*] Using token to list GCS buckets..." curl -s -H "Authorization: Bearer ${ACCESS_TOKEN}" "https://storage.googleapis.com/storage/v1/b" | jq . echo "[+] Simulation complete. The above actions should have produced a GCPServiceAccountTokenAccess audit event." -
Cleanup Commands:
# No persistent changes were made; only removing temporary variables. unset TOKEN_RESPONSE ACCESS_TOKEN echo "[*] Cleanup complete."