BLOG
Neutralinojs Compromised In DPRK Attack
DPRK threat actors compromised Neutralinojs as part of a larger attack that utilizes stolen GitHub credentials to force-push backdated malicious commits
By c0a15726-c5b1-4b0d-85e6-fe15553df9e2 ยท
The OpenSourceMalware team has identified a new attack on the popular development framework Neutralinojs. Neutralinojs is a platform that lets developers build lightweight cross-platform desktop applications using JavaScript, HTML and CSS. The project has 8400 stars on GitHub, 488 forks and dozens of contributers. The OSM team immediately removed the malicious code with a GitHub pull request and disclosed the attack to the Neutralinojs maintainers. We are working closely with the maintainers to provide threat intelligence and incident response assistance.
Four repositories belonging to the Neutralinojs GitHub organization -- a popular cross-platform desktop application framework with over 9,000 combined stars and 500+ forks -- have been compromised with malicious JavaScript payloads. The attack occurred on March 2, 2026 at 04:38 UTC, when all four repositories were force-pushed with malicious commits in a single automated 132-second burst. The commits were backdated between 5 and 35 days to blend in with legitimate repository history, making them appear as routine changes from January and February. The attack remained undetected for 3 days until March 5. The attacker used a consistent technique of hiding malicious code injections inside seemingly routine commits by appending obfuscated payloads after hundreds of whitespace characters.
Unfortunately while we have moved quickly to remove the malicious code, many users and projects have pulled the malicious versions since the compromise. Two command-and-control servers hosted on Vercel were removed by the Vercel security team after OSM alerted them to the endpoints.
The Background
This incident that affected the Neutralinojs GitHub organization is part of a larger ongoing attack targeting hundreds of open source maintainers and organizations by DPRK. OpenSourceMalware has attributed this new campaign to DPRK by leveraging our threat intelligence to tie the GitHub persona used in this attack to earlier atributed DPRK incidents. Additionally, the OSM team has identified the malware used in the final payload is the most recent Beavertail malware typically used by DPRK in their final payloads.
North Korean threat actors are known for their "Contagious Interview" fake recruiter campaigns, and the earlier "Dream Job" campaign. However, this latest attack is the first time we've seen North Korean threat actors compromise individual GitHub accounts with stolen credentials to poison upstream repositories. This should be concerning to anyone reading this as we don't know how DPRK has gotten these credentials. Do they have some yet unknown way of stealing these from unsuspecting GitHub users?
Our research is ongoing and you can expect more from us on this new broader attack very soon. For now, let's concentrate on Neutralinojs and the hundreds of developers that use and depend on this package every day.
Affected repositories
All four repositories were compromised in a single automated attack on March 2, 2026 between 04:38 and 04:40 UTC. The commit dates shown in the "Spoofed Date" column are the backdated timestamps the attacker embedded in the commits to make them appear as routine historical changes.
Repository
Stars
Actual Push (Mar 2)
Spoofed Date
Malicious Commit
Infected File
332
04:38:28 UTC
Feb 26, 2026
90665b4
.env, src/index.ts
8,370
04:39:09 UTC
Feb 27, 2026
d62abe0
spec/runner.js
113
04:40:04 UTC
Jan 26, 2026
720c0e3
src/constants.js
neutralinojs/neutralinojs.github.io
257
04:40:42 UTC
Feb 21, 2026
6c19a31
babel.config.js
Attack Timeline
How We Uncovered the Real Timeline
When we first examined the malicious commits, they appeared to span over a month -- from January 26 to February 27, 2026. However, git commit timestamps are trivially spoofable. The author.date and committer.date fields are set by the local git client and can be set to any value.
GitHub's server-side data tells a very different story. By examining GitHub Actions triggering_actor fields (which record who actually triggered a workflow run and cannot be forged), PR event logs, and push event timestamps, we discovered that all four repositories were compromised in a single 132-second automated attack on March 2, 2026, by a single GitHub account: alphagamer7.
The commit dates were deliberately backdated -- between 5 and 35 days -- to make the malicious commits blend seamlessly into the existing repository history. This timestamp spoofing technique is what initially led to the incorrect chronology.
The Real Attack: March 2, 2026 -- 04:38 to 04:40 UTC
The GitHub account alphagamer7 (Athif Shaffy), a Sri Lankan software developer who had write access to the neutralinojs organization since 2019, was used to force-push malicious commits across all four repositories in rapid succession. The account is most likely compromised -- and the Neutralinojs maintainers have taken actions to contain this users access while they identify what happened.
The attacker used two identity spoofing techniques: attributing commits to the project maintainer shalithasuranga on three repos, and to github-actions[bot] on the CLI repo. Only GitHub's unspoofable server-side triggering_actor field reveals the true pusher.
04:38:28 -- neutralino.js (spoofed date: February 26)
The client library repository was hit first. Commit `90665b4bed716c05e75ec181ddf7af9345fa1591`, attributed to Shalitha Suranga with the message "Add SendKeyState for computer.sendKey," amended a real feature commit with hidden malicious changes:
.gitignore -- removed
.envand.env.testfrom the ignore list, addedconfig.bat.env -- new file added containing a base64-encoded C2 URL
src/index.ts, package.json -- malicious code injected alongside legitimate feature code
The .env file contains:
AUTH_API_KEY="aHR0cHM6Ly9hdXRoLXJoby1kdW4udmVyY2VsLmFwcC9hcGk="Base64-decoding the value reveals a second command-and-control endpoint:
hxxps://auth-rho-dun[.]vercel[.]app/apiThis endpoint was live at the time of discovery, serving a 4,777-byte obfuscated JavaScript payload.
The .gitignore change is the attacker's signature: in every compromised repository, the attacker removes .env from the gitignore (allowing environment files containing C2 URLs to be committed) and adds config.bat (hiding their Windows batch tooling from the repository).
04:39:09 -- neutralinojs main repo (spoofed date: February 27)
Thirty-one seconds later, the highest-impact attack targeted the main framework repository (8,370 stars, 490 forks). Commit `d62abe0288901ba91fe7782094342d4ade2492ce`, attributed to Shalitha Suranga with the message "Update CHANGELOG.md," contains:
.gitignore -- added
config.batandnode_modulesCHANGELOG.md -- one line added (cover)
spec/runner.js -- payload injected

The spec/runner.js file is a test runner used by the project's CI pipeline. The payload was appended to the closing }; of the module export after a wall of whitespace, changing the file from ~3,000 bytes to over 8,000 bytes.
In addition to the main branch, alphagamer7 force-pushed 7 additional branches (pr1338, imgclipboard, cleanupwv, wincenter, refactunic, winpos, and the Dependabot PR #1580 branch) in 46 seconds -- spreading the malicious commit across as many refs as possible to maximize the chance of infection. These branches have since been deleted during cleanup.
04:40:04 -- neutralinojs-cli (spoofed date: January 26)
The CLI repository attack used the most sophisticated spoofing technique. Commit `720c0e39c02184952d24fb5dabe19d8d83c89f53`, attributed to github-actions[bot] with the message "Update changelog for v11.7.0," was backdated 35 days to January 26.
The commit contains three file changes:
CHANGELOG.md -- two lines added (legitimate-looking)
.gitignore -- removed
.envand.env.testfrom the ignore list, addedconfig.batsrc/constants.js -- one line modified (payload injected)

This was particularly clever because the repository had legitimate github-actions[bot] commits with the exact same message ("Update changelog for v11.7.0") from the real v11.7.0 release on January 26. Those legitimate commits only modified CHANGELOG.md. The attacker amended this commit type to also inject .gitignore and src/constants.js, making it appear as routine CI output. The 35-day backdate placed it right in the middle of the real release activity.
The modification to src/constants.js appears as a single-line change. Examining the file reveals the malicious payload appended to the end of the legitimate module export, separated by hundreds of space characters:
// Legitimate code at the start of the file:
module.exports = {
remote: {
binariesUrl: "https://github.com/neutralinojs/neutralinojs/releases/...",
// ... normal configuration ...
}
}; [~500 spaces] global['!']='9-547-3';var _$_1e42=...How to distinguish the malicious commit from the legitimate ones:
Commit
Author
Files Modified
Malicious?
ad39386
github-actions[bot]
CHANGELOG.md only
No
7cbcbc8
github-actions[bot]
CHANGELOG.md only
No
`720c0e3`
github-actions[bot]
CHANGELOG.md + .gitignore + src/constants.js
Yes
04:40:42 -- neutralinojs.github.io (spoofed date: February 21)
The final target was the documentation website. Commit `6c19a3106b6f6f2725c530e37bfac7f99c2cc82b`, attributed to Shalitha Suranga with the message "Update docusaurus.config.js," modifies three files:
.gitignore -- removed
.env.localfrom the ignore list, addedconfig.batbabel.config.js -- payload injected (file grew from ~100 bytes to 5,379 bytes)
docusaurus.config.js -- minor legitimate-looking change (cover)
The babel.config.js file is a textbook example of the injection technique. The legitimate content is just three lines:
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};After the closing semicolon, hundreds of space characters pad the line before the obfuscated payload begins. To a developer reviewing the diff on GitHub, the change appears as a trivial semicolon addition. The payload is only visible by scrolling far to the right, or by examining the raw file size.
Were the Neutralinojs NPM packages affected?
The Neutralinojs team maintains 8 NPM packages. The GitHub token that was compromised did not have access to NPM, and our analysis shows that the NPM packages were not compromised.
What does the malware it do and how does it work?
The malware uses a novel blockchain-based command and control architecture that stores encrypted payloads in Tron, Aptos, and Binance Smart Chain (BSC) transactions, making the C2 infrastructure resistant to takedown.
The chain consists of five stages, each progressively deobfuscated at runtime. We are going to simplify the first four stages of the attack, and then spend more time explaining the final stage.
Stage 0: The GitHub Compromise
The GitHub account alphagamer7, a long-time neutralinojs contributor with organization-level write access, is compromised. The threat actor uses this account to force-push amended commits across four neutralinojs repositories, injecting malicious payloads into spec/runner.js, src/constants.js, src/index.ts, and babel.config.js. The commits are attributed to shalithasuranga (the org owner) and github-actions[bot] to hide the true actor, with timestamps backdated weeks to blend into the repository history.
Stage 1: sfL Obfuscation Layers
Size: 4,784 bytes (C2-served variant)
Purpose: Decode and execute the blockchain C2 fetcher
SHA256 (C2 variant):
904afe0337fbbd79def403b3204f75b4c5fbe4e2271252d22c0307f9cbd14646
The first stage payload is a heavily obfuscated JavaScript file. The payload consists of three nested obfuscation layers, each with a distinct purpose.
Stage 2: Blockchain C2 Fetcher
Size: 3,858 bytes (decoded from Stage 1)
Purpose: Fetch encrypted payload from blockchain, XOR decrypt, execute
Again, this is a heavily obfuscated JavaScript file. Once decoded, Stage 2 reveals the core innovation of this malware family: a three-layer blockchain C2 architecture.
First, the payload queries the Tron API for the most recent transaction from a hardcoded wallet address. If Tron fails, it falls back to the Aptos blockchain. Finally, the malware uses the resolved transaction hash, and a JSON-RPC call retrieves the BSC transaction's input data. It's a nifty little way to hide payloads in an immutable environment.
Stage 3: Recursive C2 + Environment Configuration
Size: 5,895 bytes (Channel 1 XOR-decrypted from BSC)
SHA256:
11e87f7f27b3cf1a51e0b4b3903decd8945b5959eedf3cbc6be1920dab3c8823Purpose: Configure C2 infrastructure, set environment variables, recursively fetch Stage 4
Yet again, this is a heavily obfuscated JavaScript payload. This payload makes sure that the node environment and dependencies areprepared to download the final Beavertail malware.
Stage 4: The Final Payload
Malware Family: BeaverTail (DPRK Lazarus Group)
Campaign: "Contagious Interview" supply chain attack
File: stage4-decrypted.js (70,818 bytes)
SHA256: d4e269df0f50998c7ebf2bf56945d3d615fd6516702b1da8ac030ffcba735263
This last payload appears to be a new version of the Beavertail malware. The embedded ss_* command prefix in the sample is BeaverTail's unique signature, seen across all known samples from the DPRK "Contagious Interview" campaign.
What It Does
Primary Targets:
Discord - Steals authentication tokens and user data
VSCode - Compromises extensions (especially @vscode/deviceid)
Cursor Editor - Infects application resources
Python - Targets development environments
Attack Capabilities:
Credential Theft: Harvests Discord tokens, GitHub credentials, npm tokens, SSH keys, and cryptocurrency wallet data
Persistence: Injects malicious code into node_modules of multiple applications, modifying startup files (main.js, index.js)
Remote Control: Establishes WebSocket-based C2 over HTTPS (port 443) with full remote code execution
Data Exfiltration: Uploads source code, credentials, and system information to attacker infrastructure
Cross-Platform: Operates on Windows, macOS, and Linux
How It Works
Obfuscation:
1,101 RC4-encrypted strings using 72 unique 4-character keys
CPU-intensive array shuffle loop (checksum: 0xbe519) to evade analysis
19 wrapper functions obscure string decoder calls
Anti-sandbox checks prevent execution in virtualized environments
Command & Control: Uses BeaverTail's signature ss_ command prefix for WebSocket C2:
ss_eval64/ss_eval- Execute arbitrary JavaScriptss_connect- Establish C2 connectionss_info/ss_ip- Collect system informationss_upd/ss_upf- Update payload or upload filesss_dir/ss_fcd- Directory and file operationsss_inz- Initialize persistence
Detection
File Indicators:
*/node_modules/@vscode/deviceid/dist/index.js
*/node_modules/discord_desktop_core*/index.js
*/Cursor.app/Contents/Resources/app/*Code Signatures:
Functions named
ss_eval,ss_connect,ss_infoVariables:
_t_c,_t_s,_t_u(tracking/telemetry)Code names: "antigravity", "cloudchamber"
WebSocket patterns:
identify,client_id,emit
Command and Control Infrastructure
Two Vercel-hosted C2 endpoints are actively serving payloads:
C2 URL
Status
Payload Size
Variant Marker
hxxps://data-kappa[.]vercel[.]app/
Live
4,784 bytes
global['!']='8'
hxxps://auth-rho-dun[.]vercel[.]app/api
Live
4,777 bytes
global['!']='9'
The C2 payloads also reference blockchain addresses for resilient command delivery:
Tron:
TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP,TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcGAptos:
0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e
Impact Assessment
OpenSourceMalware gets involved
When our team initially saw that the neutralinojs/neutralinojs repository was compromised, and that it was a popular repository, we decided to get proactive. We authored a GitHub pull request that stripped out the malicious code from the spec/runner.js file.

One of the neutralinojs maintainers quickly merged the PR and the malicious code was removed from the origin.
Fork Contamination
We checked all 490 forks of the main neutralinojs/neutralinojs repository for the presence of the malicious commit SHA (d62abe0). At the beginning of the incident there were dozens of malicious forks visible on GitHub. As of 11 pm AEST there are only 16 forks remaining that are still malicious.
This is due to GitHub's shared object store architecture -- forks share the underlying git database with their parent repository. When the malicious commit was pushed upstream, it became accessible to every fork automatically. Similarly, when the OSM team pushed the fix PR, that meant that anyone who syncs with origin will get the clean version again.
Download Exposure
The @neutralinojs/neu CLI package receives approximately 7,150 downloads per month from npm. The @neutralinojs/lib client library receives approximately 5,250 downloads per month. While the npm packages themselves were not directly compromised in this attack, developers who clone the repositories directly (a common practice for contributors and those building from source) would receive the malicious payloads.
The Attacker's Playbook
Across all four compromised repositories, the attacker followed an identical operational procedure -- executed simultaneously via automation:
Amend existing commits -- take a real commit by the maintainer or CI bot, add malicious files to it, and force-push the rewritten history
Spoof the authorship -- attribute the amended commits to
shalithasurangaorgithub-actions[bot]so they appear to come from trusted sourcesBackdate the timestamps -- set commit dates weeks in the past so the malicious changes blend into existing repository history and don't appear at the top of the commit log
Choose plausible commit messages -- "Update CHANGELOG.md," "Update changelog for v11.7.0," "Add SendKeyState for computer.sendKey" -- messages that would not raise suspicion in a review
Modify `.gitignore` -- remove
.envfrom the ignore list (enabling C2 URL drops via.envfiles) and addconfig.bat(hiding Windows tooling)Inject the payload -- append the obfuscated sfL payload to a JavaScript file after hundreds of whitespace characters, making the injection invisible in standard diff views
Maximize infection surface -- force-push the same malicious commit into multiple branches (8+ branches on the main repo alone, including an open Dependabot PR)
The timestamp spoofing was effective: our initial analysis incorrectly concluded the attacks occurred over a month-long period. Only by examining GitHub's unspoofable server-side data (Actions triggering_actor fields and event timestamps) did we discover the true 132-second attack window.
Detection
The following strings are unique to this obfuscation family and can be used for detection:
# Primary detection -- search for the Layer 1 input string
grep -r 'rmcej%otb%' --include="*.js" --include="*.mjs" --include="*.cjs" --include="*.ts" .
# Secondary detection -- search for the scrambled "constructor" string
grep -r 'wuqktamceigynzbosdctpusocrjhrflovnxrt' --include="*.js" .
# File size anomaly -- config files should not be this large
find . -name "babel.config.js" -o -name "postcss.config.mjs" -o -name "tailwind.config.js" | \
xargs -I{} sh -c 'size=$(wc -c < "{}"); if [ "$size" -gt 2000 ]; then echo "SUSPICIOUS: {} ($size bytes)"; fi'GitHub code search:
https://github.com/search?q=rmcej%25otb%25&type=codeThis search currently returns 116 infected files across 106+ repositories, indicating the attack extends far beyond the Neutralinojs organization.
Indicators of Compromise
Malicious Commits (Neutralinojs Org)
All commits were force-pushed on 2026-03-02 between 04:38-04:40 UTC by the compromised alphagamer7 account. The "Spoofed Date" column shows the backdated timestamps embedded in the commits.
Repository
SHA
Actual Push (Mar 2)
Spoofed Date
Spoofed Author
neutralino.js
90665b4bed716c05e75ec181ddf7af9345fa1591
04:38:28
2026-02-26
shalithasuranga
neutralinojs
d62abe0288901ba91fe7782094342d4ade2492ce
04:39:09
2026-02-27
shalithasuranga
neutralinojs-cli
720c0e39c02184952d24fb5dabe19d8d83c89f53
04:40:04
2026-01-26
github-actions[bot]
neutralinojs.github.io
6c19a3106b6f6f2725c530e37bfac7f99c2cc82b
04:40:42
2026-02-21
shalithasuranga
C2 URLs
hxxps://data-kappa[.]vercel[.]app/hxxps://auth-rho-dun[.]vercel[.]app/api
C2 Payload Hashes (SHA256)
904afe0337fbbd79def403b3204f75b4c5fbe4e2271252d22c0307f9cbd14646(data-kappa)a507b74b6b1e25444c586bc67ae0244cba3037f2b39f25f7eb507ded97c373c1(auth-rho-dun)
Blockchain C2 Addresses
Tron:
TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAPTron:
TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcGAptos:
0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811eAptos:
0x3f0e5781d0855fb460661ac63257376db1941b2bb522499e4757ecb3ebd5dce3
Obfuscation Signatures
Layer 1 input string:
rmcej%otb%Layer 1 seed:
2857687sfL seed:
2667686sfL modulus:
4289487Scrambled constructor string:
wuqktamceigynzbosdctpusocrjhrflovnxrt
Attacker Artifacts in .gitignore
All compromised commits add config.bat to .gitignore and remove .env / .env.test entries.
Recommendations
For Neutralinojs Users
If you have cloned any Neutralinojs repository since March 2, 2026, check for the presence of the payload:
grep -r 'rmcej%otb%' .If you find the payload on a system where it was executed, assume full compromise -- rotate all credentials, tokens, and SSH keys accessible from that machine
Broader Context
This compromise of the Neutralinojs organization is part of a larger campaign. The same obfuscation family has been identified in 106+ repositories across GitHub, with payloads injected into a wide variety of JavaScript configuration files including tailwind.config.js, postcss.config.mjs, next.config.mjs, eslint.config.mjs, vite.config.ts, and application source files. The attack was carried out using the compromised GitHub account alphagamer7, a long-time neutralinojs contributor whose credentials were likely stolen through social engineering. A full analysis of the broader campaign, including attribution to a DPRK-linked threat actor operating under the GitHub handle "Polin9912," will be published in a subsequent report.