An Undocumented OtterCookie-Lineage Build and Its Invisible C2 Fleet
How one stage-3 payload led to nine OtterCookie-related hosts, three server fingerprints, and a Contagious Interview infrastructure fleet sitting at zero detections.
Author: Yury Sergeev, RST Cloud
Published: 15 August 2026
Summary
A client shared a single stage-3 payload from a DPRK-nexus Contagious Interview delivery chain. Deobfuscating it yielded more than a capability list: it exposed the structure of the operator’s command-and-control tier, and that structure turned out to be fingerprintable.
Working outward from one IP address, we identified nine hosts across three autonomous systems, two distinct infrastructure generations, and two malicious npm packages feeding the same backend. Along the way we recovered three HTTP fingerprints that identify this operator’s servers directly, none of which appear in prior public reporting.
Key findings:
- The stage-3 payload is a Node.js RAT and infostealer of the OtterCookie/OtterCandy lineage, carrying capabilities documented for no published version of either family — including a Windows DPAPI / Chrome App-Bound Encryption bypass, SSH private-key harvesting with SSH lateral movement, automatic cloud-credential theft, and a separately delivered keylogger.
- The C2 is a three-tier Express deployment — panel on
:5000, payload download on:5056, bulk exfiltration on:3011— and each tier advertises an application-specific CORS header set that works as a durable fingerprint surviving IP rotation. - Exfiltration infrastructure is deliberately hosted on different providers from the panels. Six panel and loader hosts sit in AS397423 (Tier.Net); both exfiltration hosts sit elsewhere.
- The operator migrated from a single-port
:1244BeaverTail-style pattern to the multi-tier Express design, and that migration coincided with a sharp drop in public reporting coverage. - The stage-0 and stage-1 loader chain matches a documented toolkit described by ENKI WhiteHat six months earlier, down to filenames and obfuscation scheme.
- Every live Generation 2 host scores 0/91 on VirusTotal, and every file hash in this article is absent from VirusTotal entirely. Only the two retired Generation 1 hosts carry any detection at all.
All indicators are defanged below. Detection content is in Hunting and detection.
The lead
A client shared a set of artifacts describing a Contagious Interview delivery chain, along with the stage-3 URL. No compromise or impact was identified: the material was passed to us for analysis of what the payload would have done and where it pointed, not as a response to a successful intrusion. Nothing in this article should be read as describing harm to that organisation.
The chain as reported:
token_mac.sh (stage 0 — shell loader, fake interview lure) → ~/.task/parser.js (stage 1 — dropper; stages a private Node.js runtime) → ~/.vs_cache/main.js (stage 2 — in-memory loader, new Function over remote JS) → hxxps://btwknrll.s[.]gy/843111567577a5dc (stage 3 delivery) → stage-3 payload (infostealer + Socket.IO RAT + persistence)
Our starting artifact was the HTTP response body retrieved with curl -L against that short link — a JSON document of roughly 273 KB. Its SHA-256 is 0747ae321ecddc3936d53fe3ead743218732d6f39e31f3728f1977f6ae8b0c62.
The Switchy short link resolves straight to the C2 with a single hop:
GET https://btwknrll.s[.]gy/843111567577a5dc → 301 → http://66.235.168[.]14:5000/session/843111567577a5dc
Two things are worth noting. The redirect target preserves the short-link token verbatim as the /session/<token> path component, so the token is an operator-side campaign or victim identifier, not a URL-shortener artifact. And the redirect drops from HTTPS to plaintext HTTP directly to a bare IP — a transition that is trivially detectable at a proxy and is one of the cleanest network-level opportunities in the whole chain.
The JSON is a counterfeit VS Code extension manifest impersonating Microsoft’s real Python extension:
"name": "vscode-python-envs","displayName": "Python Environments","publisher": "ms-python","version": "1.30.0","repository": "https://github.com/microsoft/vscode-python-environments.git"
Every field is plausible filler. The payload rides in one non-standard key — sessions — holding 250,704 characters of obfuscated JavaScript (SHA-256 fd3061f4f0e1c1cd92070659c1623a62809e0552223491bd2a016989f1e56bdd).
Deobfuscation
The payload uses stock obfuscator.io protection in three layers.
Layer 1 — string array. 3,687 entries, each encoded with a custom Base64 alphabet that places lowercase first:
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=
Decoded output is then percent-decoded as UTF-8.
Layer 2 — rotation shuffler. A self-checking IIFE rotates the array with push(shift()) until an arithmetic checksum over seven decoded entries equals 0x60798. The accessor indexes at idx - 0x1ca. Because decoding is index-independent, the rotation must be solved before any string maps to the right position. Brute-forcing the checksum gave rotation = 315.
Layer 3 — dictionary and proxy indirection. Every constant and operator hides behind per-scope objects — M0['SEmem'] resolves to a URL, M0['Yjzby'](a,b) to a+b — with roughly 57 chained aliases of the decoder itself (Fz = a0C, then gi = Fz), plus switch-based control-flow flattening.
We wrote two Python passes rather than executing any attacker code. The first extracts the array, brute-forces the rotation, decodes all strings and resolves decoder aliases transitively. The second performs constant propagation across the dictionary objects, rewriting proxy calls back into native operators. Together they resolved 3,687 strings and 2,534 proxy references, producing readable source.
A practical note for anyone reproducing this: the decoded payload trips endpoint AV immediately. We kept it in memory throughout and never wrote it to disk.
What the payload does
Internal version string 1.2.2; hardcoded client ID 102.
On execution it enforces a single instance via a PID lockfile, opens a Socket.IO channel to the C2, registers the victim with system information and IP geolocation, and receives a storageKey tagging all subsequent uploads. It then begins collecting immediately, without operator interaction.
Credential theft
Automatic on every connect: ~/.config/gcloud, ~/.aws, ~/.ssh, ~/.azure — Google Cloud, AWS, SSH and Azure credentials, uploaded in that order.
Browsers. Chrome, Brave, Edge, Opera and Yandex profile roots, scanning Default plus Profile 1 through Profile 100. On Windows it reads Local State, extracts os_crypt.encrypted_key, strips the 5-byte DPAPI prefix, unprotects it via @primno/dpapi under CurrentUser scope, and AES-256-GCM decrypts each blob (IV bytes 3–15, tag last 16). It writes a helper script to disk and executes it under its own Node runtime to do this — an App-Bound Encryption bypass. On macOS it takes ~/Library/Keychains/login.keychain-db.
Shell history. ~/.bash_history, ~/.zsh_history, ~/.local/share/fish/fish_history, and on Windows the PowerShell PSReadLine\ConsoleHost_history.txt.
SSH key harvesting. History files are parsed for ssh invocations; a regex extracts -i and --identity arguments, resolves ~, and uploads each referenced private key.
Wallets
67 hardcoded browser wallet extension IDs — each extension’s Local Extension Settings directory is copied. Desktop wallets: Exodus, Guarda, Electrum, atomic, plus ~/Desktop/exodus-exports.
Filesystem sweep
Globs include *.env*, *.pem, *.ppk, *.p12, *.pfx, *.secret, *metamask*, *bitcoin*, *solana*, *secret phrase*, *private key*, plus documents and source. Files are ranked, with operator-supplied keywords highest, then .env, config files, JSON, and — notably — screenshots, identified by .png files named or foldered screenshot, screencapture, snip.
On Linux the stealer enumerates /mnt/<drive>/Users/<user>/AppData/..., reaching out of WSL into the Windows host to take browser profiles from there.
Remote access
Socket.IO events from operator to victim: exec, read_file, dir, upload, env, imp, pat. The exec handler accepts {instruction, cwd, sshPassword, sshKeyPath, adminUsername, adminId} — with SSH parameters set it runs commands against a remote host and reports via an ssh_status event. Built-in lateral movement using harvested keys.
Operator commands use an ss_ prefix: ss_del, ss_upd, ss_force_upd, ss_key_start, ss_key_stop, ss_key_status, ss_stop, ss_pause, ss_resume, ss_cancel, ss_upf, ss_backup_pause, ss_backup_resume, ss_backup_status.
Follow-on payloads
/download-win-key returns JavaScript, stripped of BOM and shebang, written to ~/.vs_cache/.<pid>_<base36-timestamp>.js and spawned under the private Node runtime with --win-key-agent and environment AGENT_LABEL, SERVER_URL, CLIENT_ID, KEYBOARD_AGENT_QUIET=1, autoStartCapture=true — a keylogger.
/download-app returns a binary, saved, chmod 0755, executed. On macOS the loader first runs xattr -d com.apple.quarantine, falling back to xattr -cr — an explicit Gatekeeper bypass.
One blended payload, not a two-stage chain
Readers familiar with this ecosystem will know the classic pattern: BeaverTail steals browser data, stages a Python distribution from /pdown, then hands off to InvisibleFerret, a Python backdoor beaconing on ports 1244/1245. It is reasonable to ask whether the RAT described above is InvisibleFerret arriving by another route.
It is not. The stealer and the RAT here are one Node.js codebase — the same file, the same Socket.IO connection, the same client_id and storageKey, running in one process. The exec/dir/read_file handlers sit alongside the credential-theft routines and share module-level state; there is no second-stage handoff. Searching the deobfuscated source returns zero references to Python, /pdown, AnyDesk, or ports 1224/1244/1245.
Architecturally this is a consolidation: two roles collapsed into one process, the Python runtime dependency dropped, and one C2 channel instead of two.
One caveat, stated plainly. We did not retrieve the /download-app binary. The loader treats it as a compiled executable — chmod 0755, quarantine stripped, spawned — and Trend Micro has documented Cython-compiled InvisibleFerret in Void Dokkaebi activity. A compiled InvisibleFerret is precisely the shape of payload that loader expects. So InvisibleFerret cannot be ruled out one stage further downstream, even though it is definitively not the in-process RAT. Retrieving that binary, and the keylogger agent from /download-win-key, are the main outstanding gaps in this analysis.
Persistence
The masquerade name varies by platform: Microsoft Realtime Agreegator on Windows (the misspelling is the attacker’s), AppleAccountSync on macOS, unixkernalloader on Linux.
- Windows —
HKCU\...\CurrentVersion\Runand...\Explorer\StartupApproved\Run, with value namesNvidiaDriverUpdate,VScode.Update.Publisherand the masquerade name; Startup-folder.vbsand.cmd; launched viawscript.exe "~\.vs_cache\main.vbs". - macOS —
~/Library/LaunchAgents/com.AppleAccountSync.plist,RunAtLoad,StandardOutPath=/dev/null. - Linux —
~/.config/autostart/unixkernalloader.desktop.
Uninstall (--uninstall or ss_del) removes these, then drops %TEMP%\_vs_rm_<pid>_<timestamp>.vbs which deletes the payload and finally itself.
One operator artifact worth recording: log timestamps are generated with a hardcoded UTC+9 offset, with the literal string tz=UTC+9 in the code.
The infrastructure fingerprint
The payload’s own network code was the pivot. It references three services on distinct ports, and querying each with the RST Enrichment API showed all three advertise application-defined CORS headers — values set by the operator’s own code, not framework defaults.
| Tier | Port | Role |
|---|---|---|
| Panel / C2 | 5000 | Registration, tasking and Socket.IO channel |
| Payload download | 5056 | Follow-on binary and keylogger agent |
| Bulk exfiltration | 3011 | Resumable mass file collection |
| Admin console | 3000 / 80 | Operator-facing client management |
Each tier advertises its own set of application-defined CORS headers, and the console carries a distinctive title and meta description. Those signatures are set out in Hunting and detection.
The exfiltration tier validated the static analysis neatly: the header set its CORS preflight advertises is exactly the set we recovered from the deobfuscated code, and HEAD is permitted — matching the /check deduplication request the payload makes before uploading a file.
That service is a resumable mass-collection endpoint. It exposes /check (HEAD, keyed on a Base64 file path, to skip already-stolen files), /status?machineId=, /upload, and /complete.
One caution before using these. All these servers return the stock Express 404 body (Cannot GET /, SHA-256 52c1e7a2c36be28c42455fe1572d7d7918c3180cad99a2b82daa2a38a7e7bb23). That hash is not a usable indicator — every bare Express deployment on the internet returns it, and it is identical across the panel and download tiers, which are different applications. The CORS values are the discriminator.
A note on what is actually new here. The :5000 port and the /client path are not novel: Silent Push documented shared BeaverTail/InvisibleFerret infrastructure on port 5000 including /client/<id>, /uploads and /pdown in April 2025. What we did not find in any prior reporting is the three-tier separation, the CORS fingerprints of each tier, and the :5056 and :3011 services themselves. The port-and-path convention is inherited; the service architecture and its fingerprints are the new part.
Enumerating the fleet
Pivoting on the panel signature across Netlas, Shodan and Censys, then verifying every candidate independently with the RST Enrichment API, produced nine hosts.
This table is a snapshot, not a census. Every cell records what a single active probe returned on 14 August 2026, from RST infrastructure:
- ✅ — the tier signature was returned live and matched. This is a positive identification.
- ✕ — no response to our probe at that moment. It does not establish that the service is absent. A host may be down, firewalled against scanner ranges, reachable only by tasked victims, or simply not listening on that port that day. Read every ✕ as “not observed”, never as “not there”.
- historical — not live for us, but recorded in archived scan data.
The distinction matters in both directions. 91.202.5[.]124:3011 was initially recorded as ✕ and turned out to be a live exfiltration listener on a re-probe — which is how we found the X-Backup-Date variant at all. Conversely, a ✕ that later becomes ✅ is a normal outcome for infrastructure that rotates.
Every host was probed on every tier port. There are no untested cells.
| Host | Admin UI :3000/:80 | Panel :5000 | Download :5056 | Exfil :3011 | Gen 1 :1244 | ASN |
|---|---|---|---|---|---|---|
66.235.168[.]14 | ✅ :3000 | ✅ | ✅ | ✕ | ✕ | AS397423 |
165.140.86[.]58 | ✕ | ✅ | ✅ | ✕ | ✕ | AS397423 |
38.92.47[.]164 | ✕ | ✅ | ✅ | ✕ | ✕ | AS397423 |
45.43.11[.]224 | ✕ | ✅ | ✅ | ✕ | ✕ | AS397423 |
91.202.5[.]124 | ✅ :80 | ✅ | ✕ | ✅ | ✕ | AS43641 |
5.175.213[.]199 | ✕ | ✕ | ✕ | ✅ | ✕ | AS206996 |
45.59.160[.]215 | :3000 recorded open | historical | ✕ | ✕ | ✕ | AS397423 |
165.140.86[.]190 | ✕ | ✕ | ✕ | ✕ | ✕ | AS397423 |
147.189.172[.]163 | ✕ | ✕ | ✕ | ✕ | ✕ | AS206996 |
- AS397423 — TIER-NET, Tier.Net Technologies LLC (US) — 6 hosts
- AS43641 — Sollutium-NL, SOLLUTIUM EU Sp z.o.o. (PL/NL) — 1 host
- AS206996 — ZAP-Hosting GmbH (DE) — 2 hosts, geolocating to Herndon VA and Los Angeles CA
45.59.160[.]215 no longer answered when we scanned, but a Netlas observation from 24 July 2026 recorded it serving the identical panel — same expose-headers, same port. Threat-feed data also records :3000 open on that host, which the finding below identifies as the admin tier. 165.140.86[.]190 and 147.189.172[.]163 answered on nothing; both are carried here on the strength of third-party reporting rather than our own observation.
147.189.172[.]163 sits outside the tier model, and it is worth being precise about why. The stage-3 payload references exactly two addresses — 66.235.168[.]14 and 5.175.213[.]199 — and this is not one of them. It reaches us instead from the @sqlite-labs/createsql indicator set, which covers the whole npm chain rather than stage 3 alone, and its /api/service/<md5> scheme on port 80 matches none of the four tier fingerprints. The most consistent reading is that it is an earlier-stage check-in endpoint on the npm delivery path, generationally aligned with the August activity and sharing AS206996 with the Generation 2 exfiltration host, but not part of the stage-3 command-and-control fleet. We could not probe it to confirm.
A fourth tier: the operator’s admin console
The console carries a distinctive HTML code, both of which internet-wide scan platforms index.

Pivoting on that pair surfaces the operator-facing side of this toolkit — a React single-page application for managing infected clients, live on two hosts:
| Host | Port | Bundle | Server |
|---|---|---|---|
66.235.168[.]14 | :3000 | main.f5827c3b.js / main.ca5c44f2.css | nginx/1.30.4 |
91.202.5[.]124 | :80 | main.13da17b1.js / main.a0eed94c.css | (no server header) |
The markup is otherwise identical — same title and meta description, Bootstrap 5.3.0 from jsDelivr, Font Awesome 6.4.0 from cdnjs, and Socket.IO client 4.5.4 loaded from cdn.socket.io, matching the protocol the implant uses. The differing content-hash bundle names show these are two separate builds of the same application.
Three observations follow.
The operators gate their own console carefully. The landing page is a “Device Unlock” screen requiring a device serial and a XXXX-XXXX-XXXX-XXXX-XXXX API key, with the notice “Open this app via the Electron desktop client to unlock.”
The operator console presents a Device Unlock gate requiring a device serial and an API key, and states that the application must be opened through an Electron desktop client.
The operator-facing login gate. No credentials or client data are exposed — the device serial field is empty and the API key field shows only a format placeholder. We did not attempt authentication.
Panel access is bound to specific operator devices and fronted by a dedicated Electron application — noticeably more disciplined operational security than the victim-facing infrastructure, which leaves RPC and WSDAPI exposed to the internet. It also implies the operators distribute a purpose-built desktop client among themselves, which is a further piece of tooling not previously described.
The deployment timestamp lines up with the npm wave. The 66.235.168[.]14 console reports Last-Modified: Mon, 10 Aug 2026 19:14:16 GMT — inside the same three-hour window on 10 August in which @sqlite-labs/createsql was published and revised six times (16:56–19:52). Console build and package publication are the same operational push.
It explains a previously unattributed port. Threat-feed data recorded :3000 open on 45.59.160[.]215 alongside the Gen 1 loader port :1244, with no explanation at the time. It is the admin console, which means that host carried operator tooling as well as victim-facing infrastructure.
The console port is not fixed — :3000 on one host, :80 on another — so hunt on the title and body strings rather than the port.
Delivery and collection are hosted apart
Six hosts — every panel, download and legacy loader node — sit in AS397423, spread across unrelated netblocks (38.92, 45.43, 45.59, 66.235, 165.140), consistent with VPS instances bought individually over time. Within 165.140.86.0/24 the operator holds at least two addresses.
Sorting the fleet by function rather than by tier makes the pattern sharper:
| Function | Provider | Hosts |
|---|---|---|
| Panels, payload downloads, Gen 1 loaders | AS397423 Tier.Net | all six |
Bulk exfiltration :3011 | AS43641 Sollutium · AS206996 ZAP-Hosting | 91.202.5[.]124, 5.175.213[.]199 |
Beacon / check-in (/api/service/<md5>) | AS206996 ZAP-Hosting | 147.189.172[.]163 |
Not one AS397423 host in this fleet receives victim data, and not one data-receiving host sits on AS397423. The split holds across all nine. ZAP-Hosting carrying two collection nodes rather than one makes this look like a standing arrangement rather than incidental provider choice.
The reading is that delivery and control infrastructure is kept separate from collection infrastructure, so that an abuse complaint or takedown against the panel provider does not cost the operator their stolen data — and the reverse holds too.
The practical consequence for defenders and for takedown work is that panel-focused enumeration will systematically miss the collection infrastructure. Our own investigation is the worked example: the panel signature sweep returned five hosts and not one of the collection nodes. 5.175.213[.]199 was only known because it is hardcoded in the payload, and 147.189.172[.]163 only from third-party package reporting. Neither would ever surface from a panel hunt. The two estates have to be enumerated separately, and referrals sent to all three providers in parallel.
A newer build is already deployed
The exfiltration listener on 91.202.5[.]124 advertises one header the other does not: X-Backup-Date. That string appears nowhere in the stage-3 payload we analysed. The server accepts a field this client version never sends — indicating a build newer than 1.2.2 already in circulation, most plausibly tied to the dated ss_backup_* sweep. Traffic carrying that header implies a version not yet sampled.
Host profile
Prompted by TCP 135 and 5357 on one host, we swept the fleet on 5357. Four hosts — 165.140.86[.]58, 38.92.47[.]164, 45.43.11[.]224 and 91.202.5[.]124 — returned a byte-identical WSDAPI 503 with Server: Microsoft-HTTPAPI/2.0. The C2 fleet is largely Windows Server with default networking exposed, which is unusual for C2 and consistent with the toolkit’s heavily Windows-focused feature set.
To be explicit: ports 135 and 5357 are not indicators. They are default Windows services present on millions of unrelated hosts. This is host profiling only.
Two generations
A second npm package tied the fleet to an earlier configuration and clarified its history.
| Generation 1 (~June 2026) | Generation 2 (~August 2026) | |
|---|---|---|
| Delivery | @safehttp/strict-uri-encode | @sqlite-labs/createsql |
| C2 pattern | single port 1244 | Express tiers 5000 / 5056 / 3011 |
| Hosts | 45.59.160[.]215, 165.140.86[.]190 | the six Generation 2 hosts |
| Status | both dark | live at time of writing |
45.59.160[.]215 spans both — the Generation 1 loader C2 on :1244, and the Generation 2 panel on :5000. It is the bridge host, and it is also the only member of the fleet carrying a threat-feed tag. The :1244 pattern is long-documented and well detected; the Generation 2 Express panels are not. The architectural migration appears to have reset the operator’s exposure.
Port 1244 is a documented BeaverTail C2 port, used alongside 1224 and 1245, and the well-known /pdown endpoint hosts the Python distribution needed to run InvisibleFerret (Unit 42, Datadog Security Labs).
Detection coverage tracks the generation split exactly
We checked every host against VirusTotal. The result is the clearest evidence that the architectural migration reset the operator’s exposure:
| Host | Generation | VirusTotal |
|---|---|---|
66.235.168[.]14 | Gen 2 | 0 / 91 |
5.175.213[.]199 | Gen 2 | 0 / 91 |
91.202.5[.]124 | Gen 2 | 0 / 91 |
165.140.86[.]58 | Gen 2 | 0 / 91 |
38.92.47[.]164 | Gen 2 | 0 / 91 |
45.43.11[.]224 | Gen 2 | 0 / 91 |
147.189.172[.]163 | — | 0 / 91 |
45.59.160[.]215 | Gen 1 | 3 / 91 |
165.140.86[.]190 | Gen 1 | 2 / 91 |
bdmkaoyijqmqa6bg.public.blob.vercel-storage[.]com | delivery | 1 / 91 |
Every live Generation 2 host is at zero detections. The only hosts with any detection at all are the two Generation 1 nodes, and both of those are now dark. Even there, coverage is 2–3 vendors out of 91.
Every file hash listed in this article is likewise absent from VirusTotal entirely — not merely undetected, but unknown.
This is the practical case for fingerprint-based hunting over indicator matching. An organisation relying on IP reputation or hash blocklists would have had no signal whatsoever against the live infrastructure in this campaign. The CORS signatures in the next section do not depend on anyone having seen the sample first.
The npm delivery vector
Two malicious npm packages, catalogued by OpenSourceMalware, feed the same infrastructure.
@sqlite-labs/createsql
Published by npm user viktorkovpan, 897 downloads, all versions malicious. The registry preserves a revealing timeline:
| Version | Published (UTC) |
|---|---|
| 1.0.0 | 2026-08-10 16:56:46 |
| 1.0.1 → 1.0.5 | 2026-08-10 17:37 – 19:52 |
0.0.1-security | 2026-08-10 20:29:45 — npm takedown |
Six versions in under three hours, removed 37 minutes after the last push — live tuning of the payload, followed by a fast takedown.
Its indicator set overlaps the chain we analysed almost exactly: the same Vercel blob delivery host, the same short-link token, the same C2 endpoints, and — decisively — the same stage-2 SHA-256, 482230569e6d03f29f6f8b77b39185e0a71b7c256e0e10630e23c192433c7c10. A byte-identical stage 2 shared between a public npm package and a separately sourced sample set is a code-level link, not merely shared infrastructure.
It also revealed the delivery path behind the short link: hxxp://66.235.168[.]14:5000/session/843111567577a5dc, which explains why the payload field is called sessions.
@safehttp/strict-uri-encode
Reported June 2026. A typosquat of the widely used strict-uri-encode utility, a transitive dependency of query-string, republished under a fake @safehttp scope. Its published description notes persistence hidden under a fake .vscode directory — the direct ancestor of this sample’s ~/.vs_cache.
Because it typosquats a genuine transitive dependency, it can land in a lockfile without anyone deliberately installing it. Audit resolved lockfiles, not just direct dependencies.
The viktorkovpan account and both packages have since been removed; enumerating any further packages from that account is no longer possible from public registry data.
The loader chain is a documented toolkit
Searching for the stage-0 filename token_mac.sh returned nothing — but it surfaced tokenlinux.sh in ENKI WhiteHat’s February 2026 report on Contagious Interview abusing VS Code via GitHub. That report describes the same chain, one platform over.
| ENKI, Feb 2026 | This sample, Aug 2026 |
|---|---|
tokenlinux.sh / token.cmd | token_mac.sh — same token<platform> convention |
| Stage 0 stages a private Node.js runtime, adds it to PATH | Identical |
Fetches parser.js + package.json, runs under that runtime | Identical filenames |
| obfuscator.io: string array, runtime shuffle, offset-based access | Exactly the scheme above |
| Anti-tamper: dummy code errors if reformatted | Same protection class |
hxxp://<server>:1244/s/6df937fe9011 returns C2 + campaign ID | /session/843111567577a5dc — same /s/<hex> convention |
Payload into a home .vscode directory | ~/.vs_cache, ~/.task |
C2 66.235.175[.]117, 66.235.11[.]117 on :1244 | 66.235.168[.]14 — same 66.235.0.0/16 |
| 17 wallet extension IDs | 16 of those 17 appear in this sample’s 67 |
The stage-0/stage-1 chain is therefore not bespoke — it is the operation’s standard loader, documented six months earlier, with an extension list that has been inherited and expanded rather than rewritten. The 66.235.0.0/16 continuity is a notable thread linking the two.
ENKI’s delivery vector differs: .vscode/tasks.json with runOn: folderOpen inside trojanised GitHub repositories, executing automatically when a developer opens the folder. Organisations scoping an incident of this type should treat “did a developer open an untrusted repository in VS Code” as a live question, alongside npm and fake-interview vectors.
Attribution
We assess this activity as belonging to the Contagious Interview activity set, DPRK-nexus, with high confidence — and as using a Node.js RAT of the OtterCookie/OtterCandy lineage that post-dates any publicly documented version of either family.
Where this sits in the documented lineage
Cisco Talos records the OtterCookie version progression as: v1 remote command execution; v2 clipboard theft; v3 file theft and upload; v4 virtual-environment detection, anti-debugging and anti-logging; v5 keylogging and screenshot capture.
Our sample carries capabilities documented for none of those versions — the DPAPI and App-Bound Encryption bypass, SSH key harvesting and SSH lateral movement, automatic cloud-credential collection, and the dated ss_backup_* sweep. It also carries an internal version string, 1.2.2, where the public v1–v5 labels are analyst-assigned rather than embedded.
We are deliberately not assigning this a version number. The v1–v5 sequence reflects sustained version-tracking by NTT Security and Cisco Talos across many samples; declaring a “v6” from a single build would imply a continuity of observation we do not have, and would compound the taxonomy problem discussed below. What we can say with confidence is narrower and more useful: this build post-dates everything publicly documented for either family, in both capability and infrastructure.
It is also not StoatWaffle, the newest WaterPlum family, documented by NTT Security in March 2026 and attributed to WaterPlum Team 8 (also called Moralis / Modilus), which reportedly began using it around December 2025. StoatWaffle is also Node.js and also arrives via .vscode/tasks.json, but its C2 is HTTP polling against /api/hsocketNext, /api/hsocketResult, /api/errorMessage, /api/handleErrors, /upload and /uploadsecond — an entirely different endpoint set, with no Socket.IO, no ss_ command namespace and no client_id field.
One cross-family detail is worth recording: StoatWaffle reads Windows user-profile data from WSL using wslpath, and our sample reaches the Windows host from WSL by enumerating /mnt/<drive>/Users/<user>/AppData. Same objective, different implementation — a WaterPlum tradecraft trait appearing across separate families.
The family call is a lineage call
The sample carries three implementation details documented for OtterCandy by NTT Security Japan: the ss_del command semantics, the client_id field introduced in v2, and self-forking on SIGINT via process.on. We verified the last directly in the deobfuscated code:
const {fork} = require('child_process');function respawn(){ fork(path.join(__dirname,'express.js'), [], {...}).unref(); }process.on('SIGINT', function(){ respawn(); });
But it diverges from OtterCandy on three counts: 67 wallet extensions against a documented 7; Contagious Interview delivery rather than ClickFake Interview, with none of ClickFake’s GolangGhost or FrostyFerret toolset present; and self-implemented persistence rather than delegation to a preceding loader.
Critically, OtterCandy is itself a merge of OtterCookie and RATatouille code. Details documented only in OtterCandy reporting may well originate in the OtterCookie half of that shared codebase. “Documented only in OtterCandy” is not “present only in OtterCandy” — so a clean family assignment is not supported in either direction. This is a lineage call.
That convergence is itself well documented: Cisco Talos has reported BeaverTail and OtterCookie merging via a shared JavaScript module.
Actor naming
On actor we assess WaterPlum / Famous Chollima / PurpleBravo — interchangeable aliases for one intrusion set — at moderate confidence. We do not carry forward any sub-cluster assignment.
NTT’s reporting describes WaterPlum as operating multiple clusters or teams: Cluster B, commonly called the BlockNovas cluster, associated with ClickFake Interview activity; and Team 8 (Moralis / Modilus), which previously used OtterCookie in Contagious Interview activity before moving to StoatWaffle. We do not have evidence sufficient to place this sample in either, and the public mapping between these designations is incomplete. CrowdStrike’s taxonomy, by contrast, associates FAMOUS CHOLLIMA with NICKEL TAPESTRY, Tenacious Pungsan, Void Dokkaebi, Wagemole, PurpleBravo, PurpleDelta and Contagious Interview together — a reminder that these labels sit at different levels of abstraction across vendors.
A note on “Lazarus”
Several signals in this investigation carry a Lazarus label — a community feed tag, a package tag, and an antivirus family name. None of them evidences Lazarus proper.
“Lazarus Group” originally denoted a specific cluster and is now widely used as a catch-all for any DPRK activity. Contagious Interview / Famous Chollima is generally assessed as distinct from Lazarus proper, as are Kimsuky, Andariel and BlueNoroff. The community feed tag in question, for example, derives from a ZoomEye dork for the BeaverTail :1244/pdown pattern — the evidence supports Contagious Interview, while the label asserts something broader.
This matters practically. Reading three umbrella-labelled signals as three independent confirmations of Lazarus double-counts a generic term and inflates apparent corroboration. It also implies a link to destructive and financial-institution operations that this activity does not support. “Contagious Interview (DPRK)” is both more accurate and more useful to a defender.
What we could not establish
Nothing here ties the cluster through an operator handle, unique build artifact, confirmed victim set, or proven infrastructure ownership. Shared or rented tooling remains a live alternative: malware-family overlap is not a unique attribution key. State sponsorship of this specific activity is not independently established.
Hunting and detection
Network fingerprints
The highest-value content, in priority order. These are application-defined and survive IP rotation.
Panel tier:
Access-Control-Expose-Headers: Last-Modified,X-File-Modified-At,X-File-Mtime-Ms
Download tier:
Access-Control-Expose-Headers: X-App-Last-Modified,X-Lan-Last-Modified
Exfiltration tier — alert on the header triple, which is highly distinctive:
X-Upload-ID + X-Is-Priority + X-Overwrite
Admin console — the highest-signal pivot in this set. The console’s HTML title and meta description are both distinctive and both indexed by scan platforms; the port varies (:3000 and :80 observed), so match on body content rather than port.
The structural pattern is the part that survives. Every literal above is cheap for the operator to change — Access-Control-* headers are meaningful only to browsers, so the Node implant does not depend on them at all, and they can be dropped tomorrow at no functional cost. What is harder to re-architect is the shape: three Express services on adjacent ports advertising application-specific expose-headers, a client-management console on a separate port, and collection hosted on a different ASN from delivery. Hunt the shape as well as the strings.
Watch for X-Backup-Date specifically: its presence indicates a client build newer than the one analysed here.
Path sets. Generation 2: /register, /file-upload, /content-upload, /session/<hex>, /download-app, /download-win-key, and /check + /status?machineId= + /upload + /complete. Generation 1: /pdown, /uploads, /client/<digits>/<3 digits> on port 1244.
Do not alert on the Express 404 body hash.
Host indicators
Directories ~/.task and ~/.vs_cache; ~/.vs_cache/log.log, main.vbs, backup.pause, and transient .<pid>_<base36>.js; %TEMP%\_vs_rm_<pid>_<ts>.vbs; ~/Library/LaunchAgents/com.AppleAccountSync.plist; ~/.config/autostart/unixkernalloader.desktop; registry Run values NvidiaDriverUpdate, VScode.Update.Publisher, Microsoft Realtime Agreegator.
Supply chain
Audit resolved lockfiles for @sqlite-labs/createsql and @safehttp/strict-uri-encode, and any package published by viktorkovpan. Treat .vscode/tasks.json with runOn: folderOpen in untrusted repositories as an execution primitive.
If you find this
Treat every credential reachable from the host as compromised, in this order: cloud credentials (GCP, AWS, Azure) and any tokens they minted; all SSH private keys, plus an audit of authorized_keys on hosts those keys reach — SSH pivoting is a built-in feature, so the blast radius likely extends beyond the initial host; browser-stored passwords across all profiles; macOS login keychain; crypto wallet seed phrases, which should be assumed lost.
Given the keylogger and arbitrary follow-on binary execution, host rebuild is safer than artifact cleanup.
Indicators of compromise
All defanged.
Infrastructure
| Indicator | Role |
|---|---|
66.235.168[.]14:5000 | Panel / C2 — /register, /file-upload, /content-upload, /client, /session/<hex> |
66.235.168[.]14:5056 | Payload download — /download-app, /download-win-key |
66.235.168[.]14:3000 | Operator admin console |
91.202.5[.]124:80 | Operator admin console |
5.175.213[.]199:3011 | Bulk exfiltration |
91.202.5[.]124 | Panel :5000 + exfiltration :3011 (variant build) |
165.140.86[.]58 | Panel + download |
38.92.47[.]164 | Panel + download |
45.43.11[.]224 | Panel + download |
45.59.160[.]215 | Generation 1 :1244; Generation 2 panel (historical) |
165.140.86[.]190 | Generation 1 :1244 |
147.189.172[.]163 | Earlier-stage check-in on the npm delivery path — /api/service/<md5>; not referenced by stage 3 |
bdmkaoyijqmqa6bg.public.blob.vercel-storage[.]com | Stage-1 delivery |
hxxps://btwknrll.s[.]gy/843111567577a5dc | Stage-3 short link |
Files
| SHA-256 | Artifact |
|---|---|
e171ff04f6dac7c6c9e810be6ee7795bde765ac61f70f663b45e02772351d882 | stage 0 token_mac.sh |
d49cd526ac7fe6cfec4a390abc64ea0bc4ee2ad7db898b4ff8f50e0f13285eae | stage 1 parser.js |
482230569e6d03f29f6f8b77b39185e0a71b7c256e0e10630e23c192433c7c10 | stage 2 main.js — also in @sqlite-labs/createsql |
0747ae321ecddc3936d53fe3ead743218732d6f39e31f3728f1977f6ae8b0c62 | stage-3 JSON (response body from the short link) |
fd3061f4f0e1c1cd92070659c1623a62809e0552223491bd2a016989f1e56bdd | stage-3 payload (sessions field) |
50301b6842eee311ee64848c5a8914d63cff54f42550f67a458a36bd57f63218 | npm package artifact |
73aa93886f05b18b87a259cb390be64ec8883261d993f3b7abd03c14e66859d7 | npm package artifact |
b986f228e6ac9f285c333e8aa496816770f732e10ff538a231e853b00a0b65cb | npm package artifact |
0a5859bce803dde954c3c2d4841cd6c0342925e371dad15661cd4acc690abe47 | npm package artifact |
230b1b68e643fd7fe5b4c39fce10100c59cd8d579107a67df63a095849286808 | npm package artifact |
20aa80f25243fcadd72a3a3b6361566b59bbd7f71d2c9bd387897c54269b3be1 | npm package artifact |
None of the hashes above is present in VirusTotal.
Packages and accounts
@sqlite-labs/createsql (npm, all versions) · @safehttp/strict-uri-encode (npm, all versions) · npm user viktorkovpan
Methodology and tooling
- Custom Python deobfuscation and extraction scripts — string-array recovery, checksum-driven rotation solving, transitive alias resolution and dictionary constant propagation, all in memory.
- RST Report Hub and RST CTI Assistant — prior-reporting research and corroboration.
- RST Enrichment API — active scanning of candidate hosts and indicator lookups. All scanning was performed by RST infrastructure, not from client or analyst networks.
- RST Threat Library — actor and campaign object retrieval for naming.
- Netlas, Shodan and Censys — internet-wide scan data for fleet enumeration and historical observations.
- OpenSourceMalware — npm package intelligence.
- npm public registry API — package version history and takedown forensics.
- VirusTotal —detection-coverage baselining for every host and file hash. This produced the finding that the entire live fleet sits at zero detections.
All indicators were checked against VirusTotal; results are in Detection coverage. Hashes are published as SHA-256 throughout.
References
All links verified 15 August 2026. Each entry notes what it was checked against and what it contributed.
OtterCookie
- OtterCookie, new malware used in Contagious Interview campaign — NTT Security Japan, 2024-12-26 — https://jp.security.ntt/insights_resources/tech_blog/en-contagious-interview-ottercookie/ → Baseline family description. Established the original capability set our sample is measured against.
- Additional Features of OtterCookie Malware Used by WaterPlum — NTT Security Japan, 2025-05-08 — https://jp.security.ntt/insights_resources/tech_blog/en-waterplum-ottercookie → Checked v3/v4 capabilities. Confirmed our sample exceeds both; also the source for the WaterPlum = Famous Chollima = PurpleBravo alias mapping.
- BeaverTail and OtterCookie evolve with a new JavaScript module — Cisco Talos, 2025-10-16 — https://blog.talosintelligence.com/beavertail-and-ottercookie/ → Source of the v1–v5 progression used in this article. Confirmed no documented version carries our sample’s capabilities. Also documents BeaverTail/OtterCookie convergence, supporting the lineage reading.
- OtterCookie Malware Analysis: A Fake Job Offer Turned Into a Stealer Infection — ANY.RUN, 2025-06-03 — https://any.run/cybersecurity-blog/ottercookie-malware-analysis/ → Independent capability corroboration. No infrastructure overlap with our fleet.
- OtterCookie v4 Adds VM Detection and Chrome, MetaMask Credential Theft — The Hacker News, 2025-05 — https://thehackernews.com/2025/05/ottercookie-v4-adds-vm-detection-and.html → Ruled out v4 as a match: v4 adds VM detection and Chrome/MetaMask theft, but no DPAPI or App-Bound Encryption bypass.
- North Korean Hackers Improve OtterCookie Malware’s Data Theft Abilities — SC World — https://www.scworld.com/brief/north-korean-hackers-improve-ottercookie-malwares-data-theft-abilities → Secondary coverage; used only to cross-check the version timeline.
- OtterCookie Malware Steals Dev Secrets, SSH Keys, Cloud Credentials and Tokens — GBHackers — https://gbhackers.com/ottercookie-malware-steals-dev-secrets/ → Confirmed the developer-secrets and SSH/cloud-credential targeting profile matches our sample at family level.
- Tracking an OtterCookie Infostealer Campaign Across npm — Panther, 2026-04-21 — https://panther.com/blog/tracking-an-ottercookie-infostealer-campaign-across-npm → Established npm as a recurring OtterCookie delivery channel, supporting the two-vector picture.
- Hunting Lazarus Part VIII: OtterCookie — Red Asgard, 2026-05-16 — https://redasgard.com/blog/hunting-lazarus-part8-ottercookie → Checked for the
Microsoft Realtime Agreegatorpersistence string. Not corroborated — persistence described only generically, which is why we treat that string as sample-specific. - Hunting Lazarus Part VII: The Server That Was Not Just FTP — Red Asgard, 2026-05-06 — https://redasgard.com/blog/hunting-lazarus-part7-server-not-just-ftp → Checked for infrastructure overlap with our fleet. None found (that C2 uses an FTP sink on port 21).
- Hunting Lazarus Part III: The Infrastructure That Was Too Perfect — Red Asgard, 2026-02-04 — https://redasgard.com/blog/hunting-lazarus-part3-infrastructure-too-perfect → Confirmed the
1224/1244infrastructure signature, corroborating our Generation 1 classification.
OtterCandy, StoatWaffle and WaterPlum
- OtterCandy, malware used by WaterPlum — NTT Security Japan, 2025-10-15 — https://jp.security.ntt/insights_resources/tech_blog/ottercandy_malware_e/ → The pivotal comparison. Source of the
ss_del,client_idand SIGINT-refork discriminators, which we then verified in our own sample. Also the source of the three divergences that stopped us assigning the family outright. - WaterPlumが使うマルウェアOtterCandyについて — NTT Security Japan, 2025-10-15 — https://jp.security.ntt/insights_resources/tech_blog/ottercandy_malware_j/ → Japanese original of the above; consulted to confirm no detail was lost in the English edition.
- StoatWaffle, malware used by WaterPlum — NTT Security Japan, 2026-03-17 — https://jp.security.ntt/insights_resources/tech_blog/stoatwaffle_malware_en/ → Hypothesis decommissioned. Checked whether our sample was this newer WaterPlum family. Ruled out: StoatWaffle uses HTTP polling against
/api/hsocket*, with no Socket.IO, noss_namespace and noclient_id. Did yield the WSL-access TTP parallel. - Famous Chollima deploying Python version of GolangGhost RAT — Cisco Talos, 2025-06-18 — https://blog.talosintelligence.com/python-version-of-golangghost-rat/ → Established the ClickFake toolset. Its absence from our chain is part of why we do not carry a Cluster B mapping.
- The Deepfake Threat: Chollima APT Group Uses AI Filters — SOCRadar, 2025-11-13 — https://socradar.io/deepfake-threat-chollima-apt-group-uses-ai-crypto/ → Actor context only; no technical claim in this article rests on it.
BeaverTail and InvisibleFerret
- North Korean Threat Actors Lure Tech Job Seekers as Fake Recruiters — Unit 42, 2024-10-09 — https://unit42.paloaltonetworks.com/north-korean-threat-actors-lure-tech-job-seekers-as-fake-recruiters → Primary source confirming
:1224/:1244with/pdownand/client/<campaign_id>. Turned port 1244 from a suggestive open port into a documented campaign C2 port. - Hacking Employers and Seeking Employment: Two Job-Related Campaigns by North Korea — Unit 42 — https://unit42.paloaltonetworks.com/two-campaigns-by-north-korea-bad-actors-target-job-hunters/ → Campaign background; distinguishes the fake-recruiter operation from the IT-worker operation.
- Job Offers from the North: Contagious Interview Targeting Software Developers — Macnica, 2024-10-29 — https://security.macnica.co.jp/blog/2024/10/-contagious-interview.html → Second independent confirmation of the
:1224/pdowndownloader pattern. - Bored BeaverTail Yacht Club: A Lazarus Lure — eSentire, 2024-10-17 — https://www.esentire.com/blog/bored-beavertail-yacht-club-a-lazarus-lure → Third confirmation of
:1244/pdown, establishing it as a stable convention rather than a one-off. - InvisibleFerret Malware Analysis — ANY.RUN, 2025-01-21 — https://any.run/cybersecurity-blog/invisibleferret-malware-analysis → Confirmed paired use of ports 1244 and 1245, underpinning the Generation 1 model.
- Contagious Interview (DPRK) Launches a New Campaign Creating Three Front Companies — Silent Push, 2025-04-24 — https://www.silentpush.com/blog/contagious-interview-front-companies/ → Narrowed our novelty claim. Documents BeaverTail/InvisibleFerret infrastructure on port 5000 with
/client/<id>— so our:5000/clientendpoint is inherited convention, not new. Also defines the BlockNovas front-company campaign, whose absence here let us rule that mapping out. - BeaverTail and InvisibleFerret analysis — Objective-See, 2024-07-21 — https://objective-see.org/blog/blog_0x7A.html → macOS-side baseline for the predecessor families.
- BeaverTail and Tropidoor Malware Distributed via Recruitment Emails — AhnLab ASEC, 2025-04-02 — https://asec.ahnlab.com/en/87299/ → Checked for C2 or path overlap. None — Tropidoor uses a different endpoint scheme.
- Analyzing Void Dokkaebi’s Cython-Compiled InvisibleFerret Malware — Trend Micro, 2026-05-22 — https://www.trendmicro.com/en_us/research/26/e/analyzing-void-dokkaebi-invisibleferret-malware.html → Checked whether InvisibleFerret path changes matched our tiers. They do not; different lineage.
- BeaverTail variant distributed via malicious repositories and ClickFix lure — GitLab Threat Intelligence, 2025-09-17 — https://gitlab-com.gitlab.io/gl-security/security-tech-notes/threat-intelligence-tech-notes/north-korean-malware-sept-2025/ → Repository-based delivery context; supports treating untrusted repos as a live vector.
- APT Lazarus: Eager Crypto Beavers, Video Calls and Games — Group-IB — https://www.group-ib.com/blog/apt-lazarus-python-scripts/ → Early campaign context for the fake-interview lure.
Delivery vectors — VS Code, GitHub, SVG, JSON storage
- Analysis of Contagious Interview Campaign Abusing VSCode Distributed on GitHub — ENKI WhiteHat, 2026-02-27 — https://www.enki.co.kr/media-center/blog/contagious-interview-campaign-abusing-vscode-distributed-on-github → The closest precedent found. Matched our stage-0/stage-1 chain almost exactly —
token<platform>naming, private Node runtime,parser.js+package.json, the same obfuscation scheme, the/s/<hex>convention, and C2 in the same66.235.0.0/16space. Also the source of the GitHubtasks.jsonvector we recommend scoping for. - Contagious Interview: Malware delivered through fake developer job interviews — Microsoft Security, 2026-03-11 — https://www.microsoft.com/en-us/security/blog/2026/03/11/contagious-interview-malware-delivered-through-fake-developer-job-interviews/ → Confirmed the VS Code trust-and-autorun model that makes
tasks.jsonan execution primitive. - New North Korean campaign uses fake coding interviews to steal developer credentials — Elastic Security Labs, 2026-07-18 — https://www.elastic.co/security-labs/contagious-interview-malware-svg-steganography → Checked SVG steganography as a possible delivery path. Not present in our chain — recorded to show the vector set is broader than the two we observed.
- Contagious Interview Actors Now Utilize JSON Storage Services for Malware Delivery — NVISO, 2025-11-13 — https://blog.nviso.eu/2025/11/13/contagious-interview-actors-now-utilize-json-storage-services-for-malware-delivery/ → TTP precedent confirmed. Our stage 3 arrives as JSON with the payload in a
sessionsfield — the same delivery pattern, established nine months earlier. - Lazarus Group Uses Git Hooks To Hide Malware — OpenSourceMalware, 2026-05-06 — https://opensourcemalware.com/blog/lazarus-group-uses-git-hooks-to-hide-malware → Another vector checked and not present here; useful for scoping breadth.
- Latest Contagious Interview malware campaign abuses Microsoft VSCode Tasks — OpenSourceMalware — https://opensourcemalware.com/blog/latest-contagious-interview-malware-campaign-abuses-microsoft-vscode-tasks → Corroborates the
tasks.jsonvector independently of ENKI. - DPRK Contagious Interview “Fake Font” Abuses Malicious VS Code Fonts — OpenSourceMalware — https://opensourcemalware.com/blog/dprk-contagious-interview-campaign-fake-font-uses-malicious-vs-code-fonts → Further vector variant; shows how quickly the delivery side rotates.
- Contagious Interview: Tracking the VS Code Tasks Vector — Abstract Security — https://www.abstract.security/blog/contagious-interview-tracking-the-vs-code-tasks-infection-vector → Vector tracking over time; supports treating repo-open as a recurring primitive rather than a one-off.
- North Korean Hackers Exploit Code Repositories in Contagious Interview — GBHackers — https://gbhackers.com/contagious-interview/ → Secondary coverage linking the repository and npm vectors in one chain.
Malicious npm packages
- Stressed Pungsan: DPRK-Aligned Threat Actor Leverages npm for Initial Access — Datadog Security Labs, 2024-08-04 — https://securitylabs.datadoghq.com/articles/stressed-pungsan-dprk-aligned-threat-actor-leverages-npm-for-initial-access → Earliest npm-as-initial-access precedent in our set; establishes the vector as long-standing.
- Tenacious Pungsan: A DPRK threat actor linked to Contagious Interview — Datadog Security Labs, 2024-10-26 — https://securitylabs.datadoghq.com/articles/tenacious-pungsan-dprk-threat-actor-contagious-interview → Confirmed port 1244 as documented campaign infrastructure, and supplied one of the actor aliases we assessed.
- Inside the GitHub Infrastructure Powering North Korea’s Contagious Interview Campaign — Socket — https://socket.dev/blog/north-korea-contagious-interview-npm-attacks → Context for how npm and GitHub are operated together as one renewable access channel.
- Contagious Interview Campaign Escalates With 67 Malicious npm Packages and New Malware Loader — Socket, 2025-07-14 — https://socket.dev/blog/contagious-interview-campaign-escalates-67-malicious-npm-packages → Checked for our two packages. Not present — a different wave.
- Another Wave: North Korean Contagious Interview Campaign Drops 35 New Malicious npm Packages — Socket, 2026-08-14 — https://socket.dev/blog/north-korean-contagious-interview-campaign-drops-35-new-malicious-npm-packages → Closest wave by date. Checked for
viktorkovpanand our packages; not listed, so this is a separate publishing cluster. - North Korean Hackers Deploy BeaverTail Malware via 11 Malicious npm Packages — The Hacker News, 2025-04-08 — https://thehackernews.com/2025/04/north-korean-hackers-deploy-beavertail.html → Scale reference; our packages not among them.
- North Korean Hackers Deploy 197 npm Packages to Spread New OtterCookie Malware Variant — The Hacker News, 2025-11-10 — https://thehackernews.com/2025/11/north-korean-hackers-deploy-197-npm.html → Checked as a candidate parent campaign. Ruled out — publisher
stardev0914, different staging infrastructure. - Contagious Interview campaign expands with 197 npm packages — Security Affairs, 2025-11-30 — https://securityaffairs.com/185170/apt/contagious-interview-campaign-expands-with-197-npm-ppackages-spreading-new-ottercookie-malware.html → Same wave as above; confirmed the Vercel-staging pattern that our chain also uses.
- North Korean Hackers Publish 26 npm Packages Hiding Pastebin Steganography (StegaBin) — The Hacker News, 2026-03 — https://thehackernews.com/2026/03/north-korean-hackers-publish-26-npm.html → Ruled out as related: different C2 concealment model, no overlap with our indicators.
- Inside StegaBin: How a DPRK Steganography Campaign Uses npm to Hide C2 Infrastructure — Snyk — https://snyk.io/articles/inside-stegabin/ → Technical detail behind the above; confirmed the exclusion.
- North Korea Still Attacking Developers via npm — Phylum (now hosted by Veracode) — https://www.veracode.com/blog/north-korea-still-attacking-developers-via-npm/ → Historical npm-campaign continuity. Original Phylum URL is dead post-acquisition; link updated.
- North Korean-Linked Threat Actor Targets Developers with Malicious npm Package — OX Security — https://www.ox.security/blog/north-korean-npm-infostealer-rat/ → Comparable npm-delivered RAT/infostealer; no indicator overlap with our fleet.
- Malicious npm
node-env-resolveRAT — SafeDep, 2026-05-03 — https://safedep.io/malicious-npm-node-env-resolve-rat/ → Relevant to lineage: RATatouille, whose code is one half of OtterCandy, spread through npm supply-chain compromise.
ClickFake Interview
- Lazarus ClickFake Interview Campaign: ClickFix Malware — Sekoia.io, 2025-03-31 — https://www.sekoia.com/blog/clickfake-interview-campaign-by-lazarus → Defines ClickFake as distinct from Contagious Interview. Central to decommissioning the Cluster B / BlockNovas hypothesis: OtterCandy sits in ClickFake activity, our chain does not.
- Who Needs a Job? DPRK ClickFake Campaign Drops PylangGhost and GolangGhost RATs — SOCRadar, 2026-07-21 — https://socradar.io/blog/dprk-clickfake-pylangghost-golangghost-rats/ → Confirmed the current ClickFake toolset. None of it appears in our chain.
- Lazarus ClickFake Interview Campaign Targets Cryptocurrency Security — SecureBlink, 2025-04-03 — https://www.secureblink.com/threat-feeds/lazarus-click-fake-interview-campaign-targets-cryptocurrency-security → Secondary ClickFake coverage; used only to cross-check the campaign boundary.
Actor naming, taxonomy and DPRK IT workers
- Famous Chollima Adversary Profile — CrowdStrike — https://www.crowdstrike.com/en-us/adversaries/famous-chollima/ → Source for the breadth of the alias set — showing how many distinct operations sit under one actor name. Underpins the “Lazarus is an umbrella” argument.
- Famous Chollima Threat Actor Profile — Huntress — https://www.huntress.com/threat-library/threat-actors/famous-chollima → Independent alias corroboration.
- Contagious Interview, Group G1052 — MITRE ATT&CK — https://attack.mitre.org/groups/G1052/ → Canonical alias list; the reference point we used when choosing our label.
- PurpleBravo’s Targeting of the IT Software Supply Chain — Recorded Future, 2026-01-21 — https://www.recordedfuture.com/research/purplebravos-targeting-it-software-supply-chain → Confirmed PurpleBravo as an alias rather than a separate actor, and its supply-chain focus.
- DPRK Fake IT Worker Cyber Threat Actors’ Infrastructure — Team Cymru, 2026-04-22 — https://www.team-cymru.com/post/dprk-fake-it-worker-cyber-threat-actors-infrastructure → Checked for infrastructure overlap with our fleet. None. Reinforces that the IT-worker operation is a separate activity set despite sharing an actor label.
- From Pyongyang to Your Payroll — Zscaler, 2024-11-04 — https://www.zscaler.com/blogs/security-research/pyongyang-your-payroll-rise-north-korean-remote-workers-west → IT-worker operation background; used to keep the two activity sets distinct.
- Staying a Step Ahead: Mitigating the DPRK IT Worker Threat — Google Cloud / Mandiant, 2024-09-23 — https://cloud.google.com/blog/topics/threat-intelligence/mitigating-dprk-it-worker-threat → Source of the UNC5267 designation, one of the aliases folded into the umbrella.
- NICKEL TAPESTRY Infrastructure Associated with Crowdfunding Scheme — Secureworks, 2025-01-21 — https://www.secureworks.com/blog/nickel-tapestry-infrastructure-associated-with-crowdfunding-scheme → Another IT-worker alias checked for overlap; none found.
- NICKEL ALLEY strategy: Fake it ’til you make it — Sophos — https://www.sophos.com/en-us/blog/nickel-alley-strategy-fake-it-til-you-make-it → Alias context.
- Inside DPRK’s Fake Job Platform Targeting U.S. AI Talent — Validin, 2025-11-20 — https://www.validin.com/blog/inside_dprk_fake_job_platform/ → Lure-side tradecraft; no infrastructure overlap with our fleet.
- Russian Infrastructure Plays Crucial Role in North Korean Cybercrime Operations — Trend Micro, 2025-04-24 — https://www.trendmicro.com/en_us/research/25/d/russian-infrastructure-north-korean-cybercrime.html → Checked as a hosting-pattern comparison. Our fleet sits in US and EU ASNs, so no match — recorded to show the hosting choice here is not the previously reported pattern.
Other
- Maltrail
apt_lazarus.txttrail list — https://github.com/stamparm/maltrail/blob/master/trails/static/malware/apt_lazarus.txt → Origin of thelazarus_grouptag on one Generation 1 host. Tracing it back showed the tag derives from a:1244/pdownBeaverTail fingerprint — sound methodology, but a label broader than its own evidence. This is the worked example behind our note on umbrella labels. - OpenSourceMalware — package and indicator records for
@sqlite-labs/createsqland@safehttp/strict-uri-encode— https://opensourcemalware.com/ → Source of the npm delivery vector, theviktorkovpanpublisher, the second-generation indicator set, and the stage-2 hash match that tied the public package to our sample at code level.
RST Cloud delivers curated, agent-ready threat intelligence, indicator enrichment and reporting to security teams worldwide. Newly identified indicators from this research have been added to the RST Threat Feed.