Threat Summary
Two converging OTX pulses paint a clear picture: the npm ecosystem is under coordinated, industrialized attack, and the primary objective is credential theft at the developer layer — the keys to the enterprise kingdom.
The first pulse documents a Russian-nexus AI slopsquatting campaign: over 700 malicious packages published to the NPM registry within 48 hours, using AI-generated typosquat names that mimic legitimate packages. Unlike traditional malicious packages that rely on postinstall scripts, these payloads execute immediately upon require() import — meaning a simple dependency resolution in a developer's build environment triggers infection. The dropper (tracked as NUL1DROPPER) is cross-platform (Windows, Linux, macOS) and stages Sliver C2 implants plus an infostealer component, rotating through three Cloudflare Workers hosts for payload delivery and supporting DNS tunneling for covert exfiltration.
The second pulse covers ChainDrop, a self-propagating npm worm that infected over 400 packages — including massively popular dependencies like keyv and cacheable-request with hundreds of millions of weekly downloads. ChainDrop steals cloud credentials, npm/GitHub tokens, SSH keys, and scrapes temporary credentials directly from GitHub Actions runner memory. Its most dangerous trait: it uses stolen npm publishing tokens to autonomously republish itself into the victim's own packages, creating a worm-style propagation loop across the dependency graph. It also leverages an Ethereum smart contract as a blockchain-based C2/dead-drop mechanism, making takedown extremely difficult.
Collective assessment: Both campaigns converge on the same objective — harvesting developer credentials, CI/CD tokens, and cloud keys that are later monetized or weaponized. Stolen npm/GitHub tokens and cloud credentials from these campaigns are exactly the commodity class traded on dark web credential markets and initial access broker (IAB) forums. Any organization with a Node.js development pipeline should assume exposure and hunt now.
Threat Actor / Malware Profile
NUL1DROPPER + Sliver (Slopsquatting Campaign)
- Distribution: 700+ AI-generated typosquat packages on the NPM registry; execution on
require()import — no install scripts needed, evading traditional install-script scanning. - Payload behavior: Cross-platform downloader (Windows/Linux/macOS) staging a Sliver RAT implant and infostealer module targeting browser credential stores, session tokens, and developer secrets.
- C2 communication: Rotates through three Cloudflare Workers hosts (
sdk.dl.wel1.ru,ext.dl.wel1.ru,pkg.dl.wel1.ru,net.dl.wel1.ru) to blend malicious traffic with legitimate CDN infrastructure. Supports DNS tunneling for exfiltration in egress-restricted environments. - Persistence: Sliver implants support multiple persistence vectors (registry Run keys, cron, systemd services, launch agents depending on OS).
- Anti-analysis: Cloudflare Workers abuse defeats static domain reputation; AI-generated package names evade known-bad package lists; import-time execution bypasses install-script hooks.
ChainDrop (Self-Propagating npm Worm)
- Distribution: Trojanized versions of legitimate high-download packages (keyv, cacheable-request); self-propagation via stolen npm publishing tokens.
- Payload behavior: Harvests cloud credentials (AWS/Azure/GCP metadata and config files), npm and GitHub tokens, SSH private keys, and environment variables. Reads GitHub Actions runner memory to extract ephemeral OIDC/cloud credentials.
- C2 communication: Blockchain C2 via Ethereum smart contract (takedown-resistant dead-drop), with fallback domains
npm-cache.com,pypi-get.com,js-mirror.commasquerading as package registry infrastructure. - Persistence/Propagation: Worm-like — uses victim's own npm publish tokens to inject itself into every package the victim maintains, weaponizing trusted maintainer identities.
- Anti-analysis: Blockchain C2 cannot be sinkholed; registry-lookalike domains evade category-based filtering; execution occurs within legitimate build processes (CI runners), inheriting trusted context.
IOC Analysis
The indicator set breaks into three operational classes:
Network indicators (highest fidelity for blocking):
wel1.ruand subdomains (sdk.dl.,ext.dl.,pkg.dl.,net.dl.wel1.ru) — NUL1DROPPER payload staging. Block at DNS and egress proxy.npm-cache.com,pypi-get.com,js-mirror.com— ChainDrop registry-lookalike C2. These are designed to look benign in proxy logs; do not rely on category filtering, block explicitly.
File hashes (MD5/SHA1/SHA256):
- Use for retroactive hunts across endpoint, EDR, and CI artifact stores. The SHA256 values are your highest-fidelity indicators — MD5/SHA1 are provided for legacy tooling compatibility. Sweep
node_modulesdirectories, npm caches (~/.npm/_cacache), and CI build artifacts.
Behavioral indicators (not in the IOC list but critical):
- Node.js processes spawning shells, network connections from
node.exe/nodeto non-registry hosts, DNS tunneling patterns (long TXT/encoded subdomains), and GitHub Actions runners initiating unexpected outbound connections.
Operationalization: Feed domains to DNS firewall/proxy blocklists and SIEM watchlists; load hashes into EDR block lists (Defender, CrowdStrike, SentinelOne) and sweep historical telemetry for the past 90 days (compromise may predate pulse publication); alert on any node process network egress to non-allowlisted domains.
Detection Engineering
---
title: NUL1DROPPER / ChainDrop Payload C2 Communication
id: 7f3a1c2e-9b4d-4e8a-a1f5-2c6d8e9f0a11
status: experimental
description: Detects network connections to known NUL1DROPPER staging infrastructure and ChainDrop registry-lookalike C2 domains associated with npm supply chain credential theft campaigns.
author: Security Arsenal Threat Intelligence
date: 2026/08/10
references:
- https://opensourcemalware.com/blog/russian-ai-slopsquatting-npm-campaign
- https://unit42.paloaltonetworks.com/chaindrop-npm-worm-analysis/
tags:
- attack.command_and_control
- attack.t1071
- attack.t1105
logsource:
category: dns
product: windows
selection:
query|contains:
- 'wel1.ru'
- 'npm-cache.com'
- 'pypi-get.com'
- 'js-mirror.com'
condition: selection
falsepositives:
- Unlikely; these domains have no legitimate business purpose
level: critical
---
title: Node.js Process Spawning Suspicious Child Process or Network Egress
id: 4b8e2d1f-6a3c-4f7b-b2e9-1d5c7a8f3b22
status: experimental
description: Detects node.exe spawning shell interpreters or scripting engines — consistent with import-time malicious package execution (NUL1DROPPER require() execution, ChainDrop payload staging in build pipelines).
author: Security Arsenal Threat Intelligence
date: 2026/08/10
tags:
- attack.execution
- attack.t1059
- attack.t1195.002
logsource:
category: process_creation
product: windows
selection:
ParentImage|endswith:
- '\node.exe'
- '\npm.cmd'
- '\npm.exe'
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\pwsh.exe'
- '\wscript.exe'
- '\cscript.exe'
- '\curl.exe'
- '\certutil.exe'
filter_ci:
CommandLine|contains:
- 'npm install'
- 'npm ci'
- 'node-gyp'
condition: selection and not filter_ci
falsepositives:
- Legitimate build tooling in CI/CD; tune filters per environment
level: high
---
title: Sliver Implant Persistence via Registry Run Key
id: 9c1f5a3d-2e7b-4d6c-c3a8-5f9e2b7d4c33
status: experimental
description: Detects registry Run key persistence established by node/npm spawned processes — consistent with Sliver implant deployment following NUL1DROPPER execution.
author: Security Arsenal Threat Intelligence
date: 2026/08/10
tags:
- attack.persistence
- attack.t1060
- attack.t1547.001
logsource:
category: registry_set
product: windows
selection:
TargetObject|contains:
- '\CurrentVersion\Run'
- '\CurrentVersion\RunOnce'
Image|endswith:
- '\node.exe'
- '\powershell.exe'
- '\cmd.exe'
Details|contains:
- 'AppData'
- 'Temp'
condition: selection
falsepositives:
- Rare in developer workstations; legitimate Electron apps may register Run keys under Program Files instead
level: high
// Hunt: npm supply chain compromise — ChainDrop & NUL1DROPPER indicators and node egress behavior
let MaliciousDomains = dynamic(["wel1.ru", "sdk.dl.wel1.ru", "ext.dl.wel1.ru", "pkg.dl.wel1.ru", "net.dl.wel1.ru", "npm-cache.com", "pypi-get.com", "js-mirror.com"]);
let MaliciousHashes = dynamic([
"0fc30f82e1fa5e51a6c0c43f3ed7f13592ea731cb331e43a4d085df60a4db8b6",
"d30b4ea6f68456672f5abb35e9dcf7d54226372b66e9d60a7ee26b7a52568e74",
"54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668",
"9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc"
]);
let NetworkHits = DeviceNetworkEvents
| where TimeGenerated > ago(90d)
| where RemoteUrl has_any (MaliciousDomains)
| project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, RemoteUrl, RemoteIP, RemotePort;
let FileHits = DeviceFileEvents
| where TimeGenerated > ago(90d)
| where SHA256 in~ (MaliciousHashes) or MD5 in~ ("d382fdae4e2675b94419fdb115dbd397")
| project TimeGenerated, DeviceName, FolderPath, FileName, SHA256, InitiatingProcessFileName;
let NodeEgress = DeviceNetworkEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessFileName in~ ("node.exe", "node", "npm.exe", "npm.cmd")
| where RemoteUrl !has_any ("registry.npmjs.org", "registry.yarnpkg.com", "github.com", "nodejs.org") and isnotempty(RemoteUrl)
| summarize ConnectionCount = count(), DistinctDestinations = dcount(RemoteUrl), Destinations = make_set(RemoteUrl, 20) by DeviceName, InitiatingProcessCommandLine
| where DistinctDestinations > 3;
union NetworkHits, FileHits
| union (NodeEgress | project TimeGenerated = TimeGenerated, DeviceName, InitiatingProcessCommandLine, RemoteUrl = tostring(Destinations))
| sort by TimeGenerated desc
# Security Arsenal — npm Supply Chain Compromise Hunt (NUL1DROPPER / ChainDrop / Sliver)
# Run on developer workstations and CI build agents. Requires elevation for full coverage.
$MaliciousDomains = @("wel1.ru","sdk.dl.wel1.ru","ext.dl.wel1.ru","pkg.dl.wel1.ru","net.dl.wel1.ru","npm-cache.com","pypi-get.com","js-mirror.com")
$MaliciousHashes = @(
"0fc30f82e1fa5e51a6c0c43f3ed7f13592ea731cb331e43a4d085df60a4db8b6",
"d30b4ea6f68456672f5abb35e9dcf7d54226372b66e9d60a7ee26b7a52568e74",
"54dc7ea54a1317cca0e890a2770630cf7fa6c97813e0cb9d2caa93012b350668",
"9fc2570b7cef51c1b8df116d144d11ff4096357be7d2c4c6367cfc2509cf1bcc",
"d382fdae4e2675b94419fdb115dbd397"
)
Write-Host "[1] Active network connections to malicious C2/staging domains..." -ForegroundColor Cyan
Get-NetTCPConnection -State Established -ErrorAction SilentlyContinue | ForEach-Object {
$r = try { (Resolve-DnsName -Name $_.RemoteAddress -ErrorAction Stop -QuickTimeout).NameHost } catch { $null }
foreach ($d in $MaliciousDomains) {
if ($r -like "*$d*") { "[HIT] PID $($_.OwningProcess) -> $($_.RemoteAddress):$($_.RemotePort) ($r)" }
}
}
Write-Host "[2] DNS client cache check for malicious domains..." -ForegroundColor Cyan
Get-DnsClientCache -ErrorAction SilentlyContinue | Where-Object {
$e = $_.Entry; $MaliciousDomains | Where-Object { $e -like "*$_*" }
} | Select-Object Entry, Data | Format-Table -AutoSize
Write-Host "[3] Hash sweep of npm cache and node_modules directories..." -ForegroundColor Cyan
$SweepPaths = @("$env:APPDATA\npm-cache", "$env:LOCALAPPDATA\npm-cache", "$env:USERPROFILE\.npm")
foreach ($p in $SweepPaths) {
if (Test-Path $p) {
Get-ChildItem -Path $p -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object {
$h = (Get-FileHash -Path $_.FullName -Algorithm SHA256 -ErrorAction SilentlyContinue).Hash
if ($MaliciousHashes -contains $h) { "[HIT] Malicious file: $($_.FullName) SHA256=$h" }
}
}
}
Write-Host "[4] Registry Run key persistence check (Sliver-style)..." -ForegroundColor Cyan
$RunKeys = @("HKCU:\Software\Microsoft\Windows\CurrentVersion\Run","HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce","HKLM:\Software\Microsoft\Windows\CurrentVersion\Run")
foreach ($k in $RunKeys) {
if (Test-Path $k) {
(Get-ItemProperty $k).PSObject.Properties | Where-Object {
$_.Value -match "AppData|Temp|node|npm" -and $_.Name -notmatch "PSPath|PSParent"
} | ForEach-Object { "[SUSPICIOUS] $k :: $($_.Name) = $($_.Value)" }
}
}
Write-Host "[5] Scheduled tasks spawning node/npm or executing from user-writable paths..." -ForegroundColor Cyan
Get-ScheduledTask -ErrorAction SilentlyContinue | ForEach-Object {
$actions = $_.Actions | ForEach-Object { "$($_.Execute) $($_.Arguments)" }
if ($actions -match "node|npm|AppData|Temp") { "[REVIEW] Task: $($_.TaskName) :: $actions" }
}
Write-Host "[6] Credential exposure check — npm/GitHub tokens in environment and config files..." -ForegroundColor Cyan
$tokenFiles = @("$env:USERPROFILE\.npmrc", "$env:USERPROFILE\.git-credentials", "$env:USERPROFILE\.ssh")
foreach ($f in $tokenFiles) { if (Test-Path $f) { "[PRESENT] $f exists — rotate associated credentials if host shows other hits" } }
Write-Host "Hunt complete. Any [HIT] findings require immediate isolation and credential rotation." -ForegroundColor Yellow
Response Priorities
Immediate (0–4 hours)
- Block all IOC domains (
wel1.ru+ subdomains,npm-cache.com,pypi-get.com,js-mirror.com) at DNS resolver, secure web gateway, and egress firewall. Add file hashes to EDR blocklists. - Audit your dependency tree: run
npm audit, diffpackage-lock.jsonagainst known-good baselines, and check for the 400+ ChainDrop-affected packages and any unfamiliar recently-added dependencies. Pin dependencies to known-good versions with integrity hashes. - Hunt CI/CD build agents and developer workstations using the detections above — prioritize machines that ran
npm installornpm ciin the past 14 days. - Isolate any host showing C2 connections or hash matches; do not simply re-image before capturing memory (ChainDrop extracts credentials from runner memory — forensic value is high).
24 Hours
- Credential rotation is mandatory, not optional. These campaigns specifically target npm publishing tokens, GitHub PATs, SSH keys, cloud credentials (AWS/Azure/GCP), and GitHub Actions OIDC tokens. Rotate every credential that existed on any potentially exposed host — assume all secrets in environment variables,
.npmrc,.git-credentials,~/.ssh/, and cloud config files are compromised. - Audit npm/GitHub package registries you publish to: verify no unauthorized package versions were published using maintainer tokens (ChainDrop's self-propagation vector). Check publish logs for anomalous version bumps.
- Review GitHub Actions workflow run logs for unexpected outbound network activity and enable OIDC token audience restrictions.
- Monitor dark web credential markets and IAB listings for your corporate domains — stolen developer credentials from these campaigns typically surface for sale within days.
1 Week
- Enforce private registry proxying (Artifactory/Nexus/GitHub Packages) with allowlist-based package admission; block direct pulls from public npm in CI/CD.
- Implement scriptless install policies in CI (
npm ci --ignore-scripts) and sandboxed dependency installation; note NUL1DROPPER executes on import, so pair this with network egress controls on build runners. - Deploy egress filtering for CI runners: allowlist only
registry.npmjs.organd required artifact hosts; alert on all othernodeegress. - Adopt Sigstore/npm provenance attestation verification for critical dependencies, and enable two-factor enforcement on all npm/GitHub maintainer accounts with granular, expiring automation tokens replacing classic PATs.
- Establish DNS tunneling detection (long/encoded subdomain analysis) as a standing SOC analytic given NUL1DROPPER's fallback exfiltration channel.
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.