PolinRider, Again: Stolen npm Tokens Hit Ten CDMBase Packages
By Raphael
Well, here we go again. Same campaign, same loader, and a maintainer who has been here before.
On November 3 and 4, 2026, someone with what the maintainer says were stolen npm publish tokens pushed twelve malicious versions of ten packages maintained by CDMBase: the @adminide-stack, @cdmbase and @pubngo-stack scopes, cdeops and server-hemera-mongo. Every one of them carries the PolinRider loader. Import the package and it looks up a command server on the Ethereum blockchain, downloads the next stage and runs it in a hidden Node process that keeps going after your app exits.
If any of these versions is in your lockfile and was imported, treat the machine as compromised. All twelve were unpublished between 19:42 and 19:48 UTC on November 4. Until then, eleven carried only the maintainer's deprecation notice, and the twelfth, @pubngo-stack/help-browser 12.0.23-alpha.3, had none and was that package's latest version.
Ah crap, here we go again
If you've been following npm malware this year, you can probably skip to the indicators. PolinRider has been publicly tracked since at least early 2026, and by July it had been reported across 108 packages on npm, Packagist and Go, plus a Chrome extension, many of them published from compromised maintainer accounts. Since then it has turned up in hijacked npm packages, in nearly two thousand GitHub repositories pushed from infected developer machines, and alongside GhostAction. Its builds even carry version tags in a Sec-V header: researchers have logged A8, A9 and A10 builds, and this wave is A10.
These packages have their own history, too. On August 4, the Shai-Hulud worm published malicious versions into the same scope (SFTY-20260804-60166 for @adminide-stack/clock-tik-browser, SFTY-20260804-19781 for server-hemera-mongo 0.0.12). Different malware, and probably not where this week's tokens came from. Two days before the first malicious publish, PolinRider's GitHub worm pushed its usual payload into one of the maintainer's own public repositories, so the publish tokens most likely came off a developer machine it had already infected.
What actually shipped
All times UTC.
Two maintainer accounts, clockbook-app on the 3rd and pubngo-stack on the 4th, both long-standing maintainers on these packages. The maintainer has since deprecated every bad version but one with the same note: "malicious version published with a stolen token. Do not install."
At this time, the latest tag points at a clean version on every one of these packages. @pubngo-stack/help-browser was the exception for most of November 4: its malicious 12.0.23-alpha.3 stayed latest, so a plain npm install pulled the loader, until all twelve were unpublished at around 19:48 UTC. The registry doesn't keep a history of dist-tags, so it can't tell us whether the others were latest while they were up, and four of them (server-hemera-mongo 0.0.15 and 0.0.16, cdeops 13.5.31, yantra-help-browser 12.0.194) aren't even prereleases. Assume a fresh install during the window could have pulled one.
They're the same picture
The first thing I did was diff client-redux 0.0.16-18 against 0.0.16-17, its last release, from July 2018. Two files changed: the version in package.json, and the end of lib/index.js:
Someone took an eight-year-old tarball, added one line and published it again. With 131 tabs in front of it, the code sits off the right edge of your editor, and it runs the moment anything imports the package. There's no install hook, so --ignore-scripts doesn't help.
Under that line is the loader earlier reports already describe: the C2 address hidden in the "to" address of a transaction from one Ethereum wallet (a trick researchers have nicknamed NullReceiver), the /0x/cls and /0x/ls stages, the XOR keys, the Sec-V marker header and the detached Node process. It's even the same wallet and IP that were published as PolinRider infrastructure in late October.
One fun detail from the wallet itself: it has sent 232 transactions since September 4, and since October 16 it has posted one about every three hours and twenty minutes, every one to the same address:
That's 95.135.181[.]151, twice, followed by the text "helloipbot!!". Hello to you too.
What's actually new in this wave
A few things I didn't find in the public reports I read. If they've been covered elsewhere, I'm happy to be corrected.
utils 0.0.16-130, forms-core, yantra-help-browser, cdeops, wiki-browser and help-browser) drop eval(atob(...)) for a small character-shuffling cipher, and build Function from a function's "constructor" property. Decoded, the loader is byte-for-byte the same, but there's no eval or atob anywhere in the file, so a rule keyed on those walks right past it. utils 0.0.16-130 went up 48 minutes after 0.0.16-129, which looks a lot like someone checking what gets caught. The cipher itself isn't new: PolinRider's GitHub-push component already uses it in build config files, where community scanners call it the "build-config rider".node -i process, started detached and hidden and then released with unref(), so it keeps running after your app exits.A10-npm_inject; the cipher builds send A10 plus a short tag (-II, -E, -% or -1234567890), shared across packages.cdeops 13.5.31 only the CommonJS files were touched. The .mjs files are clean, so whether you're hit depends on how you import it.Removing the package does not fix this
The versions are gone from npm now, but that doesn't help a machine that already imported one. The loader has already run, and the second stage lives in its own process. A fresh npm ci against a lockfile that pins one of them will now fail with a 404, which is a handy tell. Internal mirrors and CI caches can still serve them, though. If one of these versions was imported on a machine or in CI since 22:00 UTC on November 3:
grep -rlE 'A10-npm_inject|function spin\(src, c\)' node_modulesnode -i processes with no terminal attached, and connections to 95.135.181[.]151. Kill them and block the IP.utils 0.0.16-128, client-redux 0.0.16-17, health-api-server 0.0.18-alpha.464, user-activity-browser 1.1.3-alpha.113, yantra-help-browser 12.0.195, forms-core 5.1.9-alpha.4, wiki-browser 12.0.18-alpha.54, cdeops 13.5.29, server-hemera-mongo 0.0.11 (not 0.0.12, that's the August one), help-browser 12.0.23-alpha.2. Clear CI and build caches so the tarballs don't come back.If you maintain packages yourself: rotate your publish tokens and publish from CI with trusted publishing, so a token on a laptop isn't enough on its own.
Indicators
Defanged.
Packages (npm)
@adminide-stack/utils: 0.0.16-129, 0.0.16-130@adminide-stack/client-redux: 0.0.16-18@adminide-stack/health-api-server: 0.0.18-alpha.465@adminide-stack/user-activity-browser: 1.1.3-alpha.114@adminide-stack/yantra-help-browser: 12.0.194@adminide-stack/forms-core: 5.1.9-alpha.3@cdmbase/wiki-browser: 12.0.18-alpha.53cdeops: 13.5.31server-hemera-mongo: 0.0.15, 0.0.16@pubngo-stack/help-browser: 12.0.23-alpha.3Files (SHA-256)
292a7692b10b3c93f1757d2a0f24148c1da6dc71d1f352acb5d5e9ff2661adee lib/index.js, utils 0.0.16-12982bcbf53406943004d532c08f896320fc19eced1ad209d0463f03e74ee2efdf3 lib/index.js, utils 0.0.16-130lib/index.js, client-redux 0.0.16-18c4b3cea95faf1294294285dd17b9e64fec6df8ad129204e7259868d4af4734f7 decoded loader, base64 variant (all six)c963e920baa12f4a5ea2be53bc4640d29263b247665a90846738ac64ff0e1911 decoded loader, cipher variant (all six)Network
0x1eEf0849EDa15d87f64e51C73C8442Cf3c8bCEF3 (Ethereum sender wallet)95.135.181[.]151 port 443, paths /0x/cls and /0x/ls (port 80 is passed to the second stage), C2 since Oct 16166.88.134[.]75 (Sep 4 to 12), 103.179.143[.]219 (Oct 14 to 16), 23.27.13[.]72 (one transaction, Oct 14)1rpc[.]io eth[.]drpc[.]org ethereum-rpc[.]publicnode[.]cometh-mainnet[.]public[.]blastapi[.]io eth[.]blockscout[.]com/apiThe RPC hosts are legitimate services. Calls to them from a build machine that has no business talking to Ethereum are the signal.
Hunt for
global.i = 'A10-npm_inject', or 'A10' plus a tag, in installed JavaScripteval(atob( at the end of a bundlefunction spin(src, c), and Function built from a "constructor" propertyq4FZkxX{!h,Sr3=@ and y-p_>d$0B&@^1aQknode -i processes with no terminal attachedHow Can Safety Help Protect You?
This loader doesn't wait for an install script. It runs the first time anything imports the package, in your app, your tests or your build, so by the time it's in node_modules it's one require away from running. The Safety Firewall checks every package installation request before it reaches the public registry, so known-malicious versions like these are blocked before they ever land, on developer machines and in CI alike.
Want to try Safety Firewall? You can sign up and get started yourself, no call required.