141 npm Packages Abuse Registry as Adware Hosting

SafeDep Team
6 min read

Table of Contents

A single npm account, terminal3airport ([email protected]), published 141 packages between May 7 and May 27, 2026. Every package contains the same payload: a web proxy unblocker built on the Scramjet framework, disguised as a tutoring website, monetized through popunder ads and external tracking scripts. The packages carry no install hooks and no credential stealers. The attack is npm registry abuse: using the registry as free, disposable CDN infrastructure for adware distribution.

TL;DR

Impact:

  • Users who visit the hosted pages are subjected to popunder ads opening hxxps://abdct[.]com/ on first interaction
  • External scripts loaded from cdn[.]21baseballacademy[.]com and woofbeginner[.]com execute in the user’s browser
  • All activity is tracked via Google Analytics G-0VL3ZSBXDH
  • The web proxy bypasses network content filters (school/corporate firewalls), routing traffic through Scramjet service workers
  • Discord OAuth handles user authentication, with .well-known/discord verification embedded in packages

Indicators of Compromise (IoC):

  • npm maintainer: terminal3airport / [email protected]
  • Domains: 21baseballacademy[.]com, cdn[.]21baseballacademy[.]com, abdct[.]com, woofbeginner[.]com
  • Google Analytics ID: G-0VL3ZSBXDH
  • GitHub: github[.]com/lucideproxy/svg
  • Discord verification hash: bb7ce9f4c508bc87f13889c18031ca42ad8c1bd5
  • 141 npm packages (full list below)

The Packages

terminal3airport-malicious-packages.csv
ecosystemnameversionpublished
1npmchangiairportpromax1.1.32026-05-07
2npmilovefemboys1.1.32026-05-07
3npmwhatsadmaidk1.1.22026-05-26
4npmbackup1-gg1.1.22026-05-26
5npmbackup2-asd1.1.22026-05-26
6npmbackup3-ff1.1.22026-05-26
7npmbackup4-gasp1.1.22026-05-26
8npmbackup5-updated1.1.22026-05-26
9npmbackupgenuine-updated1.1.22026-05-26
10npmfflc-updated1.1.22026-05-26
11npmmidnightrush1.1.22026-05-26
12npmpasirianspirit1.1.22026-05-26
13npmlowkeybored1.1.22026-05-26
14npmlowkirkuenly1.1.22026-05-26
15npmkirkland1.1.22026-05-26
16npmomgyesyesyes1.1.22026-05-26
17npmcaptainindia1.1.22026-05-26
18npmbomboclatwallahi1.1.22026-05-26
19npmthebigyahu1.1.22026-05-26
20npmomglucidesotuff1.1.22026-05-26
21npmcrazynut1.1.22026-05-26
22npmbismillahitidakimas1.1.22026-05-26
23npmhowmanygreatbritain1.1.22026-05-26
24npmmiguelphonk1.1.22026-05-26
25npmratelimitsucks1.1.22026-05-26
26npmratelimitsucks11.1.72026-05-27
27npmratelimitsucks21.1.72026-05-27
28npmratelimitsucks31.1.72026-05-27
29npmratelimitsucks41.1.72026-05-27
30npmratelimitsucks51.1.72026-05-27
31npmratelimitsucks61.1.72026-05-27
32npmbackupsitetuff31.1.72026-05-27
33npmbackupsitetuff61.1.72026-05-27
34npmbackupsitetuff91.1.72026-05-27
35npmbackupsitetuff101.1.72026-05-27
36npmratelimitsucks91.1.72026-05-27
37npmratelimitsucks101.1.72026-05-27
38npmtimmytuffknuckles31.1.72026-05-27
39npmtimmytuffknuckles61.1.72026-05-27
40npmtimmytuffknuckles91.1.72026-05-27
41npmsixseven11.1.72026-05-27
42npmsixseven21.1.72026-05-27
43npmsixseven31.1.72026-05-27
44npmsixseven41.1.72026-05-27
45npmsixseven51.1.72026-05-27
46npmsixseven61.1.72026-05-27
47npmspeed11.1.72026-05-27
48npmspeed21.1.72026-05-27
49npmspeed31.1.72026-05-27
50npmspeed41.1.72026-05-27
51npmspeed51.1.72026-05-27
52npmimillegal11.1.72026-05-27
53npmimillegal21.1.72026-05-27
54npmimillegal31.1.72026-05-27
55npmimillegal41.1.72026-05-27
56npmimillegal51.1.72026-05-27
57npmishowfeet11.1.72026-05-27
58npmishowfeet21.1.72026-05-27
59npmishowfeet31.1.72026-05-27
60npmishowfeet41.1.72026-05-27
61npmishowfeet51.1.72026-05-27
62npmishowfeet61.1.72026-05-27
63npmishowfeet71.1.72026-05-27
64npmishowfeet81.1.72026-05-27
65npmishowfeet91.1.72026-05-27
66npmishowfeet101.1.72026-05-27
67npmishowfeet111.1.72026-05-27
68npmishowfeet121.1.72026-05-27
69npmishowfeet131.1.72026-05-27
70npmishowfeet141.1.72026-05-27
71npmishowfeet151.1.72026-05-27
72npmishowfeet161.1.72026-05-27
73npmishowfeet171.1.72026-05-27
74npmishowfeet181.1.72026-05-27
75npmishowfeet191.1.72026-05-27
76npmishowfeet201.1.72026-05-27
77npmnottuff11.1.72026-05-27
78npmnottuff21.1.72026-05-27
79npmnottuff31.1.72026-05-27
80npmnottuff41.1.72026-05-27
81npmnottuff51.1.72026-05-27
82npmnottuff61.1.72026-05-27
83npmnottuff71.1.72026-05-27
84npmnottuff81.1.72026-05-27
85npmnottuff91.1.72026-05-27
86npmnottuff101.1.72026-05-27
87npmnottuff111.1.72026-05-27
88npmnottuff121.1.72026-05-27
89npmnottuff131.1.72026-05-27
90npmnottuff141.1.72026-05-27
91npmnottuff151.1.72026-05-27
92npmnottuff161.1.72026-05-27
93npmnottuff171.1.72026-05-27
94npmnottuff181.1.72026-05-27
95npmnottuff191.1.72026-05-27
96npmnottuff201.1.72026-05-27
97npmnottuff211.1.72026-05-27
98npmnottuff221.1.72026-05-27
99npmnottuff231.1.72026-05-27
100npmnottuff241.1.72026-05-27
101npmnottuff251.1.72026-05-27
102npmnottuff261.1.72026-05-27
103npmnottuff271.1.72026-05-27
104npmnottuff281.1.72026-05-27
105npmnottuff291.1.72026-05-27
106npmnottuff301.1.72026-05-27
107npmabuden11.1.72026-05-27
108npmabuden21.1.72026-05-27
109npmabuden31.1.72026-05-27
110npmabuden41.1.72026-05-27
111npmabuden51.1.72026-05-27
112npmabuden211.1.72026-05-27
113npmabuden221.1.72026-05-27
114npmabuden231.1.72026-05-27
115npmabuden241.1.72026-05-27
116npmabuden251.1.72026-05-27
117npmabuden261.1.72026-05-27
118npmabuden271.1.72026-05-27
119npmabuden281.1.72026-05-27
120npmabuden291.1.72026-05-27
121npmabuden2101.1.72026-05-27
122npmabuden2111.1.72026-05-27
123npmabuden2121.1.72026-05-27
124npmabuden2131.1.72026-05-27
125npmabuden2141.1.72026-05-27
126npmabuden2151.1.72026-05-27
127npmabuden2161.1.72026-05-27
128npmabuden2171.1.72026-05-27
129npmabuden2181.1.72026-05-27
130npmabuden2191.1.72026-05-27
131npmabuden2201.1.72026-05-27
132npmabuden2211.1.72026-05-27
133npmabuden2221.1.72026-05-27
134npmabuden2231.1.72026-05-27
135npmabuden2241.1.72026-05-27
136npmabuden2251.1.72026-05-27
137npmabuden2261.1.72026-05-27
138npmabuden2271.1.72026-05-27
139npmabuden2281.1.72026-05-27
140npmabuden2291.1.72026-05-27
141npmabuden2301.1.72026-05-27
141 rows
| 4 columns

All 141 packages are single-version publishes. Every package except package.json (which differs only in the name field) is byte-identical. SHA-256 of the shared index.html payload across the v1.1.2 and v1.1.7 generations: the content matches.

Three publishing waves stand out:

WaveDateVersionCountNotes
1May 71.1.32Readable code, Scramjet referenced directly
2May 261.1.223Obfuscated JS, randomized filenames
3May 271.1.7116Mass-published via auto-publish.sh script

How the Spam Works: auto-publish.sh

The v1.1.7 packages include the automation script that published them, left in the tarball:

backupsitetuff6/package/auto-publish.sh
#!/bin/bash
BASE="backupsitetuff"
TOTAL=10
PARALLEL=3
publish_one() {
NAME="$1"
echo "Building $NAME"
node -e "
const fs = require('fs');
const pkg = require('./package.json');
pkg.name = '$NAME';
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2));
"
npm publish --silent > /dev/null 2>&1
echo "Published: $NAME"
}
count=1
while [ $count -le $TOTAL ]
do
while [ $(jobs -rp | wc -l | tr -d ' ') -ge $PARALLEL ]
do
sleep 0.1
done
publish_one "${BASE}${count}" &
count=$((count + 1))
done
wait
echo "Done publishing all"

The script rewrites the name field in package.json, publishes, and moves to the next name. It processes 3 packages in parallel. The attacker ran variants of this script with different BASE prefixes (nottuff, ishowfeet, abuden, sixseven, speed, imillegal, ratelimitsucks, timmytuffknuckles, backupsitetuff) to produce the 116 packages in the May 27 wave. Package names like ishowfeet, ilovefemboys, bomboclatwallahi, and imillegal point to a teenage operator.

Package Structure and Entry Point

Every package.json sets main: "sw.js", but no install hooks exist. The packages are not designed to be imported as Node.js modules. They are static web assets meant to be served via a CDN or hosting provider. The sw.js file is a service worker for the Scramjet web proxy:

// sw.js (v1.1.2/v1.1.7 generation, obfuscated variable names replaced)
importScripts('./8cfc2/hgshm.js');
const { ScramjetServiceWorker } = loadWorker();
const proxySw = new ScramjetServiceWorker();
self.addEventListener('install', () => {
void self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim());
});

The service worker intercepts all fetch events on its origin, routes them through Scramjet’s proxy engine, and injects a script into every proxied HTML response. That injected script hooks window.open, anchor clicks, and form submissions to capture new-tab navigation and relay it to the parent frame via postMessage. The window.open.__lucideIntercepted flag names the project: “Lucide Proxy,” matching the GitHub repository URL github[.]com/lucideproxy/svg.

The SEO Disguise

The HTML pages masquerade as legitimate tutoring businesses. The v1.1.3 wave uses “Northstar Tutoring” branding; the v1.1.2/v1.1.7 waves use “Riverbend Tutoring.” Both claim to serve Portland, Oregon. The visible content is a loading page:

index.html
<title>Riverbend Tutoring | After-School Coaching &amp; Test Prep</title>
<meta
name="keywords"
content="academic coaching, after school coaching,
weekly tutoring, study skills coaching, executive function support,
college prep, ACT prep, SAT prep, AP exam prep, homework help..."
/>

A hidden <div style="display: none" aria-hidden="true"> block stuffs 800+ words of SEO keyword text about tutoring services, test prep, and study habits. Search engines index it; users never see it. The og:url metadata points to hxxps://21baseballacademy[.]com, linking the fake tutoring site to the ad infrastructure.

Adware Payload

The index.html loads three monetization layers:

1. Popunder ad (inline script):

// index.html, inline <script>
(function () {
var k = 'p7k2x';
var cd = 9e5; // 900,000 ms = 15 minutes cooldown
var u = 'https://abdct.com/';
function ok() {
try {
var t = +localStorage.getItem(k) || 0;
return Date.now() - t >= cd;
} catch (_) {
return true;
}
}
function fire() {
if (!ok()) {
done();
return;
}
try {
localStorage.setItem(k, String(Date.now()));
} catch (_) {}
try {
window.open(u, '_blank', 'noreferrer');
window.focus();
} catch (_) {}
done();
}
if (ok()) {
['click', 'keydown', 'touchstart'].forEach(function (e) {
document.addEventListener(e, fire, true);
});
}
})();

On the first user interaction (click, keypress, or touch), this opens hxxps://abdct[.]com/ in a new tab and refocuses the original window. It rate-limits itself to once every 15 minutes using localStorage. The use of capture-phase event listeners ensures the popunder fires before any other handler can prevent it.

2. External ad script:

<script src="https://cdn.21baseballacademy.com/script/jrqK2HPsliMjRW5Q.js" defer></script>

This domain serves additional monetization JavaScript. At the time of analysis, the endpoint returned an empty response.

3. Monetization script in the main bundle (v1.1.3 generation):

// assets/index-_PhgPvMS.js (extracted URL)
'https://woofbeginner.com/0a/91/35/0a913561831bdf2c26dcf18b852b5cc1.js';
'https://woofbeginner.com/jivd2xu8?key=c6851a038da578a80eeb201e0588c84c';

The woofbeginner[.]com domain loads additional ad scripts with a monetization key parameter. At analysis time, this endpoint returned a 500 error.

4. Google Analytics tracking:

<script async src="https://www.googletagmanager.com/gtag/js?id=G-0VL3ZSBXDH"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-0VL3ZSBXDH');
</script>

All 141 packages report user activity to the same Google Analytics property.

Obfuscation

The v1.1.3 wave (May 7) shipped readable source. Waves 2 and 3 ran the React application code through a hex variable name obfuscator, randomized file names (e.g., a3g0q43tbe.js, 73sxysj46r.js), and replaced all identifiers with _0x prefixed hex patterns:

// assets/a3g0q43tbe.js (first 200 chars)
(function(_0x1b35e9,_0x379ecc){const _0x47926c={_0x46fe72:0x7ae,
_0x53a4a0:0xaad,_0xdcea50:0xe56,_0x2a9cda:0xae0,...

The obfuscator builds a shuffled string array, then resolves literals at runtime through a rotation function and index-offset lookups. Directory names also changed: the Scramjet runtime moved from runtime/scramjet/ to 8cfc2/, baremux from runtime/baremux/ to d1g0y/.

The service worker (sw.js) and index.html stayed readable across all waves. The popunder code, external script URLs, and analytics IDs sit in plain text.

Discord Authentication

The application uses Discord OAuth for user authentication. A .well-known/discord file in the v1.1.2/v1.1.7 packages contains the verification hash bb7ce9f4c508bc87f13889c18031ca42ad8c1bd5. The React bundle includes a Discord sign-in flow that sends verification codes to users via Discord DM.

The AI branding SVGs in branding/ (Anthropic, OpenAI, DeepSeek, xAI, Gemini, Lucide) and a Roblox shortcut SVG suggest the proxy targets students who want to access AI chatbots and gaming sites from school networks.

Conclusion

These packages do not steal credentials, install backdoors, or compromise build pipelines. They abuse npm as free static hosting for an ad-monetized web proxy targeting students. Anyone who lands on these pages through search results or shared links gets popunder ads, third-party tracking, and a service worker that intercepts all their proxied web traffic.

The auto-publish.sh script left in the tarball, the juvenile package names, and the “TY WAVES + CHATGPT ILY” comment in the service worker point to a young operator monetizing a Scramjet-based proxy unblocker through ad fraud. One account published 116 packages in under 35 minutes. npm had no rate limit to stop it.

References

  • npm
  • malicious-package
  • supply-chain-security
  • adware
  • registry-abuse
  • malware

Author

SafeDep Logo

SafeDep Team

safedep.io

Share

The Latest from SafeDep blogs

Follow for the latest updates and insights on open source security & engineering

Polymarket npm Packages Steal Crypto Wallet Keys

Polymarket npm Packages Steal Crypto Wallet Keys

Nine coordinated npm packages target Polymarket traders with a social-engineered postinstall prompt that exfiltrates raw private keys to a Cloudflare Worker. The attacker published all packages...

SafeDep Team
Background
SafeDep Logo

Ship Code.

Not Malware.

Start free with open source tools on your machine. Scale to a unified platform for your organization.