Threat reportSupply ChainTL-2026-5524
GHAPPIER Loader: npm Supply-Chain Compromise of @dforge-core/dforge-mcp 0.2.21 Beside DPRK-Linked PolinRider Campaign
CloudSEK reports GHAPPIER, a previously unreported loader operation spanning at least 65 public repositories, 73 infected files and 22 accounts. On 9 September 2026 an attacker used the maintainer access of npm package @dforge-core/dforge-mcp for 105 minutes, added a remote code loader, modified the release workflow and published malicious version 0.2.21 (latest for 35 min 38 s) with valid npm provenance before the maintainer reverted to clean 0.2.22. First disclosed .
Key facts
| Identifiers | TL-2026-5524 |
|---|---|
| Also known as | GHAPPIER |
| Severity | HIGH |
| First disclosed | |
| Affected products | dforge-core @dforge-core/dforge-mcp (npm) |
| Weaknesses (CWE) | CWE-506 |
| Malware | BeaverTail - S1246, GHAPPIER, InvisibleFerret - S1245, core |
| MITRE ATT&CK techniques | T1005 Data from Local System, T1027 Obfuscated Files or Information, T1059.004 Unix Shell, T1059.007 JavaScript, T1070.004 File Deletion, T1078 Valid Accounts, T1102.001 Dead Drop Resolver, T1140 Deobfuscate/Decode Files or Information, T1195.002 Compromise Software Supply Chain, T1204.002 Malicious File, T1555.003 Credentials from Web Browsers |
| Motivation | UNKNOWN |
| Target sectors | technology, software development, open-source |
| Target regions | Global |
| Detection rules | 9 (1 shown free) |
| Sources | 8 cited sources |
Detection coverage
As of 2026-11-09, Threadlinqs Intelligence publishes 9 detection rule(s) for TL-2026-5524 across Splunk SPL, Microsoft KQL and Sigma, covering 14 indicator(s) of compromise. The whole corpus is readable without an account. Every threat comes with one complete detection in Splunk SPL, Microsoft KQL and Sigma, free for everyone; a free account adds daily TL Creds to unlock the rest. Paid plans add a larger daily allowance, correlation and the MCP server. Threadlinqs MCP server · View plans.
Free detection: GHAPPIER — Malicious @dforge-core/dforge-mcp 0.2.20/0.2.21 Install or MCP Server Spawning Shell — Exploitation Phase
TL-2026-5524: detects endpoint execution of the trojanised npm releases @dforge-core/dforge-mcp@0.2.20 / 0.2.21 (GHAPPIER loader, published with valid npm provenance on 2026-09-09), and the behaviour that matters most: a JavaScript runtime launched for dforge-mcp spawning a shell, interpreter or downloader. The loader runs when the MCP server is started, not at install, so the parent/child branch catches hosts that actually executed the payload (T1195.002, T1059.007, T1059.004, T1204.002).
- Languages: Splunk SPL, Microsoft KQL, Sigma
- ATT&CK: T1195.002, T1059.007, T1059.004, T1204.002
- Data sources: sysmon, index=sysmon sourcetype=XmlWinEventLog:Microsoft-Windows-Sysmon/Operational (EventCode 1), DeviceProcessEvents, linux process_creation
- Severity: high
- Confidence: high
Splunk SPL
index=sysmon sourcetype="XmlWinEventLog:Microsoft-Windows-Sysmon/Operational" EventCode=1 earliest=-24h
| eval img=lower(Image), pimg=lower(ParentImage), cmd=lower(CommandLine), pcmd=lower(ParentCommandLine)
| eval bad_release=if(match(cmd,"@dforge-core/dforge-mcp@0[.]2[.]2[01]([^0-9]|$)"),1,0)
| eval mcp_child=if(match(pimg,"(node|bun|deno)[.]exe$") AND match(pcmd,"dforge-mcp") AND match(img,"(cmd|powershell|pwsh|curl|wscript|cscript|mshta|bash|sh)[.]exe$"),1,0)
| where bad_release=1 OR mcp_child=1
| eval reason=case(bad_release=1 AND mcp_child=1,"malicious release executed and spawned shell",bad_release=1,"malicious dforge-mcp 0.2.20/0.2.21 referenced in command line",true(),"dforge-mcp server spawned shell or downloader")
| stats count earliest(_time) as first_seen latest(_time) as last_seen values(reason) as reasons values(CommandLine) as command_lines values(ParentCommandLine) as parent_command_lines by Computer, User
| convert ctime(first_seen) ctime(last_seen)Microsoft KQL
let lookback = 24h;
let bad_pkg = dynamic(["@dforge-core/dforge-mcp@0.2.21", "@dforge-core/dforge-mcp@0.2.20"]);
let runtimes = dynamic(["node", "node.exe", "bun", "bun.exe", "deno", "deno.exe"]);
let install_hits = DeviceProcessEvents
| where Timestamp > ago(lookback)
| where ProcessCommandLine has_any (bad_pkg)
| extend DetectionReason = "Malicious dforge-mcp release referenced in command line";
let mcp_children = DeviceProcessEvents
| where Timestamp > ago(lookback)
| where InitiatingProcessFileName in~ (runtimes)
| where InitiatingProcessCommandLine has "dforge-mcp"
| where FileName in~ ("cmd.exe", "powershell.exe", "pwsh.exe", "curl.exe", "curl", "wget", "sh", "bash", "dash", "zsh", "mshta.exe", "wscript.exe", "cscript.exe")
| extend DetectionReason = "dforge-mcp server spawned shell or downloader";
union install_hits, mcp_children
| project Timestamp, DeviceName, AccountName, FileName, ProcessCommandLine, InitiatingProcessFileName, InitiatingProcessCommandLine, DetectionReason
| order by Timestamp descSigma
title: GHAPPIER Malicious dforge-mcp npm Release Execution or Shell Spawn
id: 6b0e3a52-4c1d-4f7e-9a21-5d8c7e1f3b01
status: experimental
description: Detects installation or execution of the trojanised @dforge-core/dforge-mcp 0.2.20/0.2.21 npm releases (GHAPPIER loader) and a Node.js dforge-mcp server spawning a shell or downloader on Linux hosts.
references:
- https://www.cloudsek.com/blog/ghappier-malware-loader-npm-supply-chain-attack
- https://orca.security/resources/research/ghappier-loader-npm-supply-chain-attack/
author: Threadlinqs Team
date: 2026/11/09
tags:
- attack.initial-access
- attack.t1195.002
- attack.execution
- attack.t1059.007
- attack.t1059.004
- attack.t1204.002
logsource:
category: process_creation
product: linux
detection:
selection_install:
CommandLine|contains:
- '@dforge-core/dforge-mcp@0.2.21'
- '@dforge-core/dforge-mcp@0.2.20'
selection_parent:
ParentImage|endswith: '/node'
ParentCommandLine|contains: 'dforge-mcp'
selection_child:
Image|endswith:
- '/sh'
- '/bash'
- '/dash'
- '/zsh'
- '/curl'
- '/wget'
condition: selection_install or (selection_parent and selection_child)
falsepositives:
- Security teams or researchers intentionally installing the malicious version in a sandbox
- Legitimate dforge-mcp tooling that shells out to helper scripts
level: highKnown false positives:
- Security researchers or sandboxes deliberately installing 0.2.20/0.2.21 for analysis
- Legitimate MCP server wrappers that launch helper shells from a Node.js parent
Tuning notes: Exclude known analysis sandboxes by Computer. The package-reference branch is near-zero noise; if the parent/child branch is noisy, restrict child images to curl/wget/mshta/powershell. Extend the Image list if Sysmon for Linux feeds a separate index. Also hunt lockfiles (package-lock.json, pnpm-lock.yaml, yarn.lock) for 0.2.21 and treat hosts that launched the MCP server as compromised.
- GHAPPIER — dforge-mcp 0.2.21 Execution and PolinRider Forced Git Push Propagation (Defender XDR) — Exploitation Phase · unlock with TL Creds
- GHAPPIER — Self-Deleting Implant After dforge-mcp Launch (Defender XDR) — Post-Exploitation · unlock with TL Creds
- GHAPPIER/PolinRider — Node.js Blockchain Dead-Drop and socket.io C2 Connections (Defender XDR) — C2 · unlock with TL Creds
- GHAPPIER — dforge-mcp Release Execution and Linux Shell Spawn (Sigma) — Exploitation Phase · unlock with TL Creds
- PolinRider/InvisibleFerret — Node.js Access to Browser Credential Stores (Sigma) — Post-Exploitation · unlock with TL Creds
- GHAPPIER/PolinRider — Node.js DNS Resolution of Blockchain Dead-Drop Hosts (Sigma) — C2 · unlock with TL Creds
- GHAPPIER/PolinRider — Obfuscated Inline Node.js Loader Stage Execution — Post-Exploitation · unlock with TL Creds
- GHAPPIER/PolinRider — Blockchain Dead-Drop C2 Resolution From Node.js or Known Wallet Addresses — C2 · unlock with TL Creds
Indicators of compromise
As of 2026-11-09, Threadlinqs Intelligence tracks 14 indicators of compromise for TL-2026-5524.
Technical analysis
GHAPPIER is a loader family reported by CloudSEK (Vikas Kundu, 20 Sep 2026) that spans at least 65 public repositories, 73 infected files and 22 accounts. The headline incident is the npm package @dforge-core/dforge-mcp (an MCP server). On 9 September 2026 someone with push access to the package's GitHub repository used the maintainer's access for 105 minutes. CloudSEK could not confirm how access was obtained; it suspects a developer machine infected through a malicious extension or package, and considers PolinRider's documented harvesting of cached git credentials the leading hypothesis.
Sequence of events: the attacker added a remote code loader to the package; changed three lines so that any push to main started the release workflow; roughly 14 minutes later rewrote the workflow so it could publish unattended; and published version 0.2.21. A first malicious release attempt, 0.2.20, failed and broke installation. Version 0.2.21 was the npm 'latest' for 35 minutes 38 seconds before the maintainer published clean 0.2.22.
Version 0.2.21 carried VALID npm provenance: it was built by GitHub Actions through OIDC trusted publishing and its Sigstore attestation names the attacker's commit, so it passed 'npm audit signatures'. Under trusted publishing, push access to the repository was effectively publish access and no npm credential was needed. Provenance shows where an artifact was built, not whether the source was honest.
The payload is a single line at line 3320 of a roughly 99 KB configuration file. It opens a four-stage chain whose final implant is a general-purpose remote shell that deletes itself from disk as it runs. The loader executes when the MCP server is launched, not at install time, so systems that installed 0.2.21 but never started it did not run it. All stages still responded when CloudSEK tested five days after the package was withdrawn.
PolinRider link: in a second victim repository CloudSEK found a second payload family matching PolinRider, a campaign tracked by OpenSourceMalware since March 2026 and described by OpenSourceMalware as DPRK-linked (Lazarus / Contagious Interview cluster). An exact match was still beaconing at time of writing. That payload has no hardcoded C2: the C2 address is read from an Ethereum wallet, with configuration stored in the 20-byte recipient address of an empty transaction costing about $0.20. CloudSEK cites the DPRK attribution (from the researchers behind NullReceiver) rather than asserting it; its one independent check did not confirm it, and CloudSEK gives no attribution of its own for GHAPPIER. OpenSourceMalware's public PolinRider reporting documents config-file payload appending, blockchain dead-drops (TRON, Aptos, BSC), BeaverTail/InvisibleFerret payloads and git history rewriting; those details are PolinRider context and are not confirmed as GHAPPIER behavior.
Impact: CloudSEK reports no evidence of a successful compromise of any organization and describes the exposed population as small. No CVE exists. As of CloudSEK's report no advisory existed in OSV, the GitHub Advisory Database or from the maintainer. The full IOC list (two delivery hostnames, the socket endpoint, Ethereum wallet, hashes) is in CloudSEK's linked PDF, which could not be machine-read during this research, so those values are not included here.
MITRE ATT&CK techniques by tactic:
Collection
Defense Evasion
T1027 Obfuscated Files or Information; T1070.004 File Deletion; T1140 Deobfuscate/Decode Files or Information
Execution
T1059.004 Unix Shell; T1059.007 JavaScript; T1204.002 Malicious File
Initial Access
T1078 Valid Accounts; T1195.002 Compromise Software Supply Chain
Command and Control
Credential Access
Remediation
Patches
- Upgrade to @dforge-core/dforge-mcp 0.2.22 (clean)
Immediate actions
- Pin @dforge-core/dforge-mcp to 0.2.22 or later
- Treat any lockfile pinning @dforge-core/dforge-mcp 0.2.21 as a potential indicator of compromise
- Rotate credentials on any system where 0.2.21 was executed (MCP server launched), including CI/CD systems
- Block the GHAPPIER socket endpoint and the two delivery hostnames listed in the CloudSEK report
Workarounds
- Audit whether CI/CD or developer hosts executed 0.2.21 and isolate any host that launched it
Longer-term hardening
- Alert on changes to the trigger block of release workflows in GitHub Actions
- Require review/approval (protected branches, environment protection) before trusted-publishing workflows can publish
- Do not treat npm provenance as proof of source integrity; it shows where a build ran, not whether the source was honest
- Hunt for artifacts left by the four-stage chain rather than the self-deleting final implant
- Protect developer workstations against malicious extensions/packages and cached git credential theft
Affected and fixed versions
- dforge-core — @dforge-core/dforge-mcp (npm)
Vulnerable versions: 0.2.21 (malicious); 0.2.20 (malicious release attempt that broke installation)
Fixed in: 0.2.22
Timeline
- OpenSourceMalware publishes its first PolinRider report (675 compromised GitHub repositories); the DPRK-linked campaign is tracked from March 2026.
- PolinRider takeover of the Xpos587 GitHub account (git2md, markfetch) and bulk Go-module compromise, per OpenSourceMalware, illustrating the account-takeover tradecraft that CloudSEK suspects behind GHAPPIER.
- OpenSourceMalware updates its PolinRider report to 4,367 repositories, with spread from GitHub into Go, Packagist, npm and PyPI.
- The maintainer reverts and publishes clean version 0.2.22, ending the exposure window.
- Malicious 0.2.21 is published via GitHub Actions OIDC trusted publishing with valid npm provenance (Sigstore attestation naming the attacker's commit); it is the latest release for 35 minutes 38 seconds.
- An attacker uses the maintainer's access to @dforge-core/dforge-mcp for 105 minutes: adds a remote code loader, a malicious release 0.2.20 fails and breaks installation, three lines are changed so any push to main starts the release workflow, and the workflow is rewritten ~14 minutes later for unattended publishing.
- Approximately five days after withdrawal, CloudSEK tests the chain and finds every stage still responding; an exact PolinRider payload match is still beaconing.
- CloudSEK publishes the GHAPPIER report (65+ repositories, 73 infected files, 22 accounts); no advisory exists in OSV, the GitHub Advisory Database or from the maintainer.
- Infosecurity Magazine reports on GHAPPIER's abuse of npm trusted publishing; Orca Security publishes its analysis the following day.
Sources cited
- CloudSEK GHAPPIER full report (PDF, contains IOC list) cdn.cloudsek.com · Research · 2026-09-20
- GHAPPIER loader npm supply chain attack (Orca Security) orca.security · Research · 2026-09-22
- PolinRider jumps the fence (OpenSourceMalware) opensourcemalware.com · Research
- ChainVeil and ViteVenom: DPRK PolinRider campaign (OpenSourceMalware) opensourcemalware.com · Research · 2026-07-15
- GHAPPIER - One loader, sixty-five repositories, twenty-two accounts: an unreported loader family beside DPRK's PolinRider campaign (CloudSEK) cloudsek.com · Blog · 2026-09-20
- Attackers Abuse npm Trusted Publishing in GHAPPIER Campaign (Infosecurity Magazine) infosecurity-magazine.com · News · 2026-09-21
- North Korean Hackers Target Open-Source Devs With Hidden Malware That Steals Source Code And Credentials (Expert Insights) expertinsights.com · News
- PolinRider coverage (devops.com) devops.com · News
Previous and next in supply chain
- Attackers Exploit Third-Party Service Provider Platform to Conduct Fraudulent Transfers Against French Bank Crédit Mutuel Arkéa (~€100–118M, Laundered via Stablecoins to Monero) Previous report
- GhostAction Supply Chain Campaign Expands: Malicious GitHub Actions Workflow Steals Cloud and AI Credentials from Tens of Thousands of Repositories Next report
Further reading
Drafted by the Threadlinqs research pipeline from 8 sources; published ; last updated —. Reviewed .