Home/ Blog/ Security news/ Article
Blog · Security news

Malicious npm packages skip install scripts and hide their C2 in DNS to drop a cross-platform stealer

Flooding Dropper seeded close to 800 malicious npm packages that run on require() and fall back to DNS TXT records for C2. How to detect it and clean up.

Grid of small parcels on a dark surface, a few lids slightly open

For a year the npm security conversation has been about install scripts. Block preinstall and postinstall, sandbox them in continuous integration, and you close the door most malicious packages walked through. A campaign that seeded close to 800 packages in early August walks through a different door. It carries no lifecycle hook at all. The package README asks the developer to import the library, and the malware fires on require(), an action every build runs thousands of times. The control that stopped last year's attacks does not fire here, and that is the design.

Researchers at OpenSourceMalware tracked 788 packages published between August 4 and 6, 2026, tagged the loader WEL1DROPPER, and Sonatype named the wider effort Flooding Dropper. The names were machine-generated to look plausible rather than to imitate a specific popular package. The payload is a downloader that fingerprints the host and pulls an operating-system-specific second stage: a Windows executable, a macOS binary, and on Linux a statically linked implant that analysts assess to be the Sliver command-and-control framework. It steals credentials and cryptocurrency wallet data and gives the operator remote command execution.

It routes around the controls you already deployed

The interesting part is not the stealer. It is that almost every technical decision in this campaign is aimed at a specific defensive control that a lot of shops turned on over the past two years. Read it as a map of what defenders got right, and where the attacker moved next.

Source: OpenSourceMalware and Sonatype analysis of the Flooding Dropper campaign, August 2026.

The DNS fallback is the tell

The downloader first tries three rotating hosts on a serverless platform. If that fails, it does something most commodity loaders do not: it queries a DNS TXT record to learn how many payload chunks exist, accepts a count between 1 and 2,000, then pulls each numbered chunk as a base64 string and reassembles the binary. That fallback exists for one reason. Outbound web traffic to an odd host gets caught by a proxy allowlist or an egress rule. Outbound DNS on port 53 almost never does, because blocking it breaks everything. So the center of gravity for catching this shifts off the perimeter and onto DNS query logging. If your build hosts and developer laptops are not sending resolver logs somewhere you can search, this technique is invisible to you. This is the same move we flagged when the Cavern C2 abused trusted IT-provider tooling: the attacker picks the channel your monitoring is least likely to watch.

Removing the package is not the cleanup

The reflex when a bad dependency turns up in a lockfile is to delete it, rebuild, and move on. That reflex is wrong here, and it is worth being loud about. If the code ran, and on import it runs by default, then it may have already written persistence: a Run key and scheduled task on Windows, a LaunchAgent on macOS, a detached process on Linux. Persistence outlives the package. You can purge every trace of the dependency from your project and the implant still calls home after the next reboot. We made the same point about session-stealing malware where a password reset does not evict the attacker: the artifact you can see is not the same as the foothold you have to remove. Treat a confirmed import as host compromise. Hunt for the dropped binaries in temporary directories, find and remove the persistence, and rotate the secrets a developer machine holds: npm tokens, cloud keys, and source-control credentials.

Why your scanner stayed quiet

Two of the campaign's choices exist specifically to blind common tooling, and both deserve a hard look at your own stack. The first is naming. A classic supply-chain scanner scores how close a new package name sits to a known, popular one, on the theory that expres is probably an attack on express. Generate plausible but unrelated names and there is no near match to trigger the heuristic. This is where the self-spreading npm worm and this campaign differ: a worm poisons real, known packages, while machine-generated names slip past the similarity check entirely. The second trick is a fake analytics layer: the malicious logic sits inside a roughly 80 KB file that imitates a telemetry SDK, so a quick human review sees what looks like ordinary usage reporting. It is the same trust gap that let signed npm packages get waved through. Neither trick is exotic. Both tell you that name-based and eyeball-based checks are the wrong layer to rely on for this class of attack.

There is also an anti-analysis detail worth knowing if you detonate samples: the loader drops a marker file and refuses to run again for at least six hours. A sandbox that re-executes the package to confirm behavior can see nothing on the second pass and wave it through. If you lean on dynamic analysis, account for that rate limit or you will get a false clean.

Turn on DNS logging before you go hunting

The first move is not a scan of your dependency tree. It is making sure you can see the two signals that actually expose this: DNS resolver logs and host process telemetry from build servers and developer endpoints. With those flowing, hunt for a short list of behaviors: TXT-record lookups to subdomains you do not recognize, especially in bursts of sequentially numbered queries; executables written to temporary paths and launched right after a package install or a developer opening a project; and freshly created persistence, a new LaunchAgent, Run key, or scheduled task, timestamped close to a build. This is behavioral detection, and it is the layer a managed detection service is built to watch: file integrity monitoring on the persistence locations, process auditing on the endpoints, and DNS visibility tied together, rather than trusting the package manager to be honest. If you want the query patterns and where to run them, our threat hunting approach starts from the same host-and-DNS signals this campaign was built to dodge.

Attribution is unsettled. OpenSourceMalware notes a .ru command domain and references to Russian financial brands in the code, which they read as a possible Russian-linked operator, and they suggest this may extend an earlier campaign they tracked as Moika. Take that as reported, not confirmed. What is not in doubt is the pattern. Every hard-won control against npm attacks, install-script sandboxing, egress filtering, name scanning, lockfile hygiene, was answered here by a deliberate move around it. The lesson is not a single indicator to block. It is that the package manager is not where you win this fight. The host is.

Control most teams runHow the campaign sidesteps itWhere you still catch it
Block or sandbox install scriptsNo lifecycle hook. The README tells the developer to import the package, and code runs on require().A process spawned by node writing to temporary paths, not the install step.
Egress filtering and proxy allowlistsThe primary fetch uses rotating hosts on an abused serverless platform; when that is blocked it rebuilds the payload from DNS TXT records.DNS query logs for TXT lookups to attacker subdomains.
Typosquat and name-similarity scanningMachine-generated names have no near neighbor to a known package, so edit-distance scoring has nothing to flag.Behavior after import, not the string.
Remove the dependency, rebuild the lockfilePersistence is already installed and survives uninstalling the package and rebooting.Host persistence artifacts and the dropped binaries.
Topics

Frequently asked questions

What is the Flooding Dropper npm campaign?

Flooding Dropper, also tracked as WEL1DROPPER, is a campaign that published close to 800 malicious npm packages between August 4 and 6, 2026. Each delivers a cross-platform downloader that pulls a remote access trojan and credential stealer for Windows, macOS, and Linux.

How does the malware run without an install script?

It runs on import. Instead of a preinstall or postinstall hook, the package README tells developers to load it with require(), and the entry point quietly starts the download. Blocking npm lifecycle scripts, a common defense, does not stop this technique.

How do I detect it if my dependency scanner missed it?

Watch host and DNS telemetry, not the package name. Look for TXT-record lookups to unfamiliar subdomains, executables dropped to temporary paths, and new persistence such as a LaunchAgent, Run key, or scheduled task created shortly after a build or a developer opening a project.

Is removing the package enough to clean up?

No. Once the code ran on import, it may have installed persistence that survives uninstalling the package and rebooting. Treat a confirmed execution as host compromise: remove the dropped binaries and persistence, rebuild if needed, and rotate npm, cloud, and source-control credentials.

Why did name-based scanners not flag the packages?

The names were generated to look plausible rather than to imitate a specific popular package. Scanners that score how close a name sits to a known package have no near match to flag, so these packages pass a similarity check that would catch a classic typosquat.

Who is behind the campaign?

Attribution is not settled. OpenSourceMalware reports a .ru command domain and references to Russian financial brands in the code, which they read as a possible Russian-linked operator, and suggests it may extend an earlier campaign tracked as Moika. Treat this as reported, not confirmed.

Ready to meet the Guardians?

Deploys fast - agentless for monitoring and cloud, a lightweight agent for deep endpoint security. Just Suriq, standing watch.