```html ```
top of page

788 Malicious npm Packages Shared One Email Domain and One Version Number. Five Days Later, 15% Are Still Installable.

  • Writer: Patrick Duggan
    Patrick Duggan
  • 3 hours ago
  • 7 min read

On August 6, OpenSourceMalware published a campaign of nearly 800 malicious npm packages delivering a cross-platform RAT and infostealer through a downloader they named WEL1DROPPER. Sonatype tracks the wider wave as sonatype-2026-005660 and counts 846 components. The Hacker News and SC Media picked it up the following day.


The reporting gives defenders a list of package names. Lists of package names are the least durable thing you can be handed, because the next campaign uses different ones.


So we pulled the campaign's package list, ran it against our own malicious-package deny-list, ran a spread sample against the live npm registry, and looked at what the 787 records have in common besides being malicious. Here is what the measurement says.



What is actually novel: it does not use install hooks


Almost every npm supply-chain attack you have read about fires through a lifecycle hook — preinstall or postinstall — because that is what runs code automatically when a dependency lands.


This campaign does not use them.


The malicious code executes on import. The README tells the developer to load the library with require("checkout-mobile-bnpl"), and importing it is enough. WEL1DROPPER then fingerprints OS and architecture, pulls a matching payload over HTTPS from a Cloudflare Worker, and falls back to DNS TXT records under wel1[.]ru if that path fails. The Windows and macOS builds attempt to disable ETW and AMSI, establish persistence, and stage further payloads.


Removing the install hook is not a small change. It is the whole point. An enormous amount of supply-chain tooling — CI scanners, --ignore-scripts policies, hook-monitoring agents, sandboxed install steps — is built on the assumption that malicious npm code runs at install time. Take the hook out and every one of those controls returns clean, correctly, on a package that is fully malicious.


This is the pattern we keep writing about: the attack wins by deleting the artefact the defence is looking for. Not by evading the check. By making the check accurate and irrelevant at the same time. We saw the same move last week in the npm worm that survived cleanup by living in agent configuration files rather than in package code.



Three tells, one string each


Now the part the package lists obscure. We looked at the 787 records for shared structure.


Tell one: every single package shares one publisher email domain. All 787 registrations use an address at web-library.net. Not most. All of them. They are spread across 582 distinct publisher accounts with randomised-looking handles — dexwilt-5g-xn1, tabrex-u_ptoz, vaevt7emkzg8 — which is clearly an attempt to look like 582 unrelated developers. The account names were randomised. The email domain was not.


Tell two: 726 of the 783 packages we hold were published as version 35.x. Ninety-five point eight percent of the campaign's versions carry a major version of 15 or higher. A brand-new package, created this month, published by an account created this month, whose first release is 35.4.4. Real software does not arrive at major version thirty-five on its first day. This is version inflation — a dependency-confusion technique, where you publish a public package with a version number high enough to beat whatever the target's internal registry holds.


Tell three: the shape of the names. Alongside fake-scope clones of genuinely popular libraries — @dexwilt/node-fetch, @wrenfield/viem, @tabrex/bs58 — there are long runs of names that look nothing like typosquats: beaver-ui-card-large, beaver-ui-split-view, bpm-foundation-testing, devplatform-spa-tokens, sso-tramvai-module-context-auth. Some are doubled in a way that reads like a machine scraped a manifest and concatenated fields: arbocrate-sla-prober-arbocrate-sla-prober-core, bi-core-bi-core-core.


Those are not squats on public packages. Those are internal, private package names. tramvai is a real open-source frontend framework out of a Russian bank; sso-tramvai-module-context-auth is what an internal SSO module inside a tramvai application would plausibly be called. Combined with the version inflation, this is dependency confusion aimed at specific enterprise development teams whose internal manifests leaked somewhere.


The campaign has been described as AI-generated slopsquatting. Part of it plainly is. But a meaningful share of these names could not have been guessed, and the version numbers say the operator expected an internal registry to be on the other side.



The timeline says it was visible for three weeks


The registration dates are the most useful column in the file and nobody has printed them.


Two packages on July 13. Two on July 14. Seven on July 18. One on July 31. Six on August 4.


Then 169 on August 5, and 600 on August 6.


Eighteen packages across three weeks, followed by 769 in forty-eight hours. That is a staging period and a burst, and it is the same shape we see in scraper and scanner traffic at our own edge: a small, slow, unremarkable run-in while the operator proves the pipeline works, then everything at once when it does.


The eighteen were not hiding. They carried the same web-library.net publisher domain as the 769 that followed. A registry-side detector keyed on "new publisher email domain now behind more than N packages" would have fired somewhere in the July 13–18 window, three weeks before the flood, on a signal that required no malware analysis at all.


We are not claiming we caught it in that window. We did not — we are reading these dates after the fact, like everyone else. We are saying the signal was in the registration metadata the whole time, and it was one string.





We measured our own coverage, and then we measured npm's


Our malicious-package feed carries 231,028 curated entries across npm and PyPI, sourced from OSV. Against the 787-package campaign list:


783 of 787 are on our deny-list. 99.5%.


We want to be precise about what that number is and is not. That coverage is redistribution, not detection. OSV curated these entries; we carry OSV. Anybody pulling the same public feed had identical coverage on the same date. We did not find this campaign, OpenSourceMalware did, and the credit for the early listing belongs to them and to OSV. What our feed provides is distribution — one endpoint, no account, no rate limit, for people who are not going to build an OSV ingestion pipeline this quarter.


The four we do not hold are @npmresearch4/utils, npmresearch2026-prov-test, npmresearch2026-scope-test and npmresearch2026-scope-test-b. They carry the same web-library.net publisher domain as everything else, so we are treating them as genuinely missing rather than waving them off as somebody's test artefacts.


Then we asked the question that actually matters to a developer this morning: are these still installable?


We took a deterministic spread sample of 60 packages across the campaign list and queried the live npm registry for each. 42 were gone — unpublished or 404. 9 were replaced with security-holding placeholders. And 9 were still fully live with installable versions, five days after public disclosure.


That is 15% of the sample still shipping. @dexwilt/[email protected]. [email protected]. [email protected]. [email protected].


We then checked those nine against our own feed. All nine are blocked, with the exact malicious versions matched — MAL-2026-11203, MAL-2026-12844, MAL-2026-13110, MAL-2026-12516, MAL-2026-12750, MAL-2026-12785, MAL-2026-12066, MAL-2026-12073, MAL-2026-12226.


That gap is the argument for consuming a deny-list at all. Registry takedown is not synchronous, is not complete, and is not something you get notified about. A defender relying on "npm will remove it" was, on our sample, unprotected against roughly one in seven of these packages this morning. A defender checking names against a curated list before install was covered against all of them, including the ones npm has not touched.


We are capping that claim at the sample we ran: 60 packages of 787, one point in time, one registry endpoint. The direction is not ambiguous, but the exact percentage will move.



Indicators


Infrastructure from the OpenSourceMalware write-up, which we have added to our feed under manual-batch-wel1dropper:


DNS-based payload delivery lives under dl.wel1[.]ru, with sdk.dl, ext.dl, pkg.dl and net.dl subdomains observed. Primary HTTPS delivery runs through Cloudflare Workers at oob-worker.cf103-070.workers.dev, oob-worker.cf102-baf.workers.dev and oob-worker.cf99-9b3.workers.dev, with additional macOS proxy hosts in the cf5oobworker through cf11oobworker workers.dev range. Payload hashes: Linux [7e486657f30594afda379b97030252a09a19fe8055e25c9e371544f59bd8e9e3](https://analytics.dugganusa.com/stix/register?ref=hash-click&q=7e486657f30594afda379b97030252a09a19fe8055e25c9e371544f59bd8e9e3), macOS [c214746c74cae8ece8bdaf69aa05da4db6ce013f9e77452d1eed1a002fd9ba00](https://analytics.dugganusa.com/stix/register?ref=hash-click&q=c214746c74cae8ece8bdaf69aa05da4db6ce013f9e77452d1eed1a002fd9ba00).


None of these were in our corpus before today — we checked each one and every lookup came back not found. They are in now.


The publisher email domain web-library.net is the highest-value string on that list and the one least likely to appear in a blocklist product, because it is not network infrastructure. It is registry metadata. If you operate a registry, a mirror, or an internal proxy, that is the one to key on.



What to do


Stop relying on `--ignore-scripts` as your supply-chain control. It is still worth having. It does nothing here. This campaign runs on import, and every hook-based defence returns clean.


Check names before install, not after. Our deny-list is public at analytics.dugganusa.com/api/v1/stix-feed/packages.json, and there is an MCP tool that answers a single package-and-version query so an AI coding agent can refuse a poisoned dependency in its pre-install step. No key, no account. If you would rather pull OSV directly, do that — it is the same underlying data and we would rather you were covered than that you used ours.


If you run a private registry, hunt for version inflation. A public package whose first-ever release is major version 20 or 35 has told you what it is before you have read a single line of its code. That check costs nothing and it does not expire when this campaign's names do.


And look at who publishes, not just what. Five hundred and eighty-two accounts, one email domain, 787 packages. The operator randomised the part that was visible on the package page and left the part that was visible in the registry metadata completely untouched.




Primary research and the campaign package list are OpenSourceMalware's, published August 6, 2026; Sonatype tracks the wider wave as sonatype-2026-005660. Deny-list entries are OSV-curated and redistributed by us, not originated by us. Our coverage figure, the version-inflation and publisher-domain analysis, the registration timeline, and the npm liveness sample are our own measurements against the published list, run August 11, 2026.




Every indicator in this post is in the feed. Free.

1.58M+ IOCs, STIX 2.1 / TAXII, 88% novel vs ThreatFox, exploited-CVE leads ahead of CISA. No credit card — a free API key in 30 seconds, and you can audit every claim above against the live endpoints.



Was this useful? Thirty seconds, no cookies, no tracking, no third parties, your address hashed and never stored. If the box below does not load, the same question lives at https://analytics.dugganusa.com/nps.html?post=wel1dropper-788-packages-three-single-string-tells



Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page