Flooding Dropper — WEL1DROPPER npm Slopsquatting Campaign (August 2026)

~850 AI-slopsquatted npm packages · no lifecycle hook, executes on require() · cross-platform dropper · DNS TXT payload fallback channel · Sliver on Linux, reflective loader on Windows, LaunchAgent on macOS
Threat
Flooding Dropper / WEL1DROPPER
Severity
ACTIVE CAMPAIGN
Type
Software supply chain / RAT + infostealer
Access
Developer imports a malicious npm dependency
Tracking
sonatype-2026-005660 · CWE-506 · CVSS 8.7
Version
v1.0 · 2026-08-08
Author
HuntPack
Confidence
High (2 primary analyses, 2 corroborating)
01

Executive Summary

Between 2026-08-04 and 2026-08-07 a single threat actor published roughly 800 to 850 malicious packages to the npm registry. OpenSourceMalware tracks the downloader as WEL1DROPPER; Sonatype Research Labs tracks the surrounding campaign as Flooding Dropper under sonatype-2026-005660 (846 components at time of publication, CWE-506, CVSS 8.7). The package names appear to be AI-hallucinated typo-squats — "AI slopsquatting" — the kind of plausible-but-nonexistent dependency name a coding assistant invents and a developer then installs.

The control most teams rely on does not apply here. These packages carry no preinstall or postinstall lifecycle hook. The README instructs the developer to load the library with require("<package>"), and that single import detonates the chain. npm config set ignore-scripts true, install-script sandboxes, and install-time scanners all miss this by design. Execution happens whenever the application imports the dependency — during development, testing, CI, or production start-up.

What the chain does

  1. Import triggers stage 1. index.js presents a harmless fake mobile SDK (init(), version(), configure()), then ends with try { require("./_helpers"); } catch (_) {}. _helpers.js calls its own run() immediately.
  2. Host fingerprint. WEL1DROPPER reads the OS and CPU architecture and maps the victim to one of four payload paths: /pkg/package (Linux x64), /pkg/package-arm64 (Linux ARM64), /pkg/loader_mac (macOS universal), /pkg/package.exe (Windows).
  3. Primary delivery. It shuffles three Cloudflare Workers hosts and issues an IPv4-forced HTTPS GET with a 15-second timeout and the User-Agent node-fetch/2.6. A response is accepted only on HTTP 200 with more than 1,000 bytes. There is no signature check, no pinning, no expected hash — whatever the server returns is treated as an executable.
  4. DNS fallback — the distinctive signal. If all three HTTPS hosts fail, it queries a TXT record at c.<domain>, parses the answer as a chunk count between 1 and 2,000, then walks numbered TXT records (0.<domain>, 1.<domain>, …), joins the strings and Base64-decodes them into a binary. This is not DNS service discovery — it is a second full payload channel that survives an HTTPS blocklist.
  5. Disk and detached launch. An 8-hex-character random ID names the file: /var/tmp/.cache_<8 hex> on Unix (chmod +x, launched via /bin/sh -c "… &") or %TEMP%\dotnet_diag_<8 hex>.exe on Windows (launched via cmd.exe /c start /b). Both are detached with stdio ignored. A rate-limit marker (/tmp/.analytics_state, %TEMP%\analytics_state) suppresses re-runs for 21,760 seconds.
  6. Stage 2, per platform. Windows: patches ETW and AMSI, checks for debuggers/VMs/sandboxes/security products, copies itself under %AppData%, persists via a Registry Run key and a scheduled task, then downloads an encrypted payload from /pkg/update_win.exe, decrypts it and executes it reflectively in memory. macOS: anti-debug and VMware checks, memory-size sandbox heuristic, fetches /pkg/beacon_mac.bin through five XOR-hidden proxy hosts, writes under ~/.local/share/runtime, installs ~/Library/LaunchAgents/com.apple.windowserver.helper.plist. Linux: statically linked, non-PIE, UPX 3.96-packed ELF that pulls further binaries from a Cloudflare Worker, reportedly ending in a Sliver implant.

Why this pack scopes developer workstations and CI runners equally

Nothing in the chain needs interactive use. A build agent that runs npm ci and then starts the application imports the dependency exactly as a laptop does, and a build agent typically holds better credentials: npm publish tokens, GitHub tokens, cloud roles, signing keys, deployment secrets. Both primary sources say the same thing — treat a confirmed import as host compromise, rotate every credential reachable from that host, and rebuild.

Two anti-signature properties that shape the detection strategy

  • Payload polymorphism. Sonatype observed the packages carrying "slightly modified payloads" — different URL function and variable names producing identical behavior. File hashes are single-sample facts, not campaign-wide ones.
  • Account and name churn. Each disposable npm account publishes only a handful of packages. Early names interpolate bigops and bnpl with a 35.x.y version pattern, but Sonatype already sees the convention drifting. A name blocklist is yesterday's control.

Consequence: the durable detections in this pack are the behavioral ones — the numbered-TXT-record channel (Q3, Q4), a Node process spawning a shell or downloader (Q6), a Node-parented write-then-execute in a temp directory (Q12), and the platform persistence writes (Q9, Q10, Q11). The atomic indicators in §4 and §10 are for retro-hunting the current wave, not for standing coverage.

Attribution

OpenSourceMalware assesses probable Russian origin from the wel1[.]ru TXT-record infrastructure and the presence of Russian financial-institution domains (tcsbank[.]ru, cloudpayments[.]ru) inside the macOS payload, and assesses the campaign as a likely evolution of the Moika dependency-confusion campaign (250+ npm packages, April/May 2026) on shared tradecraft: the oob string in file and server names, focus on Russian financial institutions and mobile payments, fake-telemetry camouflage, and similar kill switches. Treat attribution as an assessment, not a fact — it changes nothing about the detections below.

02

Source Review & Web Hunter Notes

Six sources were fetched and saved verbatim to FloodingDropper-WEL1DROPPER-npm-Hunt-sources/. Every atomic indicator shipped in §4 and §10 traces back to one of those snapshots. Nothing was carried forward from memory or inference.

#TierSourceKey findingCarry forward
01PRIMARYOpenSourceMalware — "Russian AI Slopsquatting Publishes 700+ Malicious NPM Packages" (2026-08-06)Full reverse-engineering writeup. Source of every host, hash, dropped-file path, persistence artifact and the DNS TXT chunk protocol. Names the active loader as _helpers.js and lib/telemetry.js as an unimported 80 KB decoy.Yes — all indicators
03PRIMARYSonatype Research Labs — "'Flooding Dropper' Campaign Hits npm With Nearly 850 Malicious Packages" (2026-08-05)Campaign scale (846 components), sonatype-2026-005660, CWE-506 / CVSS 8.7, package-name and 35.x.y version correlation, payload polymorphism, disposable-account distribution. Sole source for the Windows stage-2 behavior: ETW/AMSI patching, AppData self-copy, Run key + scheduled task, encrypted /pkg/update_win.exe executed reflectively.Yes — Windows stage 2, campaign scoping
02TIER 2The Hacker News — "Nearly 800 Malicious npm Packages Deliver Cross-Platform RAT and Infostealer" (2026-08-07)Independent corroboration of the Cloudflare Workers hosts, the four DNS payload domains, the WEL1DROPPER name, and the Sonatype Windows findings. Confirms the Moika lineage assessment.Yes — corroboration
05TIER 2DevOps.com — "'Flooding Dropper' Is Hitting npm With a Tidal Wave of Malicious Packages" (2026-08-07)Confirms the ~850 count, the bigops-backend / bigops-api / dolyame-boxy-desktop-bnpl-card-gallery names, Jorge Cardona's attribution of the wider campaign, and npm's 96.6% share of Q2 2026 malicious-package volume.Partial — package names, scale
06TIER 2CyberPress — "Russian AI Slopsquatting Campaign Floods npm With Malicious Packages Targeting Developers"Restates the OSM findings; notes the campaign growing beyond 1,000 packages and the shared "oob" infrastructure naming with Moika.Partial — scale trend only
04TIER 3CyberTechWorld aggregator repost of the THN articleTruncated syndicated copy. Contains no independent technical content and no indicators.No — snapshot retained for completeness, not cited

Prompt-injection screening

All six snapshots were screened for text addressing an automated reader — instructions to run commands, to fetch a further URL "for the real IOCs", to skip a validation step, or claims of authorization. No injection attempt was found. The only pattern-matches were the strings "AI & LLM Governance" in Sonatype's site navigation menu, which is product marketing, not instruction. No source was dropped on injection grounds.

Where the sources disagree, and what this pack shipped

  • Which file holds the live downloader. Secondary coverage says lib/telemetry.js "contains the same downloader logic". The primary analysis is more precise and this pack follows it: lib/telemetry.js is a ~80 KB fake-telemetry decoy that the entry point never imports and that carries no hardcoded infrastructure; the live path is the much smaller _helpers.js. Both files are hashed in §10, but the hunt logic targets _helpers.js behavior.
  • Stage-1 vs stage-3 payload names. update_win.exe and beacon_mac.bin are later-stage paths, not what stage 1 fetches. Stage 1 requests /pkg/package, /pkg/package-arm64, /pkg/loader_mac, /pkg/package.exe and renames them to .cache_<hex> / dotnet_diag_<hex>.exe on disk. Queries Q7 and Q8 hunt the on-disk names, which is what endpoint telemetry actually records.
  • Example package name. The primary analysis dissects checkout-mobile-bnpl@35.6.9; Sonatype's entry point was bigops-backend. Both are the same campaign — Q13 covers both plus the two additional Sonatype names.
  • Sliver is reported, not confirmed. OpenSourceMalware states explicitly that it has not confirmed the Linux final stage is Sliver; THN reports it as fact citing Sonatype. This pack treats Sliver as a strong lead and hunts the UPX-packed-ELF staging behavior (Q8, Q10) rather than Sliver protocol signatures.
  • Package count. 788 (OSM tracking list), 846 (Sonatype components), "nearly 800" (THN), "nearly 850" (DevOps.com), "beyond 1,000" (CyberPress trend note). The pack cites the range rather than one number.

Known gaps in the intel

  • The final Windows and macOS beacons were unavailable for analysis in both primary sources. There is no confirmed C2 protocol, no beacon interval, no confirmed exfiltration destination. No exfil indicator is shipped, because none exists in any source.
  • The port used by the five package-proxy hosts could not be recovered from strings. The request template is https://%s:%d%s, so the connection may carry an explicit non-443 port. No port IOC is shipped.
  • The Linux ELF is UPX-packed with its section header table removed; its inner behavior was not statically recoverable. The visible /proc/self/exe string is the UPX loader reading its own image and is explicitly not an indicator.
  • The macOS payload's reaction to each detected analysis artifact (exit, sleep, suppress network) was not recovered.
  • nexus[.]tcsbank[.]ru, repo-linux[.]tcsbank[.]ru and alertmanager[.]cloudpayments[.]ru are XOR-hidden in the macOS binary behind a https://%s/health formatter. The primary source states static analysis does not establish whether they are attacker-owned, compromised, or decoys. They ship as no_action investigation leads only — never as detect or block rows.
  • The full package list (788 names) lives on OpenSourceMalware and GitHub and was not enumerated into this pack. Use the Sonatype sonatype-2026-005660 advisory or the OSM list as the authoritative name inventory; §7 gives the lockfile sweep.
03

Hunt Brief & Attack Chain

Each row is a step an analyst can prove or disprove from telemetry. The hunt-angle column names the query that covers it.

#StepDetailTelemetryHunt angle
1Slopsquat package enters the dependency graphAI-hallucinated name installed by a developer or resolved by a build. Early names interpolate bigops / bnpl, versions in the 35.x.y range; convention is already drifting.ProcessRollup2 command lines of npm/npx/yarn/pnpm/bun; lockfiles and SBOMs (non-EDR)Q13 + §7 lockfile sweep
2require() detonates stage 1No lifecycle hook. index.js tail-loads _helpers.js, which self-invokes run(). Nothing distinguishes this from a normal module load at the process level.None directly — GAPInferred from steps 3–6; see §15
3Kill-switch / rate-limit checkReads env vars and a marker file (/tmp/.analytics_state, %TEMP%\analytics_state) to skip re-running within 21,760 s.FileOpenInfo, NewExecutableWritten (low fidelity for a plain marker file)§7 filesystem sweep
4OS + architecture fingerprintSelects one of four payload paths. Pure in-process logic in Node.None directly — GAP
5Primary payload fetch over HTTPSShuffles three Cloudflare Workers hosts, IPv4-forced GET, 15 s timeout, UA node-fetch/2.6, accepts only HTTP 200 with >1,000 bytes. No hash or signature verification.DnsRequest, NetworkConnectIP4; proxy logsQ2 (known hosts), Q5 (any Node → *.workers.dev)
6DNS TXT fallback payload channelTXT c.<domain> returns a chunk count 1–2,000, then TXT 0.<domain>TXT n-1.<domain>; strings are joined and Base64-decoded to a binary. Per-platform domains under dl.wel1[.]ru. The macOS loader adds a session/host label: c.<id>.dl.wel1[.]ru.DnsRequest, SuspiciousDnsRequest; resolver logsQ1 (domain), Q3 (chunk-label shape, any domain), Q4 (volume anomaly)
7Payload written to a temp directory8-hex random ID. Unix: /var/tmp/.cache_<8 hex>, chmod +x. Windows: %TEMP%\dotnet_diag_<8 hex>.exe. Names chosen to look boring among real temp files.PeFileWritten, NewExecutableWritten, ELFFileWrittenQ7, Q8, Q12
8Detached executionUnix: /bin/sh -c "/var/tmp/.cache_<id> &". Windows: cmd.exe /c start /b %TEMP%\dotnet_diag_<id>.exe. Detached with stdio ignored, so killing the Node parent does not stop it.ProcessRollup2, SyntheticProcessRollup2Q6, Q7, Q8
9aWindows stage 2 — evasionPatches ETW and AMSI functions; checks for debuggers, VMs, sandboxes and security products.Falcon in-product AMSI/ETW-tamper detections; not a discrete queryable event in the CQL data model — PARTIAL GAPQ14 (proxy: Node-descended LOLBins) + §9 IOA-3; see §15
9bWindows stage 2 — persistenceSelf-copy under %AppData%, then both a Registry Run key value and a scheduled task.AsepValueUpdate, RegGenericValueUpdate, ScheduledTaskRegisteredQ9, Q10
9cWindows stage 3 — reflective loadDownloads encrypted /pkg/update_win.exe, decrypts and executes it in memory. No conventional write-and-launch from disk.Memory-scan / behavioral only — GAP§9 IOA-4; see §15
10amacOS stage 2 — evasionLooks for lldb, debugserver, dtrace, frida, Wireshark and VMware Fusion artifacts; imports ptrace/sysctlbyname; queries hw.memsize as a low-memory sandbox heuristic.ProcessRollup2 (weak); file-existence probes are not loggedPartial — §15
10bmacOS stage 2 — persistenceWrites ~/.local/share/runtime, ~/.local/share/runtime/.lock, ~/.local/share/runtime/com.apple.runtime, plus ~/Library/LaunchAgents/com.apple.windowserver.helper.plist, then launchctl load -w. The plist runs at login with KeepAlive and a 60 s throttle, stdout/stderr to /dev/null. Neither com.apple.runtime nor com.apple.windowserver.helper is a real Apple component in those locations.CriticalFileModified, NewExecutableWritten, ProcessRollup2Q11
10cmacOS stage 3 — beacon fetchRetrieves /pkg/beacon_mac.bin from five XOR-obfuscated (key 0x9c) package-proxy.* Workers hosts, possibly on an explicit non-443 port; DNS TXT fallback via dl.wel1[.]ru with a session label. Also probes http://127.0.0.1:4444/health — loopback, so a check for another local component, not external C2.DnsRequest, NetworkConnectIP4Q2, Q3
11Linux stage 2/3 — Sliver stagingStatically linked non-PIE x86-64 ELF, UPX 3.96, section header table stripped. Pulls further binaries from oob-worker.cf99-9b3.workers[.]dev, reportedly ending in a Sliver implant (unconfirmed by the primary source).ELFFileWritten, ProcessRollup2, NetworkConnectIP4Q8, Q10
12Credential exposureBoth primary sources: treat a confirmed import as host compromise. npm tokens, GitHub tokens, cloud credentials, signing keys and deployment secrets reachable from the host are exposed.Identity / cloud audit logs (out of EDR scope)§14 containment

Hunt hypotheses, ordered by fidelity

IDHypothesisMITREEvents requiredExpected FP sourceConf
H1A host in scope resolved a name under wel1[.]ru. There is no legitimate enterprise reason for this.T1071.004, T1105DnsRequestAnalyst research browsing; sandbox detonation VMsHIGH
H2A host resolved one of the eight known Cloudflare Workers staging or proxy hosts.T1102, T1105DnsRequestAnalyst research; the hosts may already be sinkholedHIGH
H3A Node process issued TXT lookups whose leftmost label is c or a small integer — the chunked-payload protocol — against any domain, not only wel1[.]ru. This survives infrastructure rotation.T1071.004, T1132.001DnsRequestSome CDN and reverse-DNS-ish schemes use numeric leftmost labelsHIGH
H4A file named dotnet_diag_<8 hex>.exe, update_win.exe, .cache_<8 hex> or beacon_mac.bin was written or executed. These names exist only in this campaign.T1105, T1036.005PeFileWritten, NewExecutableWritten, ELFFileWritten, ProcessRollup2None expectedHIGH
H5A macOS host has a LaunchAgent or ~/.local/share/runtime artifact impersonating an Apple component.T1543.001, T1036.005CriticalFileModified, ProcessRollup2Genuine third-party agents under ~/Library/LaunchAgentsHIGH
H6A Node process made an unusual volume of TXT lookups in a short window — the chunk walk is 1 to 2,000 sequential queries.T1071.004DnsRequestNode-based DNS tooling; DKIM/SPF/ACME validators; service-discovery librariesMEDIUM
H7A Node process wrote a new executable into a temp directory and something executed it shortly after.T1105, T1059PeFileWritten, ELFFileWritten, ProcessRollup2node-gyp, prebuild-install, esbuild/swc/sharp binary downloads, Electron builders, Playwright/Puppeteer browser installsMEDIUM
H8A Registry Run value or a scheduled task points at an executable in %TEMP% or %AppData% on a host with developer tooling.T1547.001, T1053.005AsepValueUpdate, RegGenericValueUpdate, ScheduledTaskRegisteredSquirrel/Electron auto-updaters, Teams, Slack, Zoom, VS Code updater, Dropbox — all legitimately persist from AppDataMEDIUM
H9A Node process spawned a shell, a downloader or a persistence utility.T1059.003, T1059.004, T1105ProcessRollup2Very common in build tooling — high on developer and CI hostsMEDIUM
H10A Node process resolved a *.workers.dev host not on the known list — a candidate for the next staging wave.T1102DnsRequestLegitimate SaaS and APIs hosted on Cloudflare WorkersLOW-MED
H11A known Flooding Dropper package name appears in an npm/node command line on a workstation or runner.T1195.002ProcessRollup2Substring collisions with legitimate bnpl-related packagesMEDIUM
04

Consolidated IOC Table

Read the Action column before importing anything. Payload polymorphism means the seven hashes are single-sample facts, not campaign coverage. The three tcsbank / cloudpayments hosts are XOR-hidden strings whose ownership the primary source explicitly could not establish — they are investigation leads, and blocking them risks breaking traffic to legitimate Russian financial services. Every value below appears verbatim in a saved snapshot; see §10 for the trace.

TypeValueConfActionContextExpiry
domainwel1[.]ruHIGHBlock + detectCampaign apex. Hosts the TXT-record payload channel. No legitimate use.2027-02-08
domaindl[.]wel1[.]ruHIGHBlock + detectSecond DNS delivery domain found in the macOS loader; queried with a session/host label.2027-02-08
domainsdk[.]dl[.]wel1[.]ruHIGHBlock + detectDNS TXT payload domain — Linux x64.2027-02-08
domainext[.]dl[.]wel1[.]ruHIGHBlock + detectDNS TXT payload domain — Linux ARM64.2027-02-08
domainpkg[.]dl[.]wel1[.]ruHIGHBlock + detectDNS TXT payload domain — macOS.2027-02-08
domainnet[.]dl[.]wel1[.]ruHIGHBlock + detectDNS TXT payload domain — Windows.2027-02-08
domainoob-worker[.]cf103-070[.]workers[.]devHIGHBlock + detectStage-1 HTTPS staging host (1 of 3, shuffled).2027-02-08
domainoob-worker[.]cf102-baf[.]workers[.]devHIGHBlock + detectStage-1 HTTPS staging host (2 of 3).2027-02-08
domainoob-worker[.]cf99-9b3[.]workers[.]devHIGHBlock + detectStage-1 staging host (3 of 3), and the host the Linux ELF pulls its next binaries from.2027-02-08
domainpackage-proxy[.]cf5oobworker[.]workers[.]devHIGHBlock + detectmacOS stage-3 beacon proxy, XOR key 0x9c.2027-02-08
domainpackage-proxy[.]cf6oobworker[.]workers[.]devHIGHBlock + detectmacOS stage-3 beacon proxy.2027-02-08
domainpackage-proxy[.]cf7oobworker[.]workers[.]devHIGHBlock + detectmacOS stage-3 beacon proxy.2027-02-08
domainpackage-proxy[.]cf8oobworker[.]workers[.]devHIGHBlock + detectmacOS stage-3 beacon proxy.2027-02-08
domainpackage-proxy[.]cf11oobworker[.]workers[.]devHIGHBlock + detectmacOS stage-3 beacon proxy.2027-02-08
domainnexus[.]tcsbank[.]ruLEADMonitor onlyXOR-hidden behind a https://%s/health formatter in the macOS binary. Ownership NOT established — may be legitimate, compromised, or decoy. Do not block.Re-assess 2026-09-08
domainrepo-linux[.]tcsbank[.]ruLEADMonitor onlyAs above. Investigation lead, not an indicator.Re-assess 2026-09-08
domainalertmanager[.]cloudpayments[.]ruLEADMonitor onlyAs above. Investigation lead, not an indicator.Re-assess 2026-09-08
sha2567e486657f30594afda379b97030252a09a19fe8055e25c9e371544f59bd8e9e3HIGHPreventLinux x86-64 second stage — statically linked, non-PIE, UPX 3.96-packed ELF.2027-02-08
sha256c214746c74cae8ece8bdaf69aa05da4db6ce013f9e77452d1eed1a002fd9ba00HIGHPreventmacOS universal Mach-O second stage (x86-64 + ARM64) — the loader that installs the LaunchAgent.2027-02-08
sha25694ef6b1c4a9d31f78f446d053048bcef34fd88f4376a1a46f7f777a9e9c83a29MEDDetect_helpers.js — the live stage-1 downloader. Single sample; payloads are polymorphic across packages.2027-02-08
sha256a3e2ffb440b779d30da3ff282affd649731088e8570df7b1aa72742d995b782cMEDDetectlib/telemetry.js — the ~80 KB fake-telemetry decoy. Not imported by the entry point.2027-02-08
sha256b74c5675725911c62091bdf40714df760cc2af7a88360d21065f4e1c878aa8f0MEDDetectindex.js — fake SDK facade with the tail require("./_helpers").2027-02-08
sha256e2650e9aa2f924433ba422857b22ee7c5996b5ad306f3f903283f6a13e248935MEDDetectpackage.json of the analyzed sample. Note it contains no lifecycle hook.2027-02-08
sha2560fc30f82e1fa5e51a6c0c43f3ed7f13592ea731cb331e43a4d085df60a4db8b6MEDDetectREADME.md — the file that social-engineers the developer into calling require().2027-02-08
filenamedotnet_diag_<8 hex>.exeHIGHHunt (Q7)Windows staged payload in %TEMP%. Pattern, not a fixed name — hunt, do not import.Durable
filename.cache_<8 hex>HIGHHunt (Q8)Unix staged payload in /var/tmp, chmod +x then detached-launched.Durable
filenameupdate_win.exeHIGHHunt (Q7)Windows stage-3 encrypted payload path (/pkg/update_win.exe), decrypted and reflectively loaded — may never touch disk.Durable
filenamebeacon_mac.binHIGHHunt (Q8)macOS stage-3 beacon path (/pkg/beacon_mac.bin).Durable
path~/Library/LaunchAgents/com.apple.windowserver.helper.plistHIGHHunt (Q11)macOS persistence. Not a legitimate Apple artifact in this location.Durable
path~/.local/share/runtime/com.apple.runtime, ~/.local/share/runtime/.lockHIGHHunt (Q11)macOS payload install directory and lock file, masquerading as an Apple component.Durable
path/tmp/.analytics_state, %TEMP%\analytics_stateHIGHHunt (§7)Rate-limit marker, not analytics. Presence indicates stage 1 ran on this host.Durable
uanode-fetch/2.6MEDProxy huntStage-1 User-Agent. Common in benign Node code — only useful correlated with a staging host or an unusual destination.Durable
packagecheckout-mobile-bnpl (@35.6.9), bigops-backend, bigops-api, dolyame-boxy-desktop-bnpl-card-galleryHIGHPurge + hunt (Q13)Named samples out of ~800–850. Use the OSM list / sonatype-2026-005660 for the full inventory.2026-11-08
patternnpm names interpolating bigops / bnpl, versions 35.x.yMEDTriage signalSonatype correlation heuristic. Explicitly labelled non-durable by its author — the convention is already drifting.Re-assess weekly
loopbackhttp://127.0.0.1:4444/healthCONTEXTDo not blockThe only literal IP in either native sample. Loopback, so a probe for another local component — not external C2. Listed to stop it being mis-imported as an indicator.
05

Affected Surface & Telemetry Matrix

This campaign hits build agents as hard as laptops. Scope both, and scope them the same way. Anything that runs node against a public registry is in the blast radius, including container build stages and ephemeral runners whose telemetry may not reach the sensor at all.

SurfaceExposureRequired telemetryPriorityGap risk
Windows developer workstationsFull chain: dropper → ETW/AMSI patch → AppData self-copy → Run key + scheduled task → reflective stage 3. Highest-value credentials sit here.ProcessRollup2, PeFileWritten, NewExecutableWritten, DnsRequest, NetworkConnectIP4, AsepValueUpdate, RegGenericValueUpdate, ScheduledTaskRegisteredP1Low — full sensor coverage typical
Windows / Linux CI runners & build agentsSame chain, plus the best credential set in the estate: npm publish tokens, GitHub tokens, cloud roles, signing keys. Ephemeral runners may execute the whole chain and be destroyed before anything is reviewed.All of the above, plus registry proxy logs and runner job logsP1High — sensors are frequently not installed on ephemeral runners; container build stages are often invisible
macOS developer workstationsUniversal Mach-O covering Intel and Apple Silicon. LaunchAgent persistence, anti-analysis, a third beacon stage.ProcessRollup2, CriticalFileModified, NewExecutableWritten, DnsRequest, NetworkConnectIP4P1Medium — verify DNS telemetry is actually flowing from macOS sensors before trusting Q1/Q3 on this platform
Linux developer workstations & serversx64 and ARM64 payloads. UPX-packed ELF staging toward a reported Sliver implant.ProcessRollup2, ELFFileWritten, DnsRequest, NetworkConnectIP4P1Medium — DnsRequest coverage on Linux is sensor-version dependent; confirm before relying on Q1/Q3/Q4
Containers built from a poisoned lockfileThe dependency ships inside the image. Every runtime instance imports it. Nothing about the running container looks unusual at pull time.Falcon Container sensor; image SBOM diff; registry scanP2High — build-stage execution frequently has no EDR at all
Internal npm mirror / proxy cacheHolds retained copies of pulled packages. Removing the dependency from a project does not evict the cache; the next resolve re-serves it.Nexus / Artifactory / Verdaccio access and quarantine logsP2Medium — coverage depends on whether a proxy is mandatory
Recursive DNS resolversThe TXT chunk channel is visible here even when the endpoint sensor is not. On a fleet without complete endpoint DNS telemetry this is the single best vantage point.Resolver query logs with QTYPE and client IPP2Medium — many estates log NXDOMAIN only, or drop QTYPE
Egress proxySees the node-fetch/2.6 User-Agent and the Workers hosts. Useful when the endpoint fetch predates sensor deployment.Proxy access logs with UA and full hostP3High on hosts that egress without a proxy
AI coding assistants in the SDLCThe delivery mechanism. Slopsquatting exists because assistants hallucinate plausible package names that developers then install unchecked.Assistant audit logs where available; code-review recordsP3High — usually no telemetry at all

Scope your hunt before you run it, not after. Every query below is written fleet-wide on purpose. If your estate is large, add a host-group filter as the second stage rather than narrowing the logic — the point of Q3, Q6, Q11 and Q12 is that they fire on hosts you did not expect to have Node on them.

06

MITRE ATT&CK Mapping

TacticTechniqueIDObserved behaviorCoverage
Initial AccessSupply Chain Compromise: Compromise Software Dependencies and Development ToolsT1195.001~800–850 slopsquatted npm packages published across many disposable accounts.Q13, §7
Resource DevelopmentStage Capabilities: Upload MalwareT1608.001Payloads staged on three rotating Cloudflare Workers hosts plus five macOS beacon proxies.Q2, Q5
ExecutionUser Execution: Malicious FileT1204.002README instructs the developer to require() the package; the import self-invokes run().Q13 (proxy)
ExecutionCommand and Scripting Interpreter: Windows Command ShellT1059.003cmd.exe /c start /b %TEMP%\dotnet_diag_<id>.exeQ6, Q7
ExecutionCommand and Scripting Interpreter: Unix ShellT1059.004/bin/sh -c "/var/tmp/.cache_<id> &"Q6, Q8
ExecutionCommand and Scripting Interpreter: JavaScriptT1059.007_helpers.js executes the full downloader inside the Node runtime.Q6, Q12
PersistenceBoot or Logon Autostart Execution: Registry Run Keys / Startup FolderT1547.001Windows stage 2 writes a Run key value pointing at its AppData self-copy.Q9
PersistenceScheduled Task/Job: Scheduled TaskT1053.005Windows stage 2 registers a scheduled task in addition to the Run key.Q10
PersistenceCreate or Modify System Process: Launch AgentT1543.001~/Library/LaunchAgents/com.apple.windowserver.helper.plist, loaded with launchctl load -w, KeepAlive with a 60 s throttle.Q11
Defense EvasionImpair Defenses: Disable or Modify ToolsT1562.001Windows stage 2 patches AMSI functions to blind script scanning.PARTIAL Q14, IOA-3
Defense EvasionImpair Defenses: Indicator BlockingT1562.006Windows stage 2 patches ETW to suppress event generation.PARTIAL Q14, IOA-3
Defense EvasionReflective Code LoadingT1620Encrypted /pkg/update_win.exe decrypted and executed in memory, never written as a conventional executable.GAP IOA-4
Defense EvasionObfuscated Files or Information: Software PackingT1027.002Linux ELF packed with UPX 3.96, statically linked, section header table stripped.Q8, Q10
Defense EvasionObfuscated Files or Information: Encrypted/Encoded FileT1027.013macOS binary hides its infrastructure strings with single-byte XOR, key 0x9c.Static-analysis only
Defense EvasionMasquerading: Match Legitimate Name or LocationT1036.005dotnet_diag_<hex>.exe, .cache_<hex>, com.apple.runtime, com.apple.windowserver.helper, and the fake-telemetry SDK facade.Q7, Q8, Q11
Defense EvasionVirtualization/Sandbox Evasion: System ChecksT1497.001VMware Fusion and VMware Tools artifact checks; hw.memsize queried as a low-memory sandbox heuristic; Windows sandbox and security-product checks.PARTIAL
Defense EvasionDebugger EvasionT1622macOS payload probes for lldb, debugserver, dtrace, frida, Wireshark; imports ptrace and sysctlbyname.PARTIAL
DiscoverySystem Information DiscoveryT1082OS and CPU-architecture fingerprint selects one of four payload paths.GAP — in-process
Command and ControlApplication Layer Protocol: DNST1071.004Numbered TXT records under dl.wel1[.]ru carry the payload; c.<domain> returns the chunk count.Q1, Q3, Q4
Command and ControlWeb Service: Bidirectional CommunicationT1102.002Cloudflare Workers used as the primary staging and beacon-proxy layer.Q2, Q5
Command and ControlIngress Tool TransferT1105Stage-2 binaries fetched over HTTPS or reassembled from DNS, written to temp and executed.Q7, Q8, Q12
Command and ControlData Encoding: Standard EncodingT1132.001TXT chunk strings joined and Base64-decoded into an executable buffer.Q3, Q4
Command and ControlFallback ChannelsT1008DNS TXT delivery activates only when all three HTTPS staging hosts fail — surviving an HTTPS-only blocklist is the design goal.Q1, Q3, Q4
Credential AccessUnsecured Credentials: Credentials In FilesT1552.001Assessed, not observed: the final beacon was unavailable, but both primary sources direct rotation of npm/GitHub/cloud/signing credentials reachable from the host.§14

Reading the coverage column honestly. Four rows are marked GAP or PARTIAL. T1082 and step 2 of the chain (the require() itself) happen entirely inside the Node process and produce no distinct telemetry — they are covered only by inference from what happens next. T1620 reflective loading is the most consequential gap: if Windows stage 3 never touches disk, no file-write query will ever see it. That is what §9 IOA-4 and Falcon's in-product memory scanning are for, and it is why §14 treats a confirmed stage-1 execution as compromise regardless of what stage 3 telemetry shows.

07

Native Audit-Log & Filesystem Hunts (non-CQL)

Run these where EDR coverage is thin — ephemeral runners, container build stages, unmanaged laptops — and to answer the question the CQL cannot: was the package ever resolved into this project at all?

7.1 · Dependency-graph sweep (run first)

The authoritative name inventory is the OpenSourceMalware campaign list and Sonatype's sonatype-2026-005660 advisory. Pull it, then sweep every lockfile, SBOM, cache and image layer. A dependency can be present transitively without appearing in any package.json.

# 1. Direct + transitive presence, per project
npm ls --all --json 2>/dev/null | grep -iE '"(checkout-mobile-bnpl|bigops-backend|bigops-api|dolyame-boxy-desktop-bnpl-card-gallery)"'

# 2. Lockfile sweep across every repo on disk (the transitive case)
find . -name package-lock.json -o -name npm-shrinkwrap.json -o -name yarn.lock -o -name pnpm-lock.yaml \
  | xargs grep -lniE 'bigops|dolyame-boxy|checkout-mobile-bnpl'

# 3. The Sonatype correlation heuristic: bigops/bnpl names on a 35.x.y version.
#    Non-durable by its author's own statement -- use as a triage signal, then
#    confirm each hit against the authoritative campaign list.
find . -name package-lock.json | xargs grep -niE '"(bigops|.*-bnpl-)[^"]*":[[:space:]]*\{' -A2 | grep -i '"version": "35\.'

# 4. The npm cache and any internal mirror retain copies after removal
npm cache ls 2>/dev/null | grep -iE 'bigops|dolyame-boxy|checkout-mobile-bnpl'
ls ~/.npm/_cacache 2>/dev/null && grep -rl 'bigops' ~/.npm/_cacache/index-v5 2>/dev/null | head

7.2 · Did stage 1 actually run? (the marker file)

The rate-limit marker is written on the first run and is the cheapest proof of execution on a host. It is a plain file, so endpoint file telemetry may not have captured it — check the disk directly.

# Unix -- marker, staged payload, and the macOS install directory
ls -la /tmp/.analytics_state /var/tmp/.cache_* 2>/dev/null
find /var/tmp /tmp "$HOME" -maxdepth 2 -name '.cache_????????' -type f 2>/dev/null -exec ls -la {} \;

# Windows (PowerShell)
Get-ChildItem -Path $env:TEMP -Force -ErrorAction SilentlyContinue |
  Where-Object { $_.Name -eq 'analytics_state' -or $_.Name -match '^dotnet_diag_[0-9a-f]{8}\.exe$' } |
  Select-Object FullName, Length, CreationTimeUtc, LastWriteTimeUtc

7.3 · macOS persistence and payload artifacts

# The LaunchAgent -- neither name is a real Apple component in these locations
ls -la ~/Library/LaunchAgents/com.apple.windowserver.helper.plist 2>/dev/null
launchctl list 2>/dev/null | grep -i 'com.apple.windowserver.helper'

# The fake runtime install directory
ls -la ~/.local/share/runtime/ 2>/dev/null            # expect .lock and com.apple.runtime

# Fleet-wide: every user LaunchAgent that claims an Apple identifier.
# Apple ships its agents from /System/Library and /Library, never ~/Library --
# so an com.apple.* plist under a user's LaunchAgents is worth reading in full.
for u in /Users/*; do
  ls "$u/Library/LaunchAgents/" 2>/dev/null | grep -i '^com\.apple\.' | sed "s|^|$u: |"
done

7.4 · Windows persistence

# Run keys pointing anywhere user-writable
foreach ($hive in 'HKCU','HKLM') {
  foreach ($k in 'Software\Microsoft\Windows\CurrentVersion\Run',
                 'Software\Microsoft\Windows\CurrentVersion\RunOnce') {
    Get-ItemProperty -Path "${hive}:\$k" -ErrorAction SilentlyContinue |
      Select-Object -Property * -Exclude PS* |
      ForEach-Object { $_.PSObject.Properties } |
      Where-Object { $_.Value -match 'AppData|\\Temp\\|dotnet_diag_|update_win' } |
      Select-Object @{n='Hive';e={$hive}}, @{n='Key';e={$k}}, Name, Value
  }
}

# Scheduled tasks whose action runs from a user-writable path
Get-ScheduledTask | ForEach-Object {
  $exec = $_.Actions | Where-Object { $_.Execute } | Select-Object -ExpandProperty Execute
  if ($exec -match 'AppData|\\Temp\\|dotnet_diag_|update_win') {
    [pscustomobject]@{ Task = $_.TaskName; Path = $_.TaskPath; Execute = $exec }
  }
}

7.5 · Resolver and proxy logs (best vantage point on thin-telemetry hosts)

  • Any query for a name ending in wel1[.]ru, over the full retention window. There is no benign reason for this. Include NXDOMAIN answers — a sinkholed or taken-down domain still proves the host tried.
  • TXT queries whose leftmost label is c or a small integer, in a burst from one client — the chunk walk is up to 2,000 sequential lookups.
  • Queries for the eight Cloudflare Workers hosts in §4.
  • Proxy: requests carrying User-Agent: node-fetch/2.6 to any *.workers.dev host. Correlate with the requesting host and user before escalating — this UA is common in benign Node code.
  • Registry proxy (Nexus / Artifactory / Verdaccio): every request for a package name on the campaign list, including requests that 404'd — a 404 still tells you a developer or a build tried to resolve it.

7.6 · CI/CD job archaeology

Ephemeral runners are the worst case: the whole chain can execute and the host can be destroyed before anyone looks. The job log usually outlives the runner.

  • Search build logs across the retention window for the campaign package names and for bigops / dolyame-boxy / -bnpl- substrings.
  • Diff the resolved dependency tree of recent builds against the previous known-good build. An unexplained new transitive dependency added after 2026-08-04 is the signal.
  • Check whether any runner image bakes a lockfile or a warm node_modules — if so, the image itself needs rebuilding, not just the projects.
  • Enumerate what each affected runner could reach: npm publish tokens, GitHub App or PAT scopes, cloud role trust policies, signing keys, deployment secrets. That list is the rotation scope in §14.
08

CrowdStrike LogScale CQL Hunt Queries

Pick your tenant's cloud first — every "Open in Falcon" button below uses this selection.

No query carries an in-query time filter. Set the window in the Falcon console instead — @timestamp compared against an expression such as now() - N is rejected by the parser and would stop every query on line 1. Start with 30 days: publishing began around 2026-08-04, but an import can happen long after the install, so the first-execution date is not the install date. For Q1, Q2 and Q7/Q8 go to full retention — those are zero-false-positive indicator lookups and there is no cost to widening them.

Run order matters. Q1 → Q2 → Q7 → Q8 → Q11 first: they are the high-confidence, low-noise indicator hunts, and a hit on any of them converts this from a hunt into an incident. Only then run the behavioral set (Q3, Q4, Q6, Q12, Q14), which needs a baseline to be readable. Q5 and Q13 are discovery queries — expect to tune them before they are useful.

Q1 · DNS resolution of any wel1[.]ru name (DNS TXT payload channel)
CONF HIGHFP LOWCOST LOW

The single highest-value query in this pack. The campaign's DNS fallback channel lives entirely under wel1[.]ru, and no legitimate enterprise workload resolves it. This deliberately matches the apex and every subdomain — including the session-labelled form the macOS loader uses (c.<session-id>.dl.wel1[.]ru) — rather than enumerating the four known payload domains, so it survives the actor adding a fifth.

// HUNT: WEL1DROPPER DNS payload-channel resolution (wel1.ru, all depths)
// MITRE: T1071.004, T1008, T1105 | CONF: high  FP: low  COST: low
// REQUIRES: DnsRequest (verify macOS/Linux sensor DNS telemetry is flowing)
// FALSE POSITIVES: none expected. An analyst researching this report from a
//   managed host is the only realistic benign hit -- confirm against the user.
#event_simpleName=/^(DnsRequest|SuspiciousDnsRequest)$/
| DomainName=/(^|\.)wel1\.ru$/i
| table([@timestamp, aid, ComputerName, UserName, ContextBaseFileName, ContextProcessId, DomainName, RequestType, DnsResponseType], limit=2000)
| sort(field=@timestamp, order=asc)

Triage: any hit is an incident, not a finding. Pivot on aid into Q7/Q8 for the staged payload, Q9/Q10/Q11 for persistence. A hit with ContextBaseFileName of node confirms stage 1 ran in-process. An NXDOMAIN answer still proves the host tried — the domain may already be sinkholed or taken down.

Q2 · The eight Cloudflare Workers staging and beacon-proxy hosts
CONF HIGHFP LOWCOST LOW

Three oob-worker.* hosts serve the stage-1 payload (shuffled per attempt); five package-proxy.* hosts were recovered by XOR-decoding the macOS binary and serve the stage-3 beacon_mac.bin. A hit on a package-proxy host is materially worse than a hit on an oob-worker host: it means the macOS chain reached its third stage.

// HUNT: Flooding Dropper Cloudflare Workers staging + macOS beacon proxies
// MITRE: T1102.002, T1608.001, T1105 | CONF: high  FP: low  COST: low
// REQUIRES: DnsRequest
// FALSE POSITIVES: none expected -- these are dedicated attacker Workers hosts.
#event_simpleName=/^(DnsRequest|SuspiciousDnsRequest)$/
| DomainName=/^(oob-worker\.cf(103-070|102-baf|99-9b3)|package-proxy\.cf(5|6|7|8|11)oobworker)\.workers\.dev$/i
| stage := if(DomainName=/^package-proxy\./i, then="stage3-macos-beacon", else="stage1-payload")
| table([@timestamp, aid, ComputerName, UserName, ContextBaseFileName, DomainName, stage], limit=2000)
| sort(field=@timestamp, order=asc)

Triage: as Q1. Also check the egress proxy for the matching HTTPS request — a node-fetch/2.6 User-Agent against one of these hosts, with a response over 1,000 bytes, means the payload was delivered rather than merely resolved.

Q3 · Chunked-TXT payload protocol shape — any domain (survives infrastructure rotation)
CONF HIGHFP LOWCOST MED

The most durable detection in the pack. Q1 and Q2 die the moment the actor registers a new domain; the protocol does not change so easily. WEL1DROPPER asks for c.<domain> to learn the chunk count, then walks 0.<domain>, 1.<domain> … up to 1,999. This query looks for that leftmost-label shape coming from a Node process against any domain at all, and requires both halves of the protocol — the count query and at least three numbered chunks — before reporting.

// HUNT: DNS TXT chunked-payload protocol from a Node process (any domain)
// MITRE: T1071.004, T1132.001, T1008 | CONF: high  FP: low  COST: medium
// REQUIRES: DnsRequest with ContextBaseFileName populated
// FALSE POSITIVES: a handful of CDN and sharded-service schemes use numeric
//   leftmost labels. Requiring the 'c.' count query AND 3+ numbered siblings on
//   the SAME parent domain is what removes them -- a CDN never does both.
// TUNING: if a benign parent domain recurs, exclude it by name at the parent
//   stage, e.g. add: | parent!=/(^|\.)your-cdn\.example\.com$/i
//   Do NOT relax the chunk_labels threshold -- 3 is already conservative
//   against a real chunk walk that runs to as many as 2,000 lookups.
#event_simpleName=/^(DnsRequest|SuspiciousDnsRequest)$/
| ContextBaseFileName=/^node(\.exe)?$/i
| DomainName=/^(?<label>c|\d{1,4})\.(?<parent>[a-z0-9][a-z0-9\-]*\.[a-z0-9\-\.]+)$/i
| kind := if(label=/^c$/i, then="count", else="chunk")
| groupBy([aid, ComputerName, parent], function=[
    count(as=lookups),
    count(field=label, distinct=true, as=distinct_labels),
    collect([kind], limit=5),
    min(@timestamp, as=first_seen),
    max(@timestamp, as=last_seen)
  ])
| chunk_labels := distinct_labels - 1
| chunk_labels >= 3
| sort(field=lookups, order=desc, limit=200)

Triage: read parent first. If it ends in wel1.ru, escalate immediately. If it is an unfamiliar domain, you may have found the campaign's next infrastructure — pivot the parent domain fleet-wide and submit it. lookups in the hundreds indicates a completed payload reassembly.

Q4 · Node process TXT-lookup volume anomaly
CONF MEDFP MEDCOST MED

The volume complement to Q3. A payload of any real size takes hundreds of TXT lookups to reassemble, and that burst is visible even if the label shape is obfuscated in a future variant. This is the query that catches the next version of the channel.

// HUNT: anomalous TXT-record query volume from node / node.exe
// MITRE: T1071.004, T1008 | CONF: medium  FP: medium  COST: medium
// REQUIRES: DnsRequest with RequestType populated
// NOTE: RequestType is the numeric DNS QTYPE; 16 = TXT.
//   (!) validate in tenant -- confirm your sensor version populates RequestType
//   numerically and not as a string before trusting the =16 filter.
// FALSE POSITIVES: Node tooling that legitimately reads TXT records --
//   ACME/Let's Encrypt DNS-01 challenge clients, DKIM/SPF/DMARC validators,
//   service-discovery libraries, and some feature-flag SDKs.
// TUNING: exclude your known DNS-01 and mail-validation hosts by aid, and
//   exclude the parent domains they legitimately query, e.g. add after the
//   groupBy:  | !in(field=ComputerName, values=["acme-runner-01","mx-verify-02"])
//   Baseline for a week before setting the threshold -- 50 is a starting point,
//   not a tuned value.
#event_simpleName=DnsRequest
| ContextBaseFileName=/^node(\.exe)?$/i
| RequestType=16
| groupBy([aid, ComputerName, UserName], function=[
    count(as=txt_lookups),
    count(field=DomainName, distinct=true, as=distinct_names),
    collect([DomainName], limit=25),
    min(@timestamp, as=first_seen),
    max(@timestamp, as=last_seen)
  ])
| txt_lookups > 50
| sort(field=txt_lookups, order=desc, limit=200)

Triage: a genuine chunk walk shows high txt_lookups with distinct_names almost equal to it (each numbered label is a unique name) and all names sharing one parent. A DKIM validator shows the opposite: many lookups across a handful of repeated names. Read collect(DomainName) to tell them apart in one glance.

Q5 · Node resolving any *.workers.dev host — next-staging-host discovery
CONF LOW-MEDFP HIGHCOST MED

A discovery query, not a detection. Cloudflare Workers hosts plenty of legitimate SaaS, so this will be noisy — but the actor has already burned eight workers.dev hosts and shows every sign of registering more. Run it once, build the allowlist, then keep it as a scheduled low-priority hunt where only genuinely new hosts surface.

// HUNT: node-initiated resolution of any workers.dev host (discovery)
// MITRE: T1102.002 | CONF: low-med  FP: high  COST: medium
// REQUIRES: DnsRequest
// FALSE POSITIVES: high by design. Legitimate SaaS, APIs, feature-flag services
//   and internal tooling are commonly hosted on Cloudflare Workers.
// TUNING: this query is only useful once. Run it, review every host, then
//   convert the benign ones into a standing exclusion and keep the remainder as
//   the alert. Replace the placeholder below with your reviewed allowlist:
//   | !in(field=DomainName, values=["your-approved-app.workers.dev"])
//   Also consider restricting to hosts where first_seen is inside the last 7d --
//   an established host that has resolved for months is not this campaign.
#event_simpleName=/^(DnsRequest|SuspiciousDnsRequest)$/
| ContextBaseFileName=/^node(\.exe)?$/i
| DomainName=/\.workers\.dev$/i
| known := if(DomainName=/^(oob-worker\.cf(103-070|102-baf|99-9b3)|package-proxy\.cf(5|6|7|8|11)oobworker)\.workers\.dev$/i, then="KNOWN-FLOODING-DROPPER", else="unreviewed")
| groupBy([DomainName, known], function=[
    count(as=hits),
    count(field=aid, distinct=true, as=hosts),
    collect([ComputerName], limit=15),
    min(@timestamp, as=first_seen),
    max(@timestamp, as=last_seen)
  ])
| sort(field=first_seen, order=desc, limit=300)

Triage: sort by first_seen descending and read the top of the list — a Workers host that appeared for the first time in the last week, on a small number of developer or CI hosts, is the shape you are looking for. A host resolving from hundreds of machines for months is business software.

Q6 · node / node.exe spawning a shell, downloader or persistence utility
CONF MEDFP HIGHCOST MED

Stage 1 ends by shelling out: cmd.exe /c start /b on Windows, /bin/sh -c "… &" on Unix. On a developer or CI host Node spawns shells constantly, so raw volume is useless — the discriminators are the arguments. This query scores each child rather than filtering it away, so you can sort the signal to the top instead of guessing at an exclusion list.

// HUNT: node-parented shell / downloader / persistence-utility execution
// MITRE: T1059.003, T1059.004, T1059.007, T1105 | CONF: medium  FP: high  COST: medium
// REQUIRES: ProcessRollup2 or SyntheticProcessRollup2
// FALSE POSITIVES: very high on developer and CI hosts. Build tooling shells out
//   constantly -- node-gyp, husky git hooks, npm run-script, nx/turbo task
//   runners, Playwright and Puppeteer browser installs, Electron builders.
// TUNING: do not exclude by child process name -- that deletes the detection.
//   Filter on the score instead: the campaign's launch lines carry a detached
//   background marker AND a temp-directory path AND a masquerading filename.
//   Start at susp_score >= 2 and only exclude a specific ParentCommandLine
//   (e.g. your CI's known build wrapper) once you have read it.
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| ParentBaseFileName=/^node(\.exe)?$/i
| FileName=/^(cmd|powershell|pwsh|sh|bash|zsh|dash|curl|wget|osascript|launchctl|schtasks|reg|chmod)(\.exe)?$/i
| detached  := if(CommandLine=/(start\s+\/b|&\s*$|nohup|setsid|disown)/i, then=1, else=0)
| tempdir   := if(CommandLine=/(\\Temp\\|\bTEMP\b|\/var\/tmp\/|\/tmp\/|\.local\/share\/runtime)/i, then=1, else=0)
| masquerade:= if(CommandLine=/(dotnet_diag_[0-9a-f]{8}|\.cache_[0-9a-f]{8}|update_win|beacon_mac|com\.apple\.(runtime|windowserver\.helper)|analytics_state)/i, then=1, else=0)
| susp_score := detached + tempdir + masquerade
| susp_score >= 1
| table([@timestamp, aid, ComputerName, UserName, ParentBaseFileName, FileName, susp_score, detached, tempdir, masquerade, CommandLine, ParentCommandLine], limit=1000)
| sort(field=susp_score, order=desc)

Triage: susp_score of 3 is the campaign's exact launch signature and should be treated as confirmed. Score 2 with a temp path is worth reading in full. Score 1 on detached alone is usually benign build tooling — that is the tier to baseline away, and the reason the score is exposed as its own column rather than folded into the filter.

Q7 · Windows staged payload written or executed (dotnet_diag_<hex>.exe, update_win.exe)
CONF HIGHFP LOWCOST LOW

The dropper renames whatever it fetched to %TEMP%\dotnet_diag_<8 hex>.exe — a name chosen to read as a .NET diagnostics artifact. The [0-9a-f]{8} anchor is what keeps this precise: a real Microsoft diagnostics tool is not named with eight random hex characters. update_win.exe is the stage-3 path; it is included because it may be written to disk on some variants even though the analyzed sample loads it reflectively.

// HUNT: WEL1DROPPER Windows staged payload -- write and execute
// MITRE: T1105, T1036.005, T1059.003 | CONF: high  FP: low  COST: low
// REQUIRES: ProcessRollup2, SyntheticProcessRollup2, PeFileWritten, NewExecutableWritten
// FALSE POSITIVES: none expected. The 8-hex-character suffix is the campaign's
//   own randomiser; no shipping Microsoft tool matches this pattern.
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2|PeFileWritten|NewExecutableWritten)$/
| FileName=/^(dotnet_diag_[0-9a-f]{8}|update_win)\.exe$/i or TargetFileName=/(dotnet_diag_[0-9a-f]{8}|update_win)\.exe$/i or CommandLine=/(dotnet_diag_[0-9a-f]{8}\.exe|update_win\.exe|start\s+\/b\s+%TEMP%)/i
| artifact := if(TargetFileName=/dotnet_diag_[0-9a-f]{8}\.exe$/i, then="stage2-written", else="stage2-or-3-executed")
| table([@timestamp, aid, ComputerName, UserName, artifact, FileName, ImageFileName, TargetFileName, SHA256HashData, ParentBaseFileName, GrandparentBaseFileName, CommandLine, ParentCommandLine], limit=1000)
| sort(field=@timestamp, order=asc)

Triage: confirmed compromise. Capture SHA256HashData — payload polymorphism means your sample is probably not one of the seven hashes in §10, and submitting it extends coverage for everyone. Check GrandparentBaseFileName to prove the Node ancestry, then go straight to Q9 and Q10 for persistence and §14 for containment.

Q8 · Unix staged payload (.cache_<hex>, beacon_mac.bin) written or executed
CONF HIGHFP LOWCOST LOW

The Linux and macOS equivalent of Q7. The dropper writes /var/tmp/.cache_<8 hex>, chmods it executable, and launches it detached. The leading dot hides it from a plain ls, and .cache_ reads as an ordinary cache artifact — the eight hex characters are what make the pattern unambiguous. beacon_mac.bin is the macOS stage-3 payload name.

// HUNT: WEL1DROPPER Unix staged payload -- write, chmod and detached execute
// MITRE: T1105, T1036.005, T1059.004 | CONF: high  FP: low  COST: low
// REQUIRES: ProcessRollup2, SyntheticProcessRollup2, ELFFileWritten, NewExecutableWritten
// FALSE POSITIVES: none expected. Real cache files are not named with an
//   8-hex-character suffix under /var/tmp and then chmod +x'd.
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2|ELFFileWritten|NewExecutableWritten)$/
| FileName=/^(\.cache_[0-9a-f]{8}|beacon_mac\.bin)$/i or TargetFileName=/(\.cache_[0-9a-f]{8}|beacon_mac\.bin)$/i or CommandLine=/(\.cache_[0-9a-f]{8}|beacon_mac\.bin)/i
| artifact := if(TargetFileName=/\.cache_[0-9a-f]{8}$/i, then="stage2-written", else="stage2-or-3-executed")
| table([@timestamp, aid, ComputerName, UserName, artifact, FileName, ImageFileName, TargetFileName, SHA256HashData, ParentBaseFileName, GrandparentBaseFileName, CommandLine, ParentCommandLine], limit=1000)
| sort(field=@timestamp, order=asc)

Triage: confirmed compromise. On Linux, capture the hash and check it against 7e486657f30594afda379b97030252a09a19fe8055e25c9e371544f59bd8e9e3; if it differs, you have a fresh sample of a polymorphic payload — submit it. A UPX-packed, statically linked, non-PIE ELF under /var/tmp is the shape to confirm, and the reported end state is a Sliver implant, so treat outbound sessions from that host as C2 until proven otherwise.

Q9 · Windows Run-key persistence pointing at a user-writable path
CONF MEDFP MEDCOST LOW

Windows stage 2 copies itself under %AppData% and persists with both a Run key and a scheduled task — belt and braces, so finding one means looking for the other (Q10). The campaign-specific filenames are scored separately from the generic user-writable-path signal, because the filenames are certainty and the path alone is merely suspicious.

// HUNT: Run/RunOnce value written pointing into AppData or Temp
// MITRE: T1547.001, T1036.005 | CONF: medium  FP: medium  COST: low
// REQUIRES: AsepValueUpdate or RegGenericValueUpdate
// FALSE POSITIVES: a real population of legitimate software persists from
//   AppData by design -- Squirrel/Electron auto-updaters, Microsoft Teams,
//   Slack, Zoom, Discord, Dropbox, OneDrive, the VS Code updater.
// TUNING: keep the campaign-filename tier (match_kind=campaign-filename)
//   unfiltered -- it has no benign population. Baseline and exclude only the
//   generic tier by RegValueName, e.g. add:
//   | !in(field=RegValueName, values=["com.squirrel.Slack.Slack","OneDrive","Discord"])
//   Excluding by path prefix instead would delete the detection, because the
//   malware persists from the same AppData tree those products use.
#event_simpleName=/^(AsepValueUpdate|RegGenericValueUpdate)$/
| RegObjectName=/\\CurrentVersion\\Run(Once)?$/i
| RegStringValue=/(dotnet_diag_[0-9a-f]{8}|update_win\.exe|\\AppData\\|\\Temp\\)/i
| match_kind := if(RegStringValue=/(dotnet_diag_[0-9a-f]{8}|update_win\.exe)/i, then="campaign-filename", else="generic-user-writable-path")
| table([@timestamp, aid, ComputerName, UserName, match_kind, RegObjectName, RegValueName, RegStringValue], limit=1000)
| sort(field=match_kind, order=asc)

Triage: match_kind=campaign-filename is confirmed compromise — escalate without further review. For the generic tier, the question is whether a vendor you actually deploy owns that RegValueName; if the value name is random or meaningless, read the target executable's hash and signer.

Q10 · Scheduled task registered for an executable in AppData or Temp
CONF MEDFP MEDCOST LOW

The second half of the Windows persistence pair. Run this alongside Q9 and correlate on aid: a host with both a Run value and a scheduled task pointing into the same user-writable tree, created within minutes of each other, is this campaign's exact persistence signature and is far more specific than either finding alone.

// HUNT: scheduled task whose action runs from AppData or Temp
// MITRE: T1053.005, T1036.005 | CONF: medium  FP: medium  COST: low
// REQUIRES: ScheduledTaskRegistered
// FALSE POSITIVES: auto-updaters again -- Google Update, Edge/Chrome updaters,
//   Adobe, Electron app updaters and some IDE toolchains register tasks that
//   run from user-writable paths.
// TUNING: keep the campaign-filename tier unfiltered. For the generic tier,
//   baseline and exclude by TaskName once you have confirmed the owner, e.g.
//   | !in(field=TaskName, values=["GoogleUpdateTaskMachineUA","MicrosoftEdgeUpdateTaskMachineUA"])
//   Correlating this query's aid set against Q9's is a better discriminator
//   than any exclusion list: legitimate updaters rarely do both at once.
#event_simpleName=ScheduledTaskRegistered
| TaskExecutable=/(dotnet_diag_[0-9a-f]{8}|update_win\.exe|\\AppData\\|\\Temp\\)/i
| match_kind := if(TaskExecutable=/(dotnet_diag_[0-9a-f]{8}|update_win\.exe)/i, then="campaign-filename", else="generic-user-writable-path")
| table([@timestamp, aid, ComputerName, UserName, match_kind, TaskName, TaskExecutable, TaskAuthor], limit=1000)
| sort(field=match_kind, order=asc)

Triage: as Q9. TaskAuthor is the fastest tell — a legitimate updater task is authored by the vendor's installer account or SYSTEM; this campaign's task is authored by the logged-on developer.

Q11 · macOS LaunchAgent and fake Apple runtime persistence
CONF HIGHFP LOWCOST LOW

The macOS loader installs ~/Library/LaunchAgents/com.apple.windowserver.helper.plist and stages its payload at ~/.local/share/runtime/com.apple.runtime with a .lock sibling, then activates it with launchctl load -w. Both names impersonate Apple components; neither is a legitimate Apple artifact in a user's home directory. That masquerade is the detection: Apple ships its own agents from /System/Library and /Library, never from ~/Library, so any com.apple.* plist under a user LaunchAgents directory is anomalous on its own.

// HUNT: macOS LaunchAgent masquerading as an Apple component + fake runtime dir
// MITRE: T1543.001, T1036.005 | CONF: high  FP: low  COST: low
// REQUIRES: CriticalFileModified, NewExecutableWritten, ProcessRollup2
// FALSE POSITIVES: near zero for the com.apple.* tier -- Apple does not ship
//   LaunchAgents into a user's home directory. Third-party agents under
//   ~/Library/LaunchAgents are common but use their own vendor identifiers and
//   do not match the com.apple. prefix this query anchors on.
#event_simpleName=/^(CriticalFileModified|NewExecutableWritten|ProcessRollup2|SyntheticProcessRollup2)$/
| TargetFileName=/(Library\/LaunchAgents\/com\.apple\.|\.local\/share\/runtime\/(\.lock|com\.apple\.runtime)|com\.apple\.windowserver\.helper\.plist)/i or CommandLine=/(launchctl\s+(load|bootstrap)\s+-w?.*com\.apple\.windowserver\.helper|\.local\/share\/runtime\/com\.apple\.runtime)/i
| confidence := if(TargetFileName=/com\.apple\.windowserver\.helper\.plist$/i, then="campaign-exact", else="apple-masquerade-anomaly")
| table([@timestamp, aid, ComputerName, UserName, confidence, TargetFileName, FileName, SHA256HashData, ParentBaseFileName, CommandLine], limit=1000)
| sort(field=confidence, order=asc)

Triage: campaign-exact is confirmed compromise. For apple-masquerade-anomaly, read the plist: this campaign's version sets KeepAlive, throttles restarts to 60 s, and redirects stdout and stderr to /dev/null. Removal is not sufficient — see §14, the payload under ~/.local/share/runtime must go too, and a third-stage beacon may already have been fetched.

Q12 · Node-parented write of a new executable into a temp directory
CONF MEDFP MEDCOST MED

The name-independent version of Q7 and Q8. If a future wave renames the dropped file, the behavior survives: a Node process writes a fresh executable into a world-writable temp directory. This is the query that still works when every string in this pack has been rotated.

// HUNT: node writes a new executable into a temp directory (name-independent)
// MITRE: T1105, T1036 | CONF: medium  FP: medium  COST: medium
// REQUIRES: PeFileWritten, NewExecutableWritten, ELFFileWritten with ContextBaseFileName
// FALSE POSITIVES: a genuine and large benign population on developer and CI
//   hosts -- node-gyp and prebuild-install fetching native modules, esbuild /
//   swc / sharp / better-sqlite3 binary downloads, Electron and Playwright and
//   Puppeteer runtime installs, and Cypress binary caching.
// TUNING: exclude the package-manager cache and build-output trees, which is
//   where all of the benign population lands, and keep the volatile temp dirs:
//   | !in(field=TargetDirectoryName, values=["node_modules","dist","build"])
//   A better discriminator than any exclusion list is correlation -- join this
//   result set against Q1/Q2/Q3 on aid. A node-written temp binary on a host
//   that also resolved campaign infrastructure needs no further tuning.
#event_simpleName=/^(PeFileWritten|NewExecutableWritten|ELFFileWritten)$/
| ContextBaseFileName=/^node(\.exe)?$/i
| TargetFileName=/(\\AppData\\Local\\Temp\\|\\Windows\\Temp\\|\/var\/tmp\/|\/tmp\/|\/private\/var\/folders\/)/i
| hidden_name := if(TargetFileName=/(\\|\/)\.[^\\\/]+$/i, then=1, else=0)
| hex_suffix  := if(TargetFileName=/[0-9a-f]{8}(\.exe)?$/i, then=1, else=0)
| susp_score  := hidden_name + hex_suffix
| table([@timestamp, aid, ComputerName, UserName, susp_score, hidden_name, hex_suffix, TargetFileName, TargetDirectoryName, SHA256HashData, FileSize, ContextBaseFileName], limit=1000)
| sort(field=susp_score, order=desc)

Triage: susp_score=2 (a dot-prefixed name ending in eight hex characters) is the campaign's exact on-disk shape. Build tooling writes descriptively named binaries into cache directories; it does not write hidden, randomly named ones into /var/tmp.

Q13 · Known Flooding Dropper package names in npm / node command lines
CONF MEDFP MEDCOST LOW

Endpoint-side coverage for the dependency question, complementing the lockfile sweep in §7.1. Only four package names are publicly named out of roughly 800–850, so this cannot be a complete inventory — the heuristic tiers (bigops, -bnpl-, dolyame) are Sonatype's correlation signals, explicitly labelled non-durable by their author because the naming convention is already drifting.

// HUNT: Flooding Dropper package names invoked via a Node package manager
// MITRE: T1195.001, T1204.002 | CONF: medium  FP: medium  COST: low
// REQUIRES: ProcessRollup2 or SyntheticProcessRollup2
// FALSE POSITIVES: the heuristic tier only. 'bnpl' is a real industry term
//   (buy-now-pay-later) and legitimate packages use it; 'bigops' may collide
//   with internal tooling. The four named-sample matches have no benign form.
// TUNING: treat match_tier=named-sample as confirmed and never exclude it.
//   For the heuristic tier, resolve each hit against the authoritative campaign
//   list (OpenSourceMalware, or Sonatype advisory sonatype-2026-005660) before
//   escalating, then exclude confirmed-benign names, e.g.
//   | !in(field=CommandLine, values=["*@acme/bnpl-checkout*"])
//   Re-run this query weekly against a refreshed name list -- the actor is
//   publishing from disposable accounts faster than any static list ages well.
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| FileName=/^(npm|npx|node|yarn|pnpm|bun|corepack)(\.exe|-cli\.js)?$/i
| CommandLine=/(checkout-mobile-bnpl|bigops-backend|bigops-api|dolyame-boxy-desktop-bnpl-card-gallery|bigops-|-bnpl-|dolyame)/i
| match_tier := if(CommandLine=/(checkout-mobile-bnpl|bigops-backend|bigops-api|dolyame-boxy-desktop-bnpl-card-gallery)/i, then="named-sample", else="heuristic-bigops-bnpl-dolyame")
| table([@timestamp, aid, ComputerName, UserName, match_tier, FileName, CommandLine, ParentBaseFileName, ParentCommandLine], limit=1000)
| sort(field=match_tier, order=asc)

Triage: a named-sample hit means the package was resolved on this host — go immediately to §7.2 to establish whether it was ever imported, which is the difference between a cleanup and an incident. Remember that a transitive dependency never appears on a command line at all, so a clean result here does not clear the host; §7.1 does.

Q14 · Node-descended Windows LOLBins — stage-2 post-exploitation proxy
CONF MEDFP MEDCOST MED

Partial coverage for the ETW/AMSI-patching and persistence behavior in Windows stage 2. Falcon's data model has no discrete queryable event for an in-process AMSI or ETW patch — that is detected in-product, not by CQL (see §15 and §9 IOA-3). What CQL can see is the surrounding tree: a persistence or scripting utility running two levels below a Node process. On a developer host this is unusual enough to be worth reading every hit.

// HUNT: persistence / scripting LOLBins in a Node-descended process tree
// MITRE: T1547.001, T1053.005, T1562.001, T1562.006 | CONF: medium  FP: medium  COST: medium
// REQUIRES: ProcessRollup2 or SyntheticProcessRollup2 with GrandparentBaseFileName
// NOTE: this is a PROXY for the AMSI/ETW patching reported in Windows stage 2.
//   Falcon detects in-process AMSI/ETW tampering in-product; it is not a
//   discrete CQL-queryable event. Pair this with the in-product detections
//   rather than treating a clean result here as absence of tampering.
// FALSE POSITIVES: build tooling that shells out two levels deep -- installer
//   scripts, husky hooks invoking reg/schtasks, and CI wrappers.
// TUNING: exclude your known build wrappers by GrandparentBaseFileName once
//   confirmed, e.g. | GrandparentBaseFileName!=/^(agent|runner)\.exe$/i
//   Do not exclude powershell.exe -- it is the most likely stage-2 child.
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| GrandparentBaseFileName=/^node(\.exe)?$/i
| FileName=/^(reg|schtasks|powershell|pwsh|rundll32|regsvr32|wscript|cscript|mshta|bitsadmin|certutil)\.exe$/i
| encoded := if(CommandLine=/(-e|-en|-enc|-enco|-encod|-encode|-encoded|-encodedc|-encodedcommand|-w\s+hidden|-nop|frombase64string)/i, then=1, else=0)
| persist := if(CommandLine=/(CurrentVersion\\Run|\/create\s|schtasks.*\/tn)/i, then=1, else=0)
| tempref := if(CommandLine=/(\\AppData\\|\\Temp\\|dotnet_diag_[0-9a-f]{8}|update_win)/i, then=1, else=0)
| susp_score := encoded + persist + tempref
| table([@timestamp, aid, ComputerName, UserName, susp_score, encoded, persist, tempref, FileName, CommandLine, ParentBaseFileName, GrandparentBaseFileName], limit=1000)
| sort(field=susp_score, order=desc)

Triage: any hit with tempref=1 is very likely this campaign. persist=1 should immediately be cross-checked against Q9 and Q10 on the same aid. A score of 0 with a plain reg query is usually build tooling.

Pivot queries (host-scoped, for after a hit)

Replace REPLACE_WITH_AID with the agent ID from any hit above. These are the three questions triage always asks next.

// P1 -- full process tree on the suspect host, Node-related only
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/
| aid="REPLACE_WITH_AID"
| ParentBaseFileName=/^(node|npm|npx|yarn|pnpm|bun|sh|bash|zsh|cmd|powershell)(\.exe)?$/i
| table([@timestamp, UserName, ParentBaseFileName, FileName, CommandLine, SHA256HashData], limit=2000)
| sort(field=@timestamp, order=asc)

// P2 -- every external destination the host reached, newest first
#event_simpleName=/^(DnsRequest|SuspiciousDnsRequest|NetworkConnectIP4)$/
| aid="REPLACE_WITH_AID"
| groupBy([DomainName, RemoteAddressIP4, RemotePort], function=[count(as=hits), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)])
| sort(field=first_seen, order=desc, limit=500)

// P3 -- fleet-wide blast radius for a hash recovered from Q7/Q8/Q12
#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2|PeFileWritten|NewExecutableWritten|ELFFileWritten)$/
| SHA256HashData="REPLACE_WITH_SHA256"
| groupBy([aid, ComputerName, UserName], function=[count(as=hits), min(@timestamp, as=first_seen), max(@timestamp, as=last_seen)])
| sort(field=first_seen, order=asc, limit=500)
09

CrowdStrike Custom IOA Recommendations

Four of the fourteen queries are worth promoting from hunt to detection. The rest stay investigate-only: they are either discovery queries that need a per-tenant allowlist first (Q5, Q13) or scored behavioral hunts whose value is in the ranking, not in a binary verdict (Q3, Q4, Q6, Q12, Q14).

IDRuleTypePatternActionSeveritySource query
IOA-1WEL1DROPPER staged payload execution — WindowsProcess CreationImage filename matches dotnet_diag_[0-9a-f]{8}\.exe or update_win\.exe; grandparent image name matches node\.exeBlock & terminateCRITICALQ7
IOA-2WEL1DROPPER staged payload execution — macOS / LinuxProcess CreationImage filename matches \.cache_[0-9a-f]{8} or beacon_mac\.bin; parent command line contains a detached background launchBlock & terminateCRITICALQ8
IOA-3Node-descended persistence utility on a developer endpointProcess CreationGrandparent image name matches node\.exe; image name matches (reg|schtasks)\.exe; command line references AppData, Temp or a campaign filenameDetect (monitor first)HIGHQ14
IOA-4macOS LaunchAgent impersonating an Apple componentFile CreationTarget file matches Library/LaunchAgents/com\.apple\..*\.plist under a user home directoryDetectHIGHQ11

Deploy IOA-1 and IOA-2 in monitor mode for 48 hours before switching to block. Both patterns are specific enough that a false positive is unlikely, but a blocking rule on a build fleet is a production-availability decision, not just a security one. IOA-3 should stay in detect indefinitely on hosts that run CI jobs — the benign population there is real.

What no IOA can cover. Windows stage 3 decrypts /pkg/update_win.exe and executes it reflectively in memory (T1620). There is no file creation and no conventional process launch to key a Custom IOA on. Coverage for that step comes from Falcon's in-product memory scanning and AMSI/ETW-tamper detections, not from anything in this pack. Confirm those are enabled in your prevention policy, and read §15 before assuming the chain is covered end to end.

Custom IOC import

Import the CSV in §10 into Falcon IOC Management. Set the two native-payload hashes to prevent; leave the five package-file hashes on detect — they are text files from a single analyzed sample and Sonatype documented the payloads as polymorphic across packages, so preventing on them buys little and risks blocking a re-used benign file hash. The three tcsbank / cloudpayments hosts are imported as no_action deliberately: their ownership was never established.

10

Machine-Readable IOC Appendix

Every atomic value below appears verbatim in a saved source snapshot under FloodingDropper-WEL1DROPPER-npm-Hunt-sources/. Values that could not be traced to a fetched source do not appear here in any form — there is no exfiltration endpoint, no beacon port and no C2 IP address in this pack, because no source contains one.

Falcon IOC Management CSVbulk import
type,value,action,severity,expiration,description,tags
domain,wel1.ru,detect,critical,2027-02-08,WEL1DROPPER DNS TXT payload channel apex,campaign:FloodingDropper
domain,dl.wel1.ru,detect,critical,2027-02-08,WEL1DROPPER DNS delivery domain macOS loader session-labelled,campaign:FloodingDropper
domain,sdk.dl.wel1.ru,detect,critical,2027-02-08,WEL1DROPPER DNS TXT payload domain Linux x64,campaign:FloodingDropper
domain,ext.dl.wel1.ru,detect,critical,2027-02-08,WEL1DROPPER DNS TXT payload domain Linux ARM64,campaign:FloodingDropper
domain,pkg.dl.wel1.ru,detect,critical,2027-02-08,WEL1DROPPER DNS TXT payload domain macOS,campaign:FloodingDropper
domain,net.dl.wel1.ru,detect,critical,2027-02-08,WEL1DROPPER DNS TXT payload domain Windows,campaign:FloodingDropper
domain,oob-worker.cf103-070.workers.dev,detect,critical,2027-02-08,Stage1 HTTPS payload staging host 1 of 3,campaign:FloodingDropper
domain,oob-worker.cf102-baf.workers.dev,detect,critical,2027-02-08,Stage1 HTTPS payload staging host 2 of 3,campaign:FloodingDropper
domain,oob-worker.cf99-9b3.workers.dev,detect,critical,2027-02-08,Stage1 staging host 3 of 3 and Linux ELF next-stage source,campaign:FloodingDropper
domain,package-proxy.cf5oobworker.workers.dev,detect,critical,2027-02-08,macOS stage3 beacon_mac.bin proxy XOR key 0x9c,campaign:FloodingDropper
domain,package-proxy.cf6oobworker.workers.dev,detect,critical,2027-02-08,macOS stage3 beacon_mac.bin proxy XOR key 0x9c,campaign:FloodingDropper
domain,package-proxy.cf7oobworker.workers.dev,detect,critical,2027-02-08,macOS stage3 beacon_mac.bin proxy XOR key 0x9c,campaign:FloodingDropper
domain,package-proxy.cf8oobworker.workers.dev,detect,critical,2027-02-08,macOS stage3 beacon_mac.bin proxy XOR key 0x9c,campaign:FloodingDropper
domain,package-proxy.cf11oobworker.workers.dev,detect,critical,2027-02-08,macOS stage3 beacon_mac.bin proxy XOR key 0x9c,campaign:FloodingDropper
domain,nexus.tcsbank.ru,no_action,informational,2026-09-08,LEAD ONLY XOR-hidden health-check host ownership not established do not block,campaign:FloodingDropper
domain,repo-linux.tcsbank.ru,no_action,informational,2026-09-08,LEAD ONLY XOR-hidden health-check host ownership not established do not block,campaign:FloodingDropper
domain,alertmanager.cloudpayments.ru,no_action,informational,2026-09-08,LEAD ONLY XOR-hidden health-check host ownership not established do not block,campaign:FloodingDropper
sha256,7e486657f30594afda379b97030252a09a19fe8055e25c9e371544f59bd8e9e3,prevent,critical,2027-02-08,Linux x86-64 second stage UPX 3.96 statically linked non-PIE ELF,campaign:FloodingDropper
sha256,c214746c74cae8ece8bdaf69aa05da4db6ce013f9e77452d1eed1a002fd9ba00,prevent,critical,2027-02-08,macOS universal Mach-O second stage x86-64 and ARM64 LaunchAgent installer,campaign:FloodingDropper
sha256,94ef6b1c4a9d31f78f446d053048bcef34fd88f4376a1a46f7f777a9e9c83a29,detect,high,2027-02-08,_helpers.js live stage1 downloader single sample payloads are polymorphic,campaign:FloodingDropper
sha256,a3e2ffb440b779d30da3ff282affd649731088e8570df7b1aa72742d995b782c,detect,medium,2027-02-08,lib/telemetry.js 80KB fake telemetry decoy not imported by entry point,campaign:FloodingDropper
sha256,b74c5675725911c62091bdf40714df760cc2af7a88360d21065f4e1c878aa8f0,detect,medium,2027-02-08,index.js fake SDK facade with tail require of _helpers,campaign:FloodingDropper
sha256,e2650e9aa2f924433ba422857b22ee7c5996b5ad306f3f903283f6a13e248935,detect,medium,2027-02-08,package.json of analyzed sample contains no lifecycle hook,campaign:FloodingDropper
sha256,0fc30f82e1fa5e51a6c0c43f3ed7f13592ea731cb331e43a4d085df60a4db8b6,detect,medium,2027-02-08,README.md that social-engineers the developer into calling require,campaign:FloodingDropper
# Not importable as atomic IOCs -- hunt these with the queries in section 8:
#   filename pattern  dotnet_diag_[0-9a-f]{8}.exe   (Windows staged payload, Q7)
#   filename pattern  .cache_[0-9a-f]{8}            (Unix staged payload, Q8)
#   filename          update_win.exe                (Windows stage 3 path, Q7)
#   filename          beacon_mac.bin                (macOS stage 3 path, Q8)
#   path              ~/Library/LaunchAgents/com.apple.windowserver.helper.plist (Q11)
#   path              ~/.local/share/runtime/com.apple.runtime and .lock         (Q11)
#   marker file       /tmp/.analytics_state and %TEMP%\analytics_state (section 7.2)
#   user-agent        node-fetch/2.6                (proxy hunt, section 7.5)
# Deliberately NOT an indicator: 127.0.0.1:4444 is loopback -- a probe for
# another local component, not external C2. Do not import it.
Network block listfirewall / DNS RPZ / proxy
wel1.ru
dl.wel1.ru
sdk.dl.wel1.ru
ext.dl.wel1.ru
pkg.dl.wel1.ru
net.dl.wel1.ru
oob-worker.cf103-070.workers.dev
oob-worker.cf102-baf.workers.dev
oob-worker.cf99-9b3.workers.dev
package-proxy.cf5oobworker.workers.dev
package-proxy.cf6oobworker.workers.dev
package-proxy.cf7oobworker.workers.dev
package-proxy.cf8oobworker.workers.dev
package-proxy.cf11oobworker.workers.dev

Block wel1.ru as a wildcard, not just the four named payload domains — the macOS loader inserts a session label the report could not enumerate. Log every blocked query: a block that fires is a compromised host.

Hashes onlySHA-256
7e486657f30594afda379b97030252a09a19fe8055e25c9e371544f59bd8e9e3  Linux x86-64 second stage (UPX 3.96)
c214746c74cae8ece8bdaf69aa05da4db6ce013f9e77452d1eed1a002fd9ba00  macOS universal Mach-O second stage
94ef6b1c4a9d31f78f446d053048bcef34fd88f4376a1a46f7f777a9e9c83a29  _helpers.js  (live stage-1 downloader)
a3e2ffb440b779d30da3ff282affd649731088e8570df7b1aa72742d995b782c  lib/telemetry.js  (80 KB decoy)
b74c5675725911c62091bdf40714df760cc2af7a88360d21065f4e1c878aa8f0  index.js  (fake SDK facade)
e2650e9aa2f924433ba422857b22ee7c5996b5ad306f3f903283f6a13e248935  package.json  (no lifecycle hook)
0fc30f82e1fa5e51a6c0c43f3ed7f13592ea731cb331e43a4d085df60a4db8b6  README.md  (require() social engineering)
Package names & correlation heuristicsSCA / registry purge
# Publicly named samples (4 of roughly 800-850). NOT a complete inventory.
checkout-mobile-bnpl          # analyzed sample, version 35.6.9
bigops-backend                # Sonatype entry point into the campaign
bigops-api
dolyame-boxy-desktop-bnpl-card-gallery

# Sonatype correlation heuristics -- explicitly labelled NON-DURABLE by their
# author. Use to triage, then confirm against the authoritative list.
name contains   bigops
name contains   -bnpl-
name contains   dolyame
version matches 35.x.y

# Authoritative inventories (fetch fresh, do not rely on this pack):
#   OpenSourceMalware campaign package list
#   Sonatype advisory sonatype-2026-005660  (846 components, CWE-506, CVSS 8.7)
Host artifactsforensic sweep
# Windows
%TEMP%\dotnet_diag_<8 hex>.exe        staged payload
%TEMP%\analytics_state                 rate-limit marker (proves stage 1 ran)
cmd.exe /c start /b %TEMP%\dotnet_diag_<id>.exe    detached launch
HKCU or HKLM ...\CurrentVersion\Run    value into AppData or Temp
scheduled task action                  executable under AppData or Temp
/pkg/update_win.exe                    stage-3 path (decrypted, loaded in memory)

# Linux
/var/tmp/.cache_<8 hex>                staged payload, chmod +x
/tmp/.analytics_state                  rate-limit marker
/bin/sh -c "/var/tmp/.cache_<id> &"    detached launch

# macOS
/var/tmp/.cache_<8 hex>                staged loader
~/.local/share/runtime/                 payload install directory
~/.local/share/runtime/.lock
~/.local/share/runtime/com.apple.runtime
~/Library/LaunchAgents/com.apple.windowserver.helper.plist
launchctl load -w '<path>/com.apple.windowserver.helper.plist'
/pkg/beacon_mac.bin                     stage-3 beacon path

# Stage-1 payload paths requested from the staging hosts
/pkg/package          Linux x64
/pkg/package-arm64    Linux ARM64
/pkg/loader_mac       macOS universal
/pkg/package.exe      Windows
STIX-lite JSONTIP ingest
{
  "campaign": "Flooding Dropper",
  "malware": "WEL1DROPPER",
  "aliases": ["AI slopsquatting npm campaign"],
  "tracking": {"sonatype": "sonatype-2026-005660", "cwe": "CWE-506", "cvss": 8.7},
  "disclosed": "2026-08-05",
  "pack_version": "1.0",
  "pack_date": "2026-08-08",
  "assessed_predecessor": "Moika dependency-confusion campaign (April-May 2026, 250+ npm packages)",
  "ecosystem": "npm",
  "package_count_reported": "788-846 (sources vary; trend note reports 1000+)",
  "execution_trigger": "require() of the package; NO preinstall/postinstall hook",
  "platforms": ["windows", "macos", "linux-x64", "linux-arm64"],
  "indicators": {
    "domains_block": [
      "wel1.ru", "dl.wel1.ru", "sdk.dl.wel1.ru", "ext.dl.wel1.ru",
      "pkg.dl.wel1.ru", "net.dl.wel1.ru",
      "oob-worker.cf103-070.workers.dev", "oob-worker.cf102-baf.workers.dev",
      "oob-worker.cf99-9b3.workers.dev",
      "package-proxy.cf5oobworker.workers.dev", "package-proxy.cf6oobworker.workers.dev",
      "package-proxy.cf7oobworker.workers.dev", "package-proxy.cf8oobworker.workers.dev",
      "package-proxy.cf11oobworker.workers.dev"
    ],
    "domains_lead_only_do_not_block": [
      "nexus.tcsbank.ru", "repo-linux.tcsbank.ru", "alertmanager.cloudpayments.ru"
    ],
    "sha256": [
      "7e486657f30594afda379b97030252a09a19fe8055e25c9e371544f59bd8e9e3",
      "c214746c74cae8ece8bdaf69aa05da4db6ce013f9e77452d1eed1a002fd9ba00",
      "94ef6b1c4a9d31f78f446d053048bcef34fd88f4376a1a46f7f777a9e9c83a29",
      "a3e2ffb440b779d30da3ff282affd649731088e8570df7b1aa72742d995b782c",
      "b74c5675725911c62091bdf40714df760cc2af7a88360d21065f4e1c878aa8f0",
      "e2650e9aa2f924433ba422857b22ee7c5996b5ad306f3f903283f6a13e248935",
      "0fc30f82e1fa5e51a6c0c43f3ed7f13592ea731cb331e43a4d085df60a4db8b6"
    ],
    "filename_patterns": [
      "dotnet_diag_[0-9a-f]{8}.exe", ".cache_[0-9a-f]{8}",
      "update_win.exe", "beacon_mac.bin"
    ],
    "packages_named": [
      "checkout-mobile-bnpl", "bigops-backend", "bigops-api",
      "dolyame-boxy-desktop-bnpl-card-gallery"
    ],
    "user_agent": "node-fetch/2.6"
  },
  "not_indicators": {
    "127.0.0.1:4444": "loopback health probe for another local component, not C2",
    "/proc/self/exe": "UPX loader reading its own image; explicitly not an IOC"
  },
  "attck": [
    "T1195.001", "T1608.001", "T1204.002", "T1059.003", "T1059.004", "T1059.007",
    "T1547.001", "T1053.005", "T1543.001", "T1562.001", "T1562.006", "T1620",
    "T1027.002", "T1027.013", "T1036.005", "T1497.001", "T1622", "T1082",
    "T1071.004", "T1102.002", "T1105", "T1132.001", "T1008", "T1552.001"
  ],
  "intel_gaps": [
    "final Windows and macOS beacons unavailable for analysis",
    "no confirmed C2 protocol, beacon interval or exfiltration destination",
    "package-proxy port not recoverable from strings",
    "Linux ELF inner behavior not statically recoverable (UPX-packed)",
    "Sliver attribution reported by secondary sources, NOT confirmed by the primary analysis"
  ]
}
11

Detection Validation Gates

Run these before you trust a clean result. A query that returns nothing because the telemetry is absent looks identical to a query that returns nothing because the threat is absent — these gates tell the two apart.

GateCheckHowIf it fails
G1DNS telemetry is flowing from every platformRun #event_simpleName=DnsRequest | groupBy([event_platform], function=[count(as=n), count(field=aid, distinct=true, as=hosts)]) over 24 h and confirm Windows, Mac and Linux all appear with a plausible host count.Q1–Q5 are blind on the missing platform. Fall back to resolver logs (§7.5) and raise sensor coverage as an action item.
G2RequestType is populated numerically#event_simpleName=DnsRequest | groupBy([RequestType], function=[count(as=n)]). You should see 16 among the values.Q4 silently returns nothing. Change the filter to whatever your tenant uses for TXT, or drop Q4 and rely on Q3.
G3ContextBaseFileName is populated on DNS events#event_simpleName=DnsRequest | ContextBaseFileName=/^node(\.exe)?$/i | groupBy([ComputerName], function=[count(as=n)]) on a host you know runs Node.Q3, Q4 and Q5 lose their process attribution and become fleet-wide domain hunts. Remove the ContextBaseFileName stage and accept the extra noise.
G4Node is actually visible in process telemetry#event_simpleName=/^(ProcessRollup2|SyntheticProcessRollup2)$/ | FileName=/^node(\.exe)?$/i | groupBy([ComputerName], function=[count(as=n)]). Compare the host count against your known developer and CI population.Q6, Q12, Q13 and Q14 are scoped to a fraction of the estate. Identify which build fleets have no sensor — those are the hosts this campaign will land on unseen.
G5Registry and scheduled-task telemetry is onConfirm AsepValueUpdate, RegGenericValueUpdate and ScheduledTaskRegistered each return events in the last 24 h.Q9 and Q10 return nothing regardless of infection state. Persistence must then be found with the §7.4 PowerShell sweep instead.
G6Field names resolve in your tenantRun each query with the final table() removed. Any field your tenant does not populate shows as empty rather than erroring.Replace the field or drop it from the projection. RequestType, DnsResponseType, TaskAuthor and TargetDirectoryName are the most tenant-variable fields used here.
G7Positive-control test — DNS chunk protocolFrom a lab host with Node installed, run a script that issues TXT lookups for c.example-lab.yourdomain.test then 0., 1., 2., 3. of the same parent. Q3 must return the parent domain.Q3 is not working. Check G1 and G3 first, then confirm your DNS events carry the full query name and not just the registrable domain.
G8Positive-control test — staged payload namingOn a lab host, copy any benign executable to %TEMP%\dotnet_diag_deadbeef.exe (or /var/tmp/.cache_deadbeef) and run it. Q7 or Q8 must return it.Q7/Q8 are not working. Most likely the file-write event is not enabled or FileName is carrying a full path rather than a basename in your tenant.
G9Baselines captured before tuningRecord the 7-day result counts for Q4, Q5, Q6, Q12, Q13 and Q14 before adding any exclusion.Without a baseline, every exclusion is a guess and you cannot tell later whether a quiet query is tuned or broken.
G10Dependency sweep actually covered everythingConfirm §7.1 ran against every repo, every lockfile, the npm cache, the internal mirror, and container image layers — not just the projects someone remembered.A transitive dependency in an unreviewed repo keeps re-infecting after cleanup. This is the most common way this campaign survives remediation.

The gate that matters most is G10. Both primary sources make the same point in different words: removing the dependency is necessary and not sufficient, and by the time the package is discovered the package is no longer the main problem. A clean endpoint hunt across a fleet whose lockfiles were never swept proves very little.

12

Hardening — Tiered & Framework-Cited

The control everyone reaches for first does not work here. npm config set ignore-scripts true, --ignore-scripts in CI, and install-script sandboxing all defend against lifecycle hooks. These packages have none. Execution happens on require(), which is a normal application operation you cannot disable. Keep the ignore-scripts controls — they are good for other campaigns — but do not count them as coverage for this one. The controls that actually bite are egress restriction, execution restriction in temp paths, and getting a malware-aware proxy in front of the registry.

Immediate — deploy this week, no compatibility risk
ControlWhy it stops this chainAuthorityVerify
Block the 14 campaign hosts at DNS and egress. Wildcard *.wel1.ru, not just the four named payload domains. Log every blocked query.Kills both delivery channels — the HTTPS staging fetch and the DNS TXT fallback. A blocked query is also a free detection: only a compromised host generates one.MITRE M1037 (Filter Network Traffic), M1031 (Network Intrusion Prevention)Playbook D, step 4. Then run Q1 and Q2 and confirm any hits now show blocked/NXDOMAIN answers.
Import the §10 CSV into Falcon IOC Management. Two native-payload hashes to prevent, five package-file hashes to detect, three lead hosts to no_action.Stops the two analyzed second-stage binaries from executing anywhere in the fleet, immediately.MITRE M1040 (Behavior Prevention on Endpoint)Falcon console → IOC Management → filter tag campaign:FloodingDropper; expect 24 entries.
Confirm Falcon prevention policy has Suspicious Process Blocking, Script-Based Execution Monitoring, and Advanced Memory Scanning enabled on developer and CI host groups.Advanced memory scanning is the only coverage that exists for the reflectively loaded Windows stage 3 (T1620). Nothing in this pack's CQL can see it.MITRE M1040; CrowdStrike prevention-policy guidancePrevention policy → compare the developer/CI policy against your standard workstation policy; they are frequently weaker.
Force all DNS through internal resolvers with full query logging including QTYPE, and block outbound 53/853/DoH endpoints from endpoints.The TXT chunk channel is only visible if you log TXT queries. If a developer host can reach a public resolver directly, the entire DNS half of this pack is blind.MITRE M1037; CIS Controls v8 §4.9, §13.10; NIST SP 800-81 (Secure DNS Deployment)From a developer host, query any public resolver directly by address (nslookup -type=txt example.com <public-resolver>) — it must fail. Then repeat against the internal resolver and confirm the query appears in its log with QTYPE=TXT.
Run the §7.1 dependency sweep across every repo, lockfile, SBOM, npm cache, internal mirror and container image layer. Treat any hit as an incident, not a cleanup ticket.This is the only control that finds the transitive case. Both primary sources say removing the dependency is necessary and not sufficient.MITRE M1051 (Update Software); CIS Controls v8 §16.4, §16.6Gate G10. Record which repos were swept, so the ones that were not are visible.
Near term — 1 to 4 weeks, pilot on a ring first
ControlWhy it stops this chainAuthorityVerify
Mandatory malware-aware registry proxy. Nexus Firewall, Artifactory Curation, or equivalent, with quarantine-on-unknown. Block direct egress to registry.npmjs.org from CI runners so the proxy cannot be bypassed.Sonatype identified this campaign through exactly this control surface. A quarantine policy on newly published, low-download packages stops a 48-hour-old slopsquat before a developer ever imports it.MITRE M1035 (Limit Access to Resource Over Network); CIS Software Supply Chain Security Guide §2 (Source), §3 (Build Pipelines)From a runner: curl -sI https://registry.npmjs.org/ must fail; npm ci against the proxy must succeed.
Deny execution from user-writable temp paths. WDAC or AppLocker on Windows (%TEMP%, %LOCALAPPDATA%\Temp); noexec,nosuid,nodev on /tmp and /var/tmp on Linux.Directly breaks step 7-to-8 of the chain on both platforms. The dropper has no fallback write location — it hardcodes %TEMP% and /var/tmp.MITRE M1038 (Execution Prevention); CIS Microsoft Windows Benchmark §18 (AppLocker/WDAC); CIS Distribution Independent Linux Benchmark §1.1.2–1.1.5Playbooks A and B. Post-deploy, re-run Q7/Q8 and confirm any staged payload now shows a blocked execution rather than a process start.
Default-deny egress for CI runners. Allow only the registry proxy, the artifact store, the VCS and the deployment target. Nothing else, including DNS to external resolvers.A runner that cannot reach *.workers.dev or resolve wel1.ru cannot complete stage 1 no matter what it imports. This is the highest-leverage control for the surface with the best credentials.MITRE M1030 (Network Segmentation), M1037; CIS Software Supply Chain Security Guide §3.3From a runner: curl -m 5 https://example.workers.dev must fail. Re-run Q5 after a week and confirm the runner population has dropped out of the results.
Windows attack-surface reduction: enable the ASR rules for "Block executable files from running unless they meet a prevalence, age, or trusted list criterion" and "Block process creations originating from PSExec and WMI commands", and enable RunAsPPL.The prevalence/age rule is a direct counter to a payload downloaded seconds ago from a Worker. RunAsPPL raises the cost of the credential theft that follows.MITRE M1038, M1040, M1043 (Credential Access Protection); Microsoft Security Baseline for Windows 11; CIS Microsoft Windows Benchmark §18.9Playbook A, step 3. Get-MpPreference | Select-Object -ExpandProperty AttackSurfaceReductionRules_Ids.
macOS: deploy a configuration profile that inventories and alerts on user LaunchAgents, and baseline ~/Library/LaunchAgents across the fleet.Apple never ships agents into a user home directory, so a com.apple.* plist there is anomalous by construction — a cheap, durable signal that outlives this campaign's specific filename.MITRE M1018 (User Account Management), M1047 (Audit); CIS Apple macOS Benchmark §2 and §5Playbook C. Run the §7.3 fleet loop and confirm the inventory is empty or fully explained.
Rotate to short-lived, workload-scoped credentials in CI (OIDC federation) and remove long-lived npm and cloud tokens from runner environments.Changes the outcome rather than the odds. Stage 1 landing on a runner that holds only a 15-minute OIDC token is a very different incident from one that holds a publish-scoped npm token.MITRE M1027 (Password Policies), M1026 (Privileged Account Management); CIS Controls v8 §5.2, §6.5Audit runner environment variables and secret stores for any static NPM_TOKEN / cloud key; the target is zero.
Keep ignore-scripts enforced anywaynpm ci --ignore-scripts in CI and ignore-scripts=true in the org .npmrc.Does NOT stop this campaign. Listed explicitly so nobody records it as coverage. It remains correct hygiene against the much larger population of lifecycle-hook malware.MITRE M1042 (Disable or Remove Feature or Program); CIS Software Supply Chain Security Guide §3.2npm config get ignore-scripts returns true. Then confirm in your risk register that this control is not credited against WEL1DROPPER.
Strategic — 1 to 3 months, architectural or organisational
ControlWhy it stops this chainAuthorityVerify
Ephemeral, network-segmented CI runners rebuilt from a golden image per job.Removes persistence as a concept on the surface that matters most. Run key, scheduled task and LaunchAgent all become irrelevant if the host does not survive the job.MITRE M1030, M1053 (Data Backup) for image integrity; CIS Software Supply Chain Security Guide §3.1Confirm runner uptime metrics show single-job lifetimes and that no runner image ships a warm node_modules or a baked lockfile.
Internal-first namespace policy: scope all first-party packages (@yourorg/) and configure the registry so unscoped names never resolve externally without review.Attacks the slopsquatting mechanism at the root. A hallucinated bare package name cannot resolve if bare names are not resolvable, and it also closes the dependency-confusion path the predecessor Moika campaign used.MITRE M1035; CIS Software Supply Chain Security Guide §2.4; npm scoped-package guidanceFrom a workstation: npm install some-nonexistent-package-name must fail closed against the proxy rather than reaching the public registry.
AI-assistant dependency policy: require that any package name suggested by a coding assistant is validated against the internal allowlist before it enters a branch, and make new-dependency additions a blocking review item.This campaign exists because assistants invent plausible package names and developers install them unchecked. No endpoint control addresses the human step; a review gate does.⚠ best-practice, no formal benchmark — MITRE M1017 (User Training) is the closest mapping; CIS Software Supply Chain Security Guide §2 covers the review requirement genericallySample recent PRs that add a dependency and confirm each carries an explicit reviewer sign-off on the package name.
Continuous SBOM diffing with alerting on any new transitive dependency.The transitive case is how this survives cleanup. A diff on every build turns "we removed the package" into a verifiable claim.MITRE M1051; CIS Controls v8 §16.4; NIST SP 800-218 (SSDF) PS.3.2, PW.4.1Introduce a benign new transitive dependency in a test branch and confirm the pipeline flags it.
Treat developer workstations and build agents as tier-0-adjacent: same EDR policy, same logging, same credential hygiene as production.Both primary sources direct rotation of npm, GitHub, cloud, signing and deployment credentials after a confirmed import. If those credentials sit on hosts with a weaker policy than the systems they control, the policy is inverted.MITRE M1026, M1018; CIS Controls v8 §4.1, §5.4, §6.8Compare the developer/CI Falcon policy, log retention and credential lifetime against the production baseline; there should be no gap.
13

Deployable Playbooks

Pilot every playbook on a small ring before fleet deployment. Playbooks A and B change what is allowed to execute; on a developer or build fleet that is a production-availability decision. Every step below carries its prerequisites, reboot requirement and rollback — if a step has no undo, that is stated explicitly and justified.

Playbook A · Windows — deny execution from temp paths and enable ASR

Prerequisites: Windows 10 1903+ or Windows 11; Microsoft Defender ASR available (works alongside Falcon); local admin or a GPO/Intune deployment channel; an AppLocker or WDAC policy already in enforcement or audit mode. Reboot required: no for ASR rules; yes for a new AppLocker policy to take effect reliably (or restart the Application Identity service). Rollback: given per step.

# --- Step 1. Start in AUDIT mode. Never deploy this in enforce first. -----
# Rollback: set the rules back to 0 (Disabled) -- see step 4.
# Block executables that lack prevalence/age/trust  (01443614-cd74-433a-b99e-2ecdc07bfc25)
Add-MpPreference -AttackSurfaceReductionRules_Ids 01443614-cd74-433a-b99e-2ecdc07bfc25 `
                 -AttackSurfaceReductionRules_Actions AuditMode
# Block process creations from PSExec and WMI          (d1e49aac-8f56-4280-b9ba-993a6d77406c)
Add-MpPreference -AttackSurfaceReductionRules_Ids d1e49aac-8f56-4280-b9ba-993a6d77406c `
                 -AttackSurfaceReductionRules_Actions AuditMode

# --- Step 2. Measure the audit population for 7 days ----------------------
# Rollback: none required -- this step only reads.
Get-WinEvent -LogName 'Microsoft-Windows-Windows Defender/Operational' -MaxEvents 5000 |
  Where-Object { $_.Id -in 1121,1122 } |
  Select-Object TimeCreated, Id, @{n='Path';e={$_.Properties[4].Value}} |
  Group-Object Path | Sort-Object Count -Descending | Select-Object -First 40

# --- Step 3. Promote to Enabled once the audit population is understood ---
# Rollback: re-run with -AttackSurfaceReductionRules_Actions AuditMode.
Set-MpPreference -AttackSurfaceReductionRules_Ids 01443614-cd74-433a-b99e-2ecdc07bfc25 `
                 -AttackSurfaceReductionRules_Actions Enabled
Set-MpPreference -AttackSurfaceReductionRules_Ids d1e49aac-8f56-4280-b9ba-993a6d77406c `
                 -AttackSurfaceReductionRules_Actions Enabled

# --- Step 4. FULL ROLLBACK for steps 1 and 3 ------------------------------
Set-MpPreference -AttackSurfaceReductionRules_Ids 01443614-cd74-433a-b99e-2ecdc07bfc25 `
                 -AttackSurfaceReductionRules_Actions Disabled
Set-MpPreference -AttackSurfaceReductionRules_Ids d1e49aac-8f56-4280-b9ba-993a6d77406c `
                 -AttackSurfaceReductionRules_Actions Disabled

# --- Step 5. Verify -------------------------------------------------------
# Rollback: none required -- read-only.
$p = Get-MpPreference
for ($i = 0; $i -lt $p.AttackSurfaceReductionRules_Ids.Count; $i++) {
  [pscustomobject]@{ Rule = $p.AttackSurfaceReductionRules_Ids[$i]
                     Action = $p.AttackSurfaceReductionRules_Actions[$i] }
}

AppLocker deny rule for the staged-payload path. Prerequisites: Application Identity service running; an existing AppLocker policy (this MERGES into it — never deploy as a standalone policy or you will replace the fleet's rule set). Reboot required: recommended, or restart AppIDSvc. Rollback: given inline.

<!-- Save as deny-temp-exec.xml. Deny rules win over allow rules in AppLocker. -->
<AppLockerPolicy Version="1">
  <RuleCollection Type="Exe" EnforcementMode="AuditOnly">
    <FilePathRule Id="7f3b1d20-0c4e-4a11-9f2b-5c8d61a04e77"
                  Name="DENY execute from user TEMP (Flooding Dropper)"
                  Description="WEL1DROPPER stages %TEMP%\dotnet_diag_<8hex>.exe"
                  UserOrGroupSid="S-1-1-0" Action="Deny">
      <Conditions><FilePathCondition Path="%OSDRIVE%\Users\*\AppData\Local\Temp\*.exe" /></Conditions>
    </FilePathRule>
  </RuleCollection>
</AppLockerPolicy>
# Deploy (MERGE, do not replace):
Set-AppLockerPolicy -XmlPolicy .\deny-temp-exec.xml -Merge
Restart-Service AppIDSvc

# Promote to enforcement only after reviewing audit events 8003/8004:
#   change EnforcementMode="AuditOnly" to "Enabled" and re-merge.

# ROLLBACK -- remove just this rule, leaving the rest of the policy intact:
$pol = Get-AppLockerPolicy -Effective -Xml
$xml = [xml]$pol
$node = $xml.SelectSingleNode("//FilePathRule[@Id='7f3b1d20-0c4e-4a11-9f2b-5c8d61a04e77']")
if ($node) { $node.ParentNode.RemoveChild($node) | Out-Null }
$xml.Save("$env:TEMP\applocker-rollback.xml")
Set-AppLockerPolicy -XmlPolicy "$env:TEMP\applocker-rollback.xml"
Restart-Service AppIDSvc
# If the policy came from GPO, remove the rule at the GPO instead -- a local
# rollback will be overwritten at the next policy refresh.

Playbook B · Linux — noexec on /tmp and /var/tmp

Prerequisites: root; systemd-based distribution; confirm no build tooling executes from these paths first — some package managers, installers and CI caches legitimately do, and this change will break them. Run the pre-flight check in step 1 for a full week before proceeding. Reboot required: no if you use mount -o remount; the fstab entry makes it survive reboot. Rollback: step 5, effective immediately without reboot.

# --- Step 1. PRE-FLIGHT: does anything legitimately execute from /tmp? ----
# Run for a week before changing anything. Rollback: none required, read-only.
sudo auditctl -w /tmp -p x -k tmp_exec_probe
sudo auditctl -w /var/tmp -p x -k tmp_exec_probe
sudo ausearch -k tmp_exec_probe --start week-ago -i 2>/dev/null | grep -oP 'exe="\K[^"]+' | sort | uniq -c | sort -rn
# Remove the probe when finished:  sudo auditctl -W /tmp -p x -k tmp_exec_probe

# --- Step 2. Back up fstab (this IS the rollback artifact) ----------------
sudo cp -a /etc/fstab "/etc/fstab.bak.$(date +%Y%m%d)"

# --- Step 3. Apply. If /tmp is already a separate mount, add the options.
#     If it is not, create a tmpfs mount for it.
grep -qE '^\S+\s+/tmp\s' /etc/fstab \
  && sudo sed -i -E 's|^(\S+\s+/tmp\s+\S+\s+)([^ ]+)|\1\2,noexec,nosuid,nodev|' /etc/fstab \
  || echo 'tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev,size=2G 0 0' | sudo tee -a /etc/fstab

grep -qE '^\S+\s+/var/tmp\s' /etc/fstab \
  && sudo sed -i -E 's|^(\S+\s+/var/tmp\s+\S+\s+)([^ ]+)|\1\2,noexec,nosuid,nodev|' /etc/fstab \
  || echo '/tmp /var/tmp none rw,noexec,nosuid,nodev,bind 0 0' | sudo tee -a /etc/fstab

sudo mount -o remount /tmp && sudo mount -o remount /var/tmp
sudo systemctl daemon-reload

# --- Step 4. Verify -- both must show noexec, and the test must fail ------
findmnt -no TARGET,OPTIONS /tmp /var/tmp
printf '#!/bin/sh\necho reached\n' > /tmp/.exec_test && chmod +x /tmp/.exec_test
/tmp/.exec_test 2>&1 | grep -q 'Permission denied' && echo "PASS: noexec enforced" || echo "FAIL: still executable"
rm -f /tmp/.exec_test

# --- Step 5. ROLLBACK -- immediate, no reboot -----------------------------
sudo cp -a "/etc/fstab.bak.$(date +%Y%m%d)" /etc/fstab
sudo mount -o remount,exec /tmp && sudo mount -o remount,exec /var/tmp
sudo systemctl daemon-reload
findmnt -no TARGET,OPTIONS /tmp /var/tmp   # confirm noexec is gone

Playbook C · macOS — LaunchAgent baseline and campaign artifact removal

Prerequisites: admin on the target Mac; Full Disk Access for the shell if run under MDM. Do not run the removal half until the host has been isolated and triage evidence is captured — deleting the plist destroys the timeline. Reboot required: no; launchctl bootout takes effect immediately. Rollback: the inventory step is read-only; the removal step is deliberately irreversible and is preceded by an evidence copy.

# --- Step 1. INVENTORY (read-only, safe to run fleet-wide) ----------------
# Rollback: none required.
for u in /Users/*; do
  [ -d "$u/Library/LaunchAgents" ] || continue
  for p in "$u/Library/LaunchAgents/"*.plist; do
    [ -e "$p" ] || continue
    lbl=$(/usr/libexec/PlistBuddy -c 'Print :Label' "$p" 2>/dev/null)
    prg=$(/usr/libexec/PlistBuddy -c 'Print :ProgramArguments:0' "$p" 2>/dev/null)
    case "$lbl" in com.apple.*) flag="APPLE-MASQUERADE" ;; *) flag="third-party" ;; esac
    printf '%s\t%s\t%s\t%s\n' "$flag" "$u" "$lbl" "$prg"
  done
done | sort
# Apple ships agents from /System/Library and /Library ONLY. Any APPLE-MASQUERADE
# row is anomalous and must be explained before it is dismissed.

# --- Step 2. CAPTURE EVIDENCE before touching anything --------------------
# Rollback: none required -- this only copies.
EV="$HOME/fd-evidence-$(date +%Y%m%d%H%M%S)"; mkdir -p "$EV"
cp -a ~/Library/LaunchAgents/com.apple.windowserver.helper.plist "$EV/" 2>/dev/null
cp -a ~/.local/share/runtime "$EV/runtime" 2>/dev/null
shasum -a 256 "$EV/runtime/com.apple.runtime" 2>/dev/null | tee "$EV/hashes.txt"
ls -la@ ~/.local/share/runtime/ > "$EV/listing.txt" 2>/dev/null

# --- Step 3. REMOVE (only after isolation and step 2) ---------------------
# Rollback: NOT reversible by design -- this is eradication, and re-creating
# malware persistence is never a desired undo. The evidence copy from step 2
# is the recovery path if the artifact turns out to be benign.
launchctl bootout "gui/$(id -u)/com.apple.windowserver.helper" 2>/dev/null
launchctl unload -w ~/Library/LaunchAgents/com.apple.windowserver.helper.plist 2>/dev/null
rm -f ~/Library/LaunchAgents/com.apple.windowserver.helper.plist
rm -rf ~/.local/share/runtime
rm -f /var/tmp/.cache_???????? /tmp/.analytics_state

# --- Step 4. Verify -------------------------------------------------------
# Rollback: none required -- read-only.
launchctl list 2>/dev/null | grep -i 'windowserver.helper' && echo "FAIL: still loaded" || echo "PASS: agent gone"
ls ~/.local/share/runtime 2>/dev/null && echo "FAIL: runtime dir remains" || echo "PASS: runtime dir gone"

Removal is not remediation. A macOS host that reached this stage may already have fetched beacon_mac.bin through one of the five proxy hosts, and the final beacon was never available for analysis — so nobody knows what it did. Rebuild the host and rotate its credentials per §14; do not return a cleaned Mac to service on the strength of step 4 passing.

Playbook D · Network — deploy the campaign blocklist

Prerequisites: admin on the DNS/RPZ platform and the egress proxy or firewall; a change window if your DNS platform reloads zones disruptively. Reboot required: no. Rollback: step 5 — remove the zone/category and reload; effective in seconds.

# --- Step 1. Record current state (this is the rollback baseline) ---------
# Rollback: none required -- read-only.
cp -a /etc/bind/db.rpz "/etc/bind/db.rpz.bak.$(date +%Y%m%d)" 2>/dev/null || true

# --- Step 2. RPZ entries. Wildcard the apex -- the macOS loader inserts a
#     session label under dl.wel1.ru that no report enumerated.
# Rollback: delete these lines and reload (step 5).
wel1.ru                                   CNAME .
*.wel1.ru                                 CNAME .
oob-worker.cf103-070.workers.dev          CNAME .
oob-worker.cf102-baf.workers.dev          CNAME .
oob-worker.cf99-9b3.workers.dev           CNAME .
package-proxy.cf5oobworker.workers.dev    CNAME .
package-proxy.cf6oobworker.workers.dev    CNAME .
package-proxy.cf7oobworker.workers.dev    CNAME .
package-proxy.cf8oobworker.workers.dev    CNAME .
package-proxy.cf11oobworker.workers.dev   CNAME .

# --- Step 3. Do NOT wildcard *.workers.dev ---------------------------------
# It hosts a large volume of legitimate SaaS. Block the ten names above only,
# and use Q5 to review new workers.dev hosts on their own merits.

# --- Step 4. Reload and verify --------------------------------------------
rndc reload
dig +short txt c.sdk.dl.wel1.ru   # must return NXDOMAIN / empty
dig +short oob-worker.cf99-9b3.workers.dev   # must return NXDOMAIN / empty
# Confirm the block is LOGGED, not just enforced -- a blocked query identifies
# a compromised host, which is worth more than the block itself.

# --- Step 5. ROLLBACK ------------------------------------------------------
cp -a "/etc/bind/db.rpz.bak.$(date +%Y%m%d)" /etc/bind/db.rpz
rndc reload
dig +short oob-worker.cf99-9b3.workers.dev   # resolution restored

# --- Note on the three tcsbank / cloudpayments hosts -----------------------
# Do NOT add nexus.tcsbank.ru, repo-linux.tcsbank.ru or
# alertmanager.cloudpayments.ru to this list. The primary analysis states that
# static evidence does not establish whether they are attacker-owned,
# compromised, or decoys. Monitor them; blocking them risks breaking traffic to
# legitimate financial services and proves nothing either way.
14

Containment Runbook

Standing assumption: both primary sources direct that a confirmed import be treated as a host compromise. The final Windows and macOS beacons were never available for analysis, so nobody can tell you what the second stage did. Do not scope this incident to what your telemetry happened to capture.

Phase 1 · Isolate (0 to 1 hour)

  1. Network-contain every host with a Q1, Q2, Q7, Q8 or Q11 hit via Falcon Host Containment. Contain, do not power off — the Windows stage 3 runs in memory and a shutdown destroys the only place it exists.
  2. Freeze the affected CI runners. Pause the runner pool rather than deleting instances; an ephemeral runner that is auto-recycled takes the evidence with it. If the pool has already recycled, the job logs are your only artifact — preserve them now (§7.6).
  3. Capture volatile state before anything else. Running process list with full command lines, network connections, the contents of %TEMP% / /var/tmp, the marker file timestamps (they date the first execution), and on Windows a memory image if your process supports it.
  4. Suspend, do not yet rotate, the credentials reachable from the host. Rotating before eradication hands the new secret to a still-resident implant. Suspend npm publish rights, disable the GitHub token, and revoke active cloud sessions.
  5. Deploy the §10 blocklist fleet-wide immediately (Playbook D) even if only one host is confirmed. This is the cheapest action available and it converts every remaining infected host into a logged block event.

Phase 2 · Scope (1 to 8 hours)

  1. Run the full query set in the order given in §8 across the entire estate, full retention window, not just the suspected host group.
  2. Run the §7.1 dependency sweep everywhere — every repo, lockfile, SBOM, npm cache, internal mirror and container image layer. Gate G10. This is where the incident is either bounded or found to be much larger.
  3. Pull the authoritative package list from OpenSourceMalware and Sonatype sonatype-2026-005660, then re-run the sweep against the full list rather than the four names in this pack.
  4. Query the registry proxy for every request matching the campaign list, including 404s. A 404 tells you a developer or a build tried to resolve a slopsquat name, which is a training and policy finding even where no package existed.
  5. Enumerate the credential blast radius per affected host: npm tokens, GitHub App and PAT scopes, cloud role trust policies, signing keys, deployment secrets, SSH keys, and any .env or secret file on disk. That list is the Phase 4 rotation scope.
  6. Check published artifacts. If an affected runner held publish rights, review everything it published since the first marker-file timestamp. A compromised build agent is a supply-chain incident for your own consumers.

Phase 3 · Eradicate (8 to 48 hours)

  1. Rebuild, do not clean. Windows stage 3 is loaded reflectively and stage 2 patches ETW and AMSI. A host where those ran cannot be certified clean by scanning it — the instrumentation you would scan with is the instrumentation that was patched. Reimage from known-good.
  2. Where a rebuild is genuinely impossible, remove persistence explicitly and document the accepted risk: Windows Run key value and scheduled task (§7.4), the AppData self-copy, %TEMP%\dotnet_diag_* and analytics_state; macOS the LaunchAgent, ~/.local/share/runtime and /var/tmp/.cache_* (Playbook C); Linux /var/tmp/.cache_* and /tmp/.analytics_state.
  3. Purge the dependency at every layer: remove it from package.json and the lockfile, clear the npm cache, evict it from the internal mirror, rebuild any container image that included it, and rebuild any runner image with a warm node_modules.
  4. Rebuild affected CI runner images from source and confirm the new image resolves dependencies only through the malware-aware proxy.
  5. Verify eradication with the pack itself: re-run Q1, Q2, Q7, Q8, Q9, Q10 and Q11 scoped to the rebuilt hosts and confirm zero results across a full week.

Phase 4 · Recover (48 hours onward)

  1. Now rotate every credential identified in Phase 2 step 5 — after eradication, not before. npm tokens, GitHub tokens and App keys, cloud credentials, signing keys, deployment secrets, and any SSH key present on an affected host.
  2. Invalidate active sessions for the affected users across the registry, VCS and cloud consoles. A rotated secret does not end a session that is already open.
  3. Re-issue signing keys and re-sign artifacts if a host with signing material was affected. This is slow and unpleasant, and it is the step most often skipped.
  4. Return hosts to service only after a clean rebuild, a clean week of the query set, and confirmation that the immediate-tier hardening from §12 is deployed on that host.
  5. Close the loop: submit any new sample hash and any new staging host discovered via Q3 or Q5 to OpenSourceMalware and your registry vendor. Payload polymorphism and account churn mean the community list is only as good as what defenders feed back into it.
  6. Write up the dependency-introduction path. Which developer or which assistant suggested the package, whether review caught it, and what would have stopped it. That finding drives the strategic tier in §12 — every other control in this pack is downstream of that one decision.
15

Detection Coverage Map

Honest coverage, step by step. Four steps of the chain have no CQL coverage at all and are marked as such — a green row for every step would be a nicer picture and a worse pack.

Chain stepCoverageQueriesNotes
1 · Slopsquat package enters the dependency graphPARTIALQ13, §7.1Only 4 of roughly 800–850 names are public. Transitive dependencies never appear on a command line, so endpoint coverage alone cannot answer this — §7.1 is the real control and G10 is the gate.
2 · require() detonates stage 1NONEA module import inside a running Node process produces no distinct telemetry. Detected only by inference from steps 5 onward. Not fixable with better queries.
3 · Kill-switch / rate-limit markerPARTIAL§7.2A plain non-executable file, so file-write events frequently miss it. Best found by sweeping the disk. Valuable because its timestamp dates the first execution.
4 · OS + architecture fingerprintNONEIn-process logic. No telemetry exists. Harmless as a gap — every following step is covered.
5 · HTTPS payload fetch from Workers hostsGOODQ2, Q5, §7.5Q2 covers the eight known hosts with near-zero FP. Q5 is the discovery path for hosts nine and onward but needs a per-tenant allowlist first.
6 · DNS TXT chunked payload channelSTRONGQ1, Q3, Q4The best-covered step. Q1 is atomic and dies on domain rotation; Q3 targets the protocol shape and survives it; Q4 catches the volume signature even if the labels change. Depends on gate G1 and G3 passing.
7 · Payload written to a temp directorySTRONGQ7, Q8, Q12Q7/Q8 are exact-name and near-zero FP. Q12 is the name-independent version that survives a rename. Between them this step is well covered on all three platforms.
8 · Detached executionGOODQ6, Q7, Q8Q6 scores rather than filters, which is what makes it usable on a developer fleet where Node shells out constantly. Score 3 is the campaign's exact signature.
9a · Windows ETW / AMSI patchingPARTIALQ14 (proxy), IOA-3No discrete CQL-queryable event exists for an in-process AMSI or ETW patch. Q14 sees the surrounding process tree only. Real coverage is Falcon's in-product tamper detection — confirm it is enabled rather than assuming this row is green.
9b · Windows Run key + scheduled task persistenceGOODQ9, Q10Both carry a real benign population from auto-updaters, which is why each is tiered into campaign-filename and generic-user-writable-path. Correlating Q9 and Q10 on the same aid is the strongest discriminator available.
9c · Windows stage 3 reflective in-memory loadNONEIOA-4 note onlyThe most consequential gap in the pack. The encrypted update_win.exe is decrypted and executed in memory, so there is no file write and no conventional process start to key on. Coverage depends entirely on Falcon Advanced Memory Scanning. This is why §14 treats a confirmed stage-1 execution as compromise regardless of stage-3 telemetry.
10a · macOS anti-analysis checksPARTIALFile-existence probes for lldb, frida, VMware artifacts and a hw.memsize query are not reliably logged. Low value to chase — step 10b covers the same infection with far better fidelity.
10b · macOS LaunchAgent persistenceSTRONGQ11, §7.3The Apple-masquerade anchor makes this durable well beyond this campaign's specific filename.
10c · macOS stage-3 beacon fetchGOODQ2, Q3Five proxy hosts are covered atomically by Q2; the DNS fallback by Q3. The port is unknown, so no port-based detection is possible or shipped.
11 · Linux Sliver stagingPARTIALQ8, Q10, Q2The staging behavior is covered. The Sliver end state is not confirmed by the primary source, so no Sliver protocol detection is shipped — inventing one on secondary reporting would be a fabricated indicator.
12 · Credential exposureOUT OF SCOPE§14No EDR coverage. Identity, registry and cloud audit logs own this, and §14 Phase 2 step 5 defines the enumeration.

Summary

  • 14 CQL queries — 6 high-confidence / low-FP, 6 medium with mandatory tuning, 2 discovery queries requiring a per-tenant allowlist before use.
  • 4 Custom IOA recommendations, 2 of which are block-capable after a 48-hour monitor period.
  • 24 atomic indicators in the Falcon import CSV — 14 blockable domains, 3 monitor-only leads, 7 hashes. All 24 trace to a saved source snapshot.
  • 3 steps with no coverage (2, 4, 9c) and 5 partial (1, 3, 9a, 10a, 11). Steps 2 and 4 are unfixable by design. Step 9c is the one that should change your response posture, not your query set.

What would improve coverage most, in order

  1. Sensor coverage on ephemeral CI runners and container build stages. Every query in this pack is worthless on a host with no sensor, and that is precisely where this campaign lands with the best credentials available.
  2. Confirm Falcon Advanced Memory Scanning is enabled on developer and CI host groups. It is the only coverage for step 9c.
  3. Complete DNS telemetry from macOS and Linux sensors (gate G1). Half this pack's strongest detections are DNS-based.
  4. A malware-aware registry proxy. It moves detection left of everything here — Sonatype found this campaign that way.
16

Hunt Summary Ticket

HUNT: Flooding Dropper / WEL1DROPPER npm slopsquatting campaign
VERSION: v1.0 · 2026-08-08
PRIORITY: P1 -- Critical
TRACKING: sonatype-2026-005660 · CWE-506 · CVSS 8.7
DISCLOSED: 2026-08-05 (Sonatype) / 2026-08-06 (OpenSourceMalware) / 2026-08-07 (press)

SUMMARY
  Roughly 800-850 AI-slopsquatted npm packages deliver WEL1DROPPER, a
  cross-platform downloader staging OS-specific RAT/infostealer payloads for
  Windows, macOS and Linux. The packages carry NO preinstall/postinstall hook --
  the README instructs the developer to require() the package, and that import
  detonates the chain. Install-time scanners and ignore-scripts controls do not
  see it. Primary HTTPS delivery from three Cloudflare Workers hosts, with a DNS
  TXT chunked-payload fallback under wel1[.]ru that survives an HTTPS blocklist.

SCOPE
  Developer workstations AND CI runners / build agents, equally. Also: container
  build stages, internal npm mirrors and caches, and any runner image that bakes
  a lockfile or a warm node_modules.

HYPOTHESES: 11 (see section 3)
QUERIES: 14 CQL + 3 pivots (section 8)
IOAs: 4 recommended, 2 block-capable after 48h monitor (section 9)
ATOMIC IOCS: 24 in the Falcon import CSV -- 14 blockable domains,
             3 monitor-only leads, 7 SHA-256 hashes (section 10)
GATES: 10 validation gates (section 11). G1, G3 and G10 are the load-bearing ones.

RUN ORDER
  1. Q1, Q2  -- campaign infrastructure, full retention, near-zero FP
  2. Q7, Q8, Q11 -- staged payload and persistence artifacts, exact names
  3. Section 7.1 dependency sweep -- the only way to find the transitive case
  4. Q3, Q4, Q6, Q12, Q14 -- behavioral, needs a baseline to read
  5. Q5, Q13 -- discovery, build the allowlist before treating as alerts

ESCALATE IMMEDIATELY ON
  Any Q1 or Q2 hit                      -> confirmed contact with campaign infra
  Any Q7 or Q8 hit                      -> confirmed payload staged and executed
  Q11 confidence=campaign-exact         -> confirmed macOS persistence
  Q9/Q10 match_kind=campaign-filename   -> confirmed Windows persistence
  Q13 match_tier=named-sample           -> package resolved; check 7.2 for import

KNOWN GAPS (do not report these as clean)
  - require() itself and the OS fingerprint produce no telemetry (steps 2, 4)
  - Windows stage 3 is loaded reflectively in memory -- NO CQL coverage (step 9c);
    depends entirely on Falcon Advanced Memory Scanning
  - No confirmed C2 protocol, beacon interval or exfil destination exists in any
    source, so this pack ships none. Absence of an exfil IOC is not absence of exfil.
  - Sliver is reported by secondary sources and explicitly NOT confirmed by the
    primary analysis; no Sliver protocol detection is shipped
  - Payload polymorphism: the 7 hashes are single-sample facts, not coverage

STANDING ASSUMPTION
  A confirmed import is a host compromise. Rebuild, do not clean -- stage 2
  patches ETW and AMSI, so the instrumentation you would scan with is the
  instrumentation that was tampered with. Rotate npm, GitHub, cloud, signing and
  deployment credentials AFTER eradication, never before.

OWNER: ____________________   OPENED: 2026-08-08   DUE: ____________________
17

Changelog

v1.0 2026-08-08 Initial release. Six sources fetched and snapshotted; two primary (OpenSourceMalware reverse-engineering writeup, Sonatype Research Labs campaign analysis), two corroborating, one trend note, one aggregator retained but not cited. 14 CQL hunt queries, 3 pivot queries, 4 Custom IOA recommendations, 24 atomic indicators all traced to saved snapshots, 10 validation gates, 3-tier hardening with 4 deployable playbooks carrying prerequisites, reboot requirements and rollback, and a 4-phase containment runbook. Coverage map records 3 uncovered and 5 partially covered chain steps, including the reflective in-memory Windows stage 3 for which no CQL coverage is possible.
Next review Re-pull the authoritative package list weekly while the campaign is active — the actor publishes from disposable accounts and Sonatype notes the naming convention is already drifting away from the bigops / bnpl / 35.x.y heuristics. Re-assess the three tcsbank / cloudpayments lead hosts by 2026-09-08. Update this pack if the final Windows or macOS beacon is recovered and analyzed, which would supply the C2 and exfiltration indicators this version deliberately omits, or if the Sliver attribution for the Linux final stage is confirmed by the primary analyst.
18

References

Sources fetched 2026-08-08 and saved verbatim to FloodingDropper-WEL1DROPPER-npm-Hunt-sources/. Snapshot numbers match the file names in that directory.

  1. [01 · PRIMARY] OpenSourceMalware — "Russian AI Slopsquatting Publishes 700+ Malicious NPM Packages", Paul McCarty, 2026-08-06. opensourcemalware.com/blog/russian-ai-slopsquatting-npm-campaign — full reverse-engineering writeup and the origin of every indicator in this pack.
  2. [03 · PRIMARY] Sonatype Research Team — "'Flooding Dropper' Campaign Hits npm With Nearly 850 Malicious Packages", 2026-08-05. sonatype.com/blog/flooding-dropper-hits-npm-with-850-malicious-packages — campaign scale, advisory sonatype-2026-005660, and the Windows stage-2 analysis.
  3. [02 · TIER 2] The Hacker News — "Nearly 800 Malicious npm Packages Deliver Cross-Platform RAT and Infostealer", Ravie Lakshmanan, 2026-08-07. thehackernews.com/2026/08/nearly-800-malicious-npm-packages.html — independent corroboration of hosts, payload domains and the WEL1DROPPER name.
  4. [05 · TIER 2] DevOps.com — "'Flooding Dropper' Is Hitting npm With a Tidal Wave of Malicious Packages", Jeff Burt, 2026-08-07. devops.com/flooding-dropper-is-hitting-npm-with-a-tidal-wave-of-malicious-packages — package naming, campaign scale and registry-moderation context.
  5. [06 · TIER 2] CyberPress — "Russian AI Slopsquatting Campaign Floods npm With Malicious Packages Targeting Developers". cyberpress.org/russian-ai-slopsquatting-campaign-floods-npm — growth trend and shared Moika tradecraft.
  6. [04 · NOT CITED] CyberTechWorld aggregator repost of the THN article. Snapshot retained for completeness; truncated syndication with no independent technical content and no indicators. Nothing in this pack derives from it.

Framework references

  • MITRE ATT&CK Enterprise — techniques and mitigations cited throughout §6 and §12. attack.mitre.org
  • CIS Software Supply Chain Security Guide — registry proxy, build-pipeline and dependency-review controls (§12).
  • CIS Microsoft Windows Benchmark §18, and the Microsoft Security Baseline for Windows 11 — AppLocker/WDAC and ASR controls (§12, Playbook A).
  • CIS Distribution Independent Linux Benchmark §1.1.2–1.1.5 — noexec,nosuid,nodev on /tmp and /var/tmp (Playbook B).
  • CIS Apple macOS Benchmark §2 and §5 — LaunchAgent and user-directory controls (Playbook C).
  • CIS Controls v8 §4, §5, §6, §13, §16 — DNS control, credential hygiene and application-software security.
  • NIST SP 800-218 (SSDF) and NIST SP 800-81 — SBOM diffing and secure DNS deployment (§12).

Provenance note. Every atomic indicator shipped in §4 and §10 was mechanically traced back to the saved text of the sources above. Indicators that exist in reporting but could not be traced — a C2 protocol, a beacon interval, an exfiltration endpoint, the package-proxy port — are absent from this pack by design rather than by oversight, and are listed as intel gaps in §2 and in the JSON appendix.