Back to Intelligence

npm Supply Chain Compromise: keyv & cacheable Packages Poisoned with Ethereum C2 Credential Stealer — OTX Pulse Analysis

SA
Security Arsenal Team
August 5, 2026
11 min read

Threat Summary

AlienVault OTX pulse data confirms an active software supply chain compromise targeting the JavaScript ecosystem. On August 4, 2026, a threat actor compromised the npm maintainer account Jaredwray — maintainer of the widely deployed keyv and cacheable packages — and published at least ten malicious package versions. Combined, these packages account for tens of millions of weekly downloads, meaning the blast radius extends across a massive portion of the global Node.js dependency tree, including transitive dependencies inside enterprise CI/CD pipelines.

The attack chain is surgical and modern:

  1. Initial access: Compromise of a legitimate maintainer account (likely via credential theft or session token hijacking), bypassing the need for typosquatting or social engineering.
  2. Delivery: Malicious preinstall hooks embedded in package.json execute automatically during npm install — before any dependency audit or lockfile review can catch them.
  3. Staging: The hook downloads a Bun runtime (a legitimate JavaScript runtime abused as a living-off-the-land execution vehicle) and runs a heavily obfuscated payload through it.
  4. C2 resolution via blockchain: Rather than hardcoding C2 infrastructure — which defenders can sinkhole — the malware reads its command-and-control address from an Ethereum smart contract. This makes the C2 mutable, resilient, and effectively impossible to take down through traditional registrar/hosting channels.
  5. Objective: Mass credential harvesting — cloud provider keys (AWS/GCP/Azure), npm tokens, SSH keys, environment variables, and CI/CD secrets — with self-propagating worm behavior that uses stolen npm tokens to publish malicious versions of additional packages the victim maintains.

This is a credential theft and propagation engine, not a smash-and-grab. The worm component means every infected developer machine becomes a new distribution node.

Threat Actor / Malware Profile

Attribution: Unknown actor. The tradecraft — maintainer account compromise, preinstall hook execution, blockchain-based C2 resolution, and self-propagation via stolen publish tokens — mirrors techniques observed in prior npm ecosystem attacks (e.g., the Shai-Hulud worm lineage), but no formal attribution exists at this time.

Distribution method: Trojanized legitimate packages published to the official npm registry under a trusted maintainer account. Victims pull the malware through normal npm install workflows — no user error required.

Payload behavior:

  • Executes during package installation via preinstall lifecycle scripts
  • Downloads and stages the Bun runtime to execute payloads outside of the standard Node.js process lineage (evading Node-focused EDR detections)
  • Harvests credentials from environment variables, ~/.npmrc, ~/.aws/credentials, ~/.ssh/, cloud metadata endpoints, and CI secret stores
  • Uses stolen npm automation tokens to self-propagate by publishing poisoned versions of any packages the victim can publish

C2 communication: The C2 address is retrieved dynamically by reading state from an Ethereum smart contract. Network defenders will see outbound JSON-RPC calls to public Ethereum endpoints (e.g., eth_call to nodes such as cloudflare-eth.com, Infura, Alchemy, or public RPC gateways) followed by HTTPS beaconing to the resolved C2. Blocking static C2 domains is insufficient — monitor for anomalous blockchain RPC traffic from developer workstations and build runners.

Persistence mechanism: Primary persistence is environmental — the malware embeds in node_modules, lockfiles, and downstream published packages rather than traditional host persistence. On developer machines, expect credential theft to enable follow-on access that outlives removal of the package itself.

Anti-analysis techniques:

  • Heavy payload obfuscation executed through Bun to break Node.js-specific instrumentation
  • Blockchain C2 resolution defeats static IOC blocking and sandbox detonation without live Ethereum RPC access
  • Execution during preinstall occurs before most npm audit and SCA tooling scans the dependency

IOC Analysis

The pulse contains 9 file-based indicators — 2 SHA1, 3 SHA256, and 4 MD5 hashes — representing the malicious payload samples, staged Bun runtime artifacts, and obfuscated scripts recovered from the compromised package versions.

Operationalization guidance for SOC teams:

  • File hashes (SHA256 priority): Push all SHA256 values into your EDR blocklist immediately. SHA256 is collision-resistant and should be your enforcement-grade indicator. MD5/SHA1 values are useful for retro-hunting in forensic images and artifact stores but should not be the sole blocking mechanism.
  • Where to hunt: These artifacts will appear in node_modules directories, npm cache paths (~/.npm/_cacache), temporary download directories, and CI/CD build agent workspaces — not in standard program directories.
  • Tooling: Use npm ls keyv cacheable and lockfile diffing to identify affected dependency versions across repositories. YARA scanning of build agent disk images and EDR hash-based retrohunts (Defender, CrowdStrike, SentinelOne) will surface historical infections. Network-side, hunt proxy and DNS logs for Ethereum JSON-RPC endpoints originating from non-browser processes.
  • Blockchain IOCs: Identify the Ethereum contract address referenced in the Netskope research and monitor for eth_call requests to it — this is a high-fidelity, low-noise network indicator.

Detection Engineering

YAML
---
title: Malicious npm preinstall Hook Spawning Runtime Download
description: Detects npm/npx install processes spawning shell commands that download or execute payloads, consistent with the keyv/cacheable supply chain compromise using preinstall hooks to stage the Bun runtime
id: 9f3a2c1e-7b4d-4e8f-a2c1-5d6e7f8a9b0c
status: experimental
author: Security Arsenal Threat Intelligence
date: 2026/08/06
logsource:
  category: process_creation
  product: windows
  service: null
detection:
  selection_parent:
    ParentImage|endswith:
      - '\node.exe'
      - '\npm.cmd'
      - '\npm.exe'
      - '\bun.exe'
  selection_child:
    Image|endswith:
      - '\curl.exe'
      - '\powershell.exe'
      - '\pwsh.exe'
      - '\cmd.exe'
      - '\certutil.exe'
      - '\bitsadmin.exe'
  selection_args:
    CommandLine|contains:
      - 'preinstall'
      - 'bun.sh'
      - 'bun.exe'
      - 'install.sh'
      - 'curl '
      - 'Invoke-WebRequest'
  condition: selection_parent and (selection_child or selection_args)
falsepositives:
  - Legitimate build tooling downloading dependencies during CI builds
level: high
tags:
  - attack.initial_access
  - attack.t1195.002
  - attack.t1059
---
title: Outbound Ethereum JSON-RPC C2 Resolution from Non-Browser Process
description: Detects processes other than browsers making outbound connections to public Ethereum RPC endpoints, matching the npm stealer technique of reading C2 configuration from an Ethereum smart contract
id: 2b7c4d5e-1a8f-4c9d-b3e2-6f7a8b9c0d1e
status: experimental
author: Security Arsenal Threat Intelligence
date: 2026/08/06
logsource:
  category: network_connection
  product: windows
detection:
  selection_dest:
    DestinationHostname|contains:
      - 'cloudflare-eth.com'
      - 'infura.io'
      - 'alchemy.com'
      - 'rpc.ankr.com'
      - 'eth.llamarpc.com'
      - 'ethereum-rpc.publicnode.com'
      - 'mainnet.infura.io'
  filter_browsers:
    Image|endswith:
      - '\chrome.exe'
      - '\firefox.exe'
      - '\msedge.exe'
      - '\brave.exe'
      - '\opera.exe'
  condition: selection_dest and not filter_browsers
falsepositives:
  - Legitimate Web3 development tooling and crypto wallet desktop applications
level: high
tags:
  - attack.command_and_control
  - attack.t1071.001
  - attack.t1102
---
title: Bun Runtime Execution of Obfuscated JavaScript Payload
description: Detects execution of the Bun runtime with script arguments from temporary or npm cache directories, matching the keyv/cacheable payload staging behavior
id: 5e8f1a2b-3c4d-4e5f-9a6b-7c8d9e0f1a2b
status: experimental
author: Security Arsenal Threat Intelligence
date: 2026/08/06
logsource:
  category: process_creation
  product: linux
detection:
  selection_img:
    Image|endswith:
      - '/bun'
      - '/npm'
      - '/node'
  selection_paths:
    CommandLine|contains:
      - '/tmp/'
      - '/.npm/_cacache'
      - 'node_modules'
      - 'preinstall'
      - 'eval('
      - 'atob('
      - 'base64'
  condition: selection_img and selection_paths
falsepositives:
  - Legitimate packages using preinstall scripts for native module compilation
level: medium
tags:
  - attack.execution
  - attack.t1059.007
  - attack.t1027
KQL — Microsoft Sentinel / Defender
// Hunt: npm supply chain compromise artifacts — keyv/cacheable Ethereum C2 stealer
// Looks for: (1) hash matches on known malicious payloads, (2) suspicious child processes
// of npm/node/bun, (3) non-browser connections to Ethereum RPC endpoints

let MaliciousHashes = dynamic([
  "54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668",
  "9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc",
  "fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1eb",
  "4140f7e17e6f97f83aa3472473e01add",
  "7bcf8d9f6834c44450eac145a967d2f2",
  "f92ee93a0af971a3966bfa8efa9c2625"
]);
let EthRpcHosts = dynamic([
  "cloudflare-eth.com", "infura.io", "alchemy.com",
  "rpc.ankr.com", "eth.llamarpc.com", "ethereum-rpc.publicnode.com"
]);

// Part 1: File hash matches in node_modules, npm cache, or temp paths
union withsource=tableName_
  (DeviceFileEvents
   | where SHA256 in~ (MaliciousHashes) or MD5 in~ (MaliciousHashes)
   | project tableName_, TimeGenerated, DeviceName, FileName, FolderPath, SHA256, MD5, InitiatingProcessCommandLine),
  (DeviceProcessEvents
   | where SHA256 in~ (MaliciousHashes) or MD5 in~ (MaliciousHashes)
   | project tableName_, TimeGenerated, DeviceName, FileName, FolderPath, SHA256, MD5, ProcessCommandLine),

// Part 2: Suspicious child processes spawned by package managers
  (DeviceProcessEvents
   | where InitiatingProcessFileName has_any ("node.exe", "npm.cmd", "npm", "bun", "bun.exe")
   | where FileName has_any ("powershell.exe", "pwsh.exe", "cmd.exe", "curl.exe", "curl", "wget", "certutil.exe", "sh", "bash")
   | where ProcessCommandLine has_any ("preinstall", "bun", "Invoke-WebRequest", "iwr", "base64", "eval(", "/tmp/")
   | project tableName_, TimeGenerated, DeviceName, FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine, AccountName),

// Part 3: Non-browser processes resolving C2 via Ethereum RPC
  (DeviceNetworkEvents
   | where RemoteUrl has_any (EthRpcHosts)
   | where not(InitiatingProcessFileName has_any ("chrome.exe", "firefox.exe", "msedge.exe", "brave.exe", "opera.exe"))
   | project tableName_, TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, RemoteUrl, RemoteIP, RemotePort)
| sort by TimeGenerated desc
PowerShell
<#
.SYNOPSIS
  Hunt script: npm keyv/cacheable supply chain compromise artifacts
.DESCRIPTION
  Checks developer workstations and build agents for:
  - Known malicious file hashes (SHA256/MD5/SHA1) in npm cache, node_modules, temp dirs
  - Installed keyv/cacheable packages with suspicious preinstall hooks
  - Recent Bun runtime downloads staged outside normal paths
  - Evidence of credential store access following install activity
  Run as Administrator. Output written to C:\Temp\npm-sc-hunt.csv
#>

$ErrorActionPreference = 'SilentlyContinue'
$results = @()
$outFile = "C:\Temp\npm-sc-hunt.csv"
New-Item -ItemType Directory -Path "C:\Temp" -Force | Out-Null

$maliciousHashes = @(
  "54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668",
  "9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc",
  "fd3ca4007b225fdf8de7af4345a19179d5efa8c4bb9205f88cda806e5684b1eb",
  "4140f7e17e6f97f83aa3472473e01add",
  "7bcf8d9f6834c44450eac145a967d2f2",
  "f92ee93a0af971a3966bfa8efa9c2625",
  "35a672cf34b996b91f3e1c28cbf3a05a37e036e4",
  "f525d52ceb966516686b482d3dc0137028cc6a63"
)

Write-Host "[*] Phase 1: Hash sweep of npm cache, temp, and node_modules paths..." -ForegroundColor Cyan
$searchRoots = @(
  "$env:LOCALAPPDATA\npm-cache",
  "$env:APPDATA\npm-cache",
  "$env:TEMP",
  "$env:USERPROFILE\.npm\_cacache",
  "C:\Windows\Temp"
)
foreach ($root in $searchRoots) {
  if (Test-Path $root) {
    Get-ChildItem -Path $root -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object {
      $sha256 = (Get-FileHash -Path $_.FullName -Algorithm SHA256).Hash
      if ($maliciousHashes -contains $sha256.ToLower()) {
        $results += [PSCustomObject]@{
          Finding = "MALICIOUS_HASH_MATCH"
          Path    = $_.FullName
          Hash    = $sha256
          Detail  = "SHA256 match against OTX pulse IOC"
          Time    = Get-Date
        }
        Write-Host "[!!!] MALICIOUS FILE: $($_.FullName)" -ForegroundColor Red
      }
    }
  }
}

Write-Host "[*] Phase 2: Scanning package.json files for suspicious preinstall hooks..." -ForegroundColor Cyan
$userDirs = Get-ChildItem "C:\Users" -Directory
foreach ($user in $userDirs) {
  Get-ChildItem -Path $user.FullName -Recurse -Filter "package.json" -File -ErrorAction SilentlyContinue |
    Where-Object { $_.FullName -match "node_modules" } | ForEach-Object {
      $content = Get-Content $_.FullName -Raw
      if ($content -match '"preinstall"' -and $content -match 'curl|wget|bun|powershell|Invoke-WebRequest|base64') {
        $results += [PSCustomObject]@{
          Finding = "SUSPICIOUS_PREINSTALL_HOOK"
          Path    = $_.FullName
          Hash    = "N/A"
          Detail  = "preinstall hook containing download/execution primitives"
          Time    = Get-Date
        }
        Write-Host "[!!] Suspicious preinstall: $($_.FullName)" -ForegroundColor Yellow
      }
    }
}

Write-Host "[*] Phase 3: Checking for rogue Bun runtime installs..." -ForegroundColor Cyan
$bunPaths = @("$env:TEMP\bun.exe", "$env:LOCALAPPDATA\Temp\bun.exe", "C:\ProgramData\bun\bun.exe")
foreach ($p in $bunPaths) {
  if (Test-Path $p) {
    $h = (Get-FileHash $p -Algorithm SHA256).Hash
    $results += [PSCustomObject]@{
      Finding = "BUN_RUNTIME_STAGED"
      Path    = $p
      Hash    = $h
      Detail  = "Bun runtime present in non-standard staging path"
      Time    = Get-Date
    }
    Write-Host "[!!] Bun runtime staged: $p" -ForegroundColor Yellow
  }
}

Write-Host "[*] Phase 4: Checking active connections to Ethereum RPC endpoints..." -ForegroundColor Cyan
$ethHosts = @("cloudflare-eth.com","infura.io","alchemy.com","rpc.ankr.com","eth.llamarpc.com")
Get-NetTCPConnection -State Established | ForEach-Object {
  $proc = Get-Process -Id $_.OwningProcess
  if ($proc.ProcessName -notmatch "chrome|firefox|msedge|brave|opera") {
    $results += [PSCustomObject]@{
      Finding = "NONBROWSER_OUTBOUND_CONN"
      Path    = $proc.Path
      Hash    = "N/A"
      Detail  = "$($proc.ProcessName) -> $($_.RemoteAddress):$($_.RemotePort) — validate against Ethereum RPC hosts"
      Time    = Get-Date
    }
  }
}

Write-Host "[*] Phase 5: Flagging credential files modified in last 72h (possible theft staging)..." -ForegroundColor Cyan
$credFiles = @("$env:USERPROFILE\.npmrc", "$env:USERPROFILE\.aws\credentials", "$env:USERPROFILE\.ssh\id_rsa")
foreach ($cf in $credFiles) {
  if (Test-Path $cf) {
    $item = Get-Item $cf
    if ($item.LastWriteTime -gt (Get-Date).AddHours(-72)) {
      $results += [PSCustomObject]@{
        Finding = "CREDENTIAL_FILE_RECENT_ACCESS"
        Path    = $cf
        Hash    = "N/A"
        Detail  = "Modified $($item.LastWriteTime) — correlate with install activity"
        Time    = Get-Date
      }
      Write-Host "[!!] Credential file recently modified: $cf" -ForegroundColor Yellow
    }
  }
}

if ($results.Count -gt 0) {
  $results | Export-Csv -Path $outFile -NoTypeInformation
  Write-Host "`n[+] $($results.Count) findings exported to $outFile" -ForegroundColor Green
} else {
  Write-Host "`n[+] No indicators found." -ForegroundColor Green
}

Response Priorities

Immediate (0–4 hours):

  • Push all 9 IOC hashes into EDR/AV blocklists; enforce at the gateway where possible
  • Freeze dependency updates in CI/CD pipelines; pin lockfiles to known-good versions of keyv and cacheable released before 2026-08-04
  • Run the hash sweep and preinstall-hook hunt across developer workstations and build agents
  • Block or alert on non-browser outbound traffic to public Ethereum RPC endpoints

Within 24 hours:

  • Rotate all credentials on any machine that installed a compromised package version — this is credential-stealing malware: npm automation tokens, AWS/GCP/Azure keys, SSH keys, and any secrets present in environment variables or CI secret stores must be treated as compromised
  • Audit npm publish activity for any packages maintained by your developers — the self-propagating worm uses stolen tokens to publish poisoned versions under victim identities
  • Revoke and reissue CI/CD pipeline tokens; review cloud audit logs (CloudTrail, Azure Activity Log) for anomalous API usage from developer IP ranges

Within 1 week:

  • Enforce phishing-resistant MFA (FIDO2/hardware keys) on all npm maintainer and publisher accounts — this attack began with a maintainer account compromise
  • Deploy lifecycle-script blocking in build environments (npm config set ignore-scripts true where feasible) and require explicit allowlisting for preinstall/postinstall hooks
  • Implement private registry proxying (Artifactory/Nexus/Verdaccio) with quarantine windows for newly published package versions before they reach build agents
  • Add egress controls restricting build runners to an approved destination list — blockchain RPC endpoints and arbitrary download hosts should never be reachable from CI infrastructure
  • Integrate lockfile integrity verification and SCA tooling with install-time behavioral analysis into the pipeline

Related Resources

Security Arsenal Incident Response Managed SOC & MDR Services AlertMonitor Threat Detection From The Dark Side Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.