Joyfill npm Supply-Chain Compromise: @joyfill/components and @joyfill/layouts Ship Obfuscated Worm-Like RAT and Credential Stealer — Threadlinqs Intelligence
As of 2026-08-13, Joyfill npm Supply-Chain Compromise: @joyfill/components and @joyfill/layouts Ship Obfuscated Worm-Like RAT and Credential Stealer is a critical-severity supply chain threat attributed to WageMole (North Korea (DPRK)), tracked by Threadlinqs Intelligence with 9 detection rules (Splunk SPL, Microsoft KQL, Sigma) and 48 indicators of compromise.
Threat ID: TL-2026-1805 · Severity: CRITICAL · Status: ACTIVE · Category: SUPPLY_CHAIN
Updated: 2026-08-13 · 4 updates · revalidated 2×
Attribution: WageMole · North Korea (DPRK) · FINANCIAL
Malicious beta releases of @joyfill/components and @joyfill/layouts, published to npm on 2026-07-28, embed a five-stage obfuscated payload that resolves C2 addresses through Tron/BNB Smart Chain/Aptos
On 2026-07-28, threat actors published malicious pre-release versions of two legitimate npm packages, @joyfill/components (4.0.0-rc24-2773-beta.4, -beta.5, -beta.6) and @joyfill/layouts (0.1.2-2773.beta.0, -beta.1, -beta.2), within hours of each other. The malicious code was injected exclusively into the compiled distribution bundles shipped in the published tarballs (dist/index.js, dist/index.esm.js, dist/joyfill.min.js for components; dist/index.cjs.js, dist/index.es.js for layouts) — the visible source repositories were untouched, indicating compromise of the registry publishing pipeline or a maintainer's publish credentials rather than a malicious pull request.
The payload's most consequential design choice is that it executes at package IMPORT time, not at npm install time. Because it never uses an npm lifecycle hook (no postinstall script), the widely-recommended defense `npm install --ignore-scripts` provides no protection whatsoever — any application that imports the compromised module triggers the chain the moment it loads.
Stage 1 is an obfuscated bootstrap loader that plants a campaign marker (`global["!"] = "9-0135-3"`), uses a seeded-PRNG string-shuffle decoder to alias core Node primitives under innocuous global names (`global.r = require`, `global.m = module`), and builds a Function-constructor ladder that never spells out the literal words "Function" or "eval" in cleartext to defeat static string-matching detections. A 30-second re-entry guard (`global._p_t`) prevents repeated execution during sandbox detonation, and the decoder function checksums its own source against a stored constant — any tampering (e.g., a debugger inserting breakpoints or a security tool patching the function) silently neuters execution instead of throwing, an anti-analysis pattern consistent with MITRE's Execution Guardrails: Environmental Keying (T1480.001).
Stage 2 resolves command-and-control infrastructure entirely through blockchain lookups rather than a hardcoded server, letting the operator rotate infrastructure at will while making takedown far harder — smart contracts and transactions on public chains are immutable and cannot be seized like a domain or IP. The resolver queries a fixed Tron address (TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP) for its latest outbound transaction; the `raw_data.data` field of that transaction contains a reversed BNB Smart Chain transaction hash. The loader then calls `eth_getTransactionByHash` against a BSC RPC endpoint, and the returned transaction's input field holds an XOR-encrypted payload that is decrypted with a repeating-key cipher. If this path fails, the loader falls back to an Aptos account as a secondary dead-drop pointer. This exact technique — using blockchain read calls as a censorship-resistant C2 dead-drop resolver — is known as EtherHiding, first documented by Google Threat Intelligence Group/Mandiant in October 2025 against the DPRK-linked UNC5342 (aka WageMole/Famous Chollima/Nickel Tapestry/Storm-1877/Void Dokkaebi/WaterPlum/PurpleBravo) cluster's JADESNOW downloader. The resolver runs on two parallel branches — an in-process branch using direct `eval()` and a detached child process — each with its own XOR key and blockchain pointer set, and the detached branch separately queries 23.27.13.43 for a secondary payload.
A campaign-tag variable (`_V`, rendered as HTTP header `Sec-V: A9-0135-3` for the npm infection vector) selects the live C2 endpoint: the npm campaign resolves Socket.IO and upload traffic to 166.88.134.62 (ports 80/443), while other campaign variants of the same loader point to 198.105.127.210 and 23.27.202.27.
Stage 3 is a roughly 77 KB Socket.IO-based RAT (LZString-compressed string table, 337 entries) that fingerprints the host — detecting CI/sandbox environments via hostname markers (`github-runner`, `buildbot`, `sandbox-pool-`, `buildkitsandbox`, `cloudchamber`, WSL2, running as `root`) — and self-heals its own dependency by running `npm install socket.io-client`
Weaknesses (CWE)
CWE-506, CWE-829, CWE-494, CWE-522
Target sectors: software development, open source ecosystem, technology, cryptocurrency, devops ci-cd
Target regions: Global
Detections & IOCs
As of 2026-08-25, this threat has 9 detection rule(s) across Splunk SPL, Microsoft KQL and Sigma, and 48 indicator(s) of compromise. Detection query text and full IOC values are available to authenticated users and programmatically via the Threadlinqs MCP server (Purple tier). View plans.
Community OSINT corroboration
2 of this threat's indicators have also been reported by the open-source security community, which observed at least one of them before this report was published. Community sightings are unverified and are kept separate from Threadlinqs' curated indicators. Indicator values, reporters and campaign linkage are available to authenticated Red-tier users.
SUPPLY_CHAIN, CRITICAL, threat intelligence, cybersecurity, T1195, T1059, T1129, T1554, T1027, T1140, T1497, T1480, T1555, T1552