---
title: "141 npm Packages Abuse Registry as Adware Hosting"
description: "npm account terminal3airport published 141 packages containing a web proxy unblocker disguised as tutoring websites. The packages load popunder ads, external monetization scripts, and Google Analytics tracking, using npm as free static file hosting. An auto-publish shell script automates the spam at scale."
url: "https://safedep.io/blog/malicious-npm-terminal3airport-proxy-adware-spam"
author: "SafeDep Team"
published: "2026-05-27"
---

# 141 npm Packages Abuse Registry as Adware Hosting

A single npm account, `terminal3airport` (`adofhiter23@gmail.com`), published 141 packages between May 7 and May 27, 2026. Every package contains the same payload: a web proxy unblocker built on the [Scramjet](https://github.com/MercuryWorkshop/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` / `adofhiter23@gmail.com`
- 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](https://safedep.io/data/blogs/terminal3airport-malicious-packages.csv)

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:

| Wave | Date   | Version | Count | Notes                                       |
| ---- | ------ | ------- | ----- | ------------------------------------------- |
| 1    | May 7  | 1.1.3   | 2     | Readable code, Scramjet referenced directly |
| 2    | May 26 | 1.1.2   | 23    | Obfuscated JS, randomized filenames         |
| 3    | May 27 | 1.1.7   | 116   | Mass-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:

```bash
# 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:

```javascript
// 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:

```html
<!-- 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):**

```javascript
// 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:**

```html
<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):**

```javascript
// 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:**

```html
<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:

```javascript
// 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 registry: terminal3airport profile](https://www.npmjs.com/~terminal3airport)
- [Scramjet web proxy framework](https://github.com/MercuryWorkshop/scramjet)
- [SafeDep vet: open source dependency scanner](https://github.com/safedep/vet)
