Skip to navigation

Threat reportSupply ChainTL-2026-2588

GHAPPIER Loader: npm Supply-Chain Compromise of @dforge-core/dforge-mcp Linked to DPRK PolinRider Campaign

highACTIVE

CloudSEK identified GHAPPIER, a previously unreported four-stage loader delivered through a 105-minute maintainer-account takeover of the npm package @dforge-core/dforge-mcp (malicious v0.2.21, reverted to clean v0.2.22). The malicious release carried valid npm provenance via abused GitHub Actions OIDC trusted publishing, used an Ethereum-blockchain address as its command source, and self-deleted its final implant.

Key facts for GHAPPIER npm Ethereum C2 loader

IdentifiersTL-2026-2588
Also known asGHAPPIER
SeverityHIGH
Exploitation statusExploited in the wild
Affected productsdforge-core @dforge-core/dforge-mcp (npm package)
AttributionPolinRider (low confidence)
Nation-state nexusNorth Korea
MalwareBeaverTail - S1246, DEV#POPPER, GHAPPIER, OmniStealer, PolinRider, core
Tools0xa322e5f3d311d3080e6f0121063e9adc2490ef1a, 0xa658863ea658863e68656c6c6f6970626f742121, 0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e, NullReceiver, TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP, TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG
MITRE ATT&CK techniquesT1027 Obfuscated Files or Information, T1059.007 JavaScript, T1070.004 File Deletion, T1071.001 Web Protocols, T1078 Valid Accounts, T1102.002 Bidirectional Communication, T1140 Deobfuscate/Decode Files or Information, T1195.002 Compromise Software Supply Chain, T1199 Trusted Relationship, T1552.001 Credentials In Files, T1583.001 Domains, T1585 Establish Accounts and 2 more in the attack flow
MotivationFINANCIAL
Target sectorssoftware-development, technology, open-source-supply-chain, cryptocurrency
Target regionsGlobal
Detection rules9 (1 shown free)
Sources10 cited sources

Detection coverage for GHAPPIER npm Ethereum C2 loader

As of 2026-09-20, Threadlinqs Intelligence publishes 9 detection rule(s) for TL-2026-2588 across Splunk SPL, Microsoft KQL and Sigma, covering 24 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: GitHub Actions Workflow Rewritten to Unattended Auto-Publish Before npm Release (GHAPPIER Pattern)

Detects a push modifying a .github/workflows/*.yml file on a trusted-publishing-enabled repository's main branch, followed within 30 minutes by an OIDC trusted-publisher npm release — the exact 14-minute-into-compromise sequence CloudSEK observed in the GHAPPIER/dforge-mcp incident, where a rewritten CI workflow forced an unreviewed publish of malicious v0.2.21.

  • Languages: Splunk SPL, Microsoft KQL, Sigma
  • ATT&CK: T1195.002, T1199, T1078, T1552.001
  • Data sources: github_audit_log, npm_registry_audit
  • Severity: high
  • Confidence: medium

Splunk SPL

index=github_audit sourcetype="github:audit" action="git.push" ref="refs/heads/main" file_path="*.github/workflows/*.yml" repository_settings.trusted_publishing=true
| rename actor as workflow_editor, _time as workflow_edit_time, repository as repo
| join type=inner max=0 repo
    [ search index=npm_registry sourcetype="npm:audit" action="package.publish" publish_method="oidc-trusted-publisher"
      | rename actor as publish_actor, _time as publish_time, repository as repo
      | fields repo, publish_actor, publish_time, package_name, package_version ]
| eval minutes_between=round((publish_time-workflow_edit_time)/60,2)
| where minutes_between>=0 AND minutes_between<=30
| table workflow_edit_time, workflow_editor, publish_time, publish_actor, repo, package_name, package_version, minutes_between
| sort - publish_time

Microsoft KQL

let WorkflowEdits = GitHubAuditLogPolling_CL
| where TimeGenerated > ago(24h)
| where Action_s in ("git.push", "workflow.rerun")
| where FilePath_s has ".github/workflows/" and Ref_s == "refs/heads/main"
| project WorkflowEditTime = TimeGenerated, WorkflowEditor = Actor_s, Repository_s, FilePath_s;
let Publishes = NpmRegistryAuditLogs_CL
| where TimeGenerated > ago(24h)
| where Action_s == "package.publish" and PublishMethod_s == "oidc-trusted-publisher"
| project PublishTime = TimeGenerated, PublishActor = Actor_s, Repository_s, PackageName_s, PackageVersion_s;
WorkflowEdits
| join kind=inner (Publishes) on Repository_s
| extend MinutesBetween = datetime_diff('minute', PublishTime, WorkflowEditTime)
| where MinutesBetween between (0 .. 30)
| project WorkflowEditTime, WorkflowEditor, PublishTime, PublishActor, Repository_s, PackageName_s, PackageVersion_s, MinutesBetween
| sort by PublishTime desc

Sigma

title: GitHub Actions Workflow Rewritten to Auto-Publish Without Review (GHAPPIER/PolinRider Pattern)
id: 7e2f1a44-6b3d-4c9a-9f21-1a2b3c4d5e6f
status: experimental
description: Detects a push modifying a GitHub Actions workflow file on the default branch of a trusted-publishing-enabled repository, consistent with the GHAPPIER/PolinRider technique of rewriting CI to auto-publish on push without manual review.
references:
    - https://www.cloudsek.com/blog/ghappier-malware-loader-npm-supply-chain-attack
author: AII-Detector — ThreadLinqs Intelligence
date: 2026-09-20
tags:
    - attack.resource-development
    - attack.initial-access
    - attack.t1195.002
    - attack.t1199
    - attack.t1078
    - attack.t1552.001
logsource:
    category: application
    product: github
    service: audit
detection:
    selection_push:
        action: 'git.push'
        ref: 'refs/heads/main'
        file_path|contains: '.github/workflows/'
    selection_trusted_publishing:
        repository_settings.trusted_publishing: true
    condition: selection_push and selection_trusted_publishing
falsepositives:
    - Authorized DevOps engineer performing routine CI/CD pipeline maintenance immediately before a scheduled release
level: high

Known false positives:

  • Authorized DevOps engineer performing routine CI/CD pipeline maintenance immediately before a scheduled release
  • Automated dependency-bot (Dependabot/Renovate) workflow updates that coincidentally precede a legitimate publish

Tuning notes: Baseline against the org's known CI change-management calendar; correlate publish_actor/PublishActor against the approved release-engineer roster; suppress rows where the workflow edit carries a linked, approved change-ticket ID.

8 more detection rules (Splunk SPL, Microsoft KQL, Sigma) · unlock with TL Creds. Sign up free to unlock the detection rules or query them with the Threadlinqs MCP server (Purple)
  • GitHub Actions Workflow Rewritten to Unattended Auto-Publish Before npm Release (GHAPPIER Pattern) · unlock with TL Creds
  • Obfuscated Node.js Loader Execution With Rapid Self-Deletion (GHAPPIER Loader Pattern) · unlock with TL Creds
  • Outbound Beaconing to Public Blockchain RPC Endpoints Consistent With GHAPPIER/PolinRider Dead-Drop C2 · unlock with TL Creds
  • GitHub Actions Workflow Rewritten to Auto-Publish Without Review (GHAPPIER/PolinRider Pattern) · unlock with TL Creds
  • Obfuscated Node.js Loader Execution With Rapid Self-Deletion (GHAPPIER Loader Pattern) · unlock with TL Creds
  • Build/Developer Host Beaconing to Blockchain RPC Endpoints (GHAPPIER/PolinRider Dead-Drop C2) · unlock with TL Creds
  • Obfuscated Node.js Loader Execution With Rapid Self-Deletion (GHAPPIER Loader Pattern) · unlock with TL Creds
  • Outbound Beaconing to Public Blockchain RPC Endpoints Consistent With GHAPPIER/PolinRider Dead-Drop C2 · unlock with TL Creds

Indicators of compromise for GHAPPIER npm Ethereum C2 loader

As of 2026-09-20, Threadlinqs Intelligence tracks 24 indicators of compromise for TL-2026-2588.

Sample indicators (3 of 24)

  • IP address: 166.88.134[.]62
  • Domain: default-configuration[.]vercel[.]app
  • URL: hxxps://1rpc[.]io/eth

Defanged, TLP:CLEAR. Full set of 24 indicators: unlock with TL Creds.

24 indicators of compromise · unlock with TL Creds. Compare plans

Community OSINT corroboration for GHAPPIER npm Ethereum C2 loader

1 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.

How GHAPPIER npm Ethereum C2 loader works

Payload and infrastructure overlap ties GHAPPIER to the DPRK-linked PolinRider campaign, which has separately compromised 65+ npm/GitHub repositories in this incident and, cumulatively since March 2026, thousands of repositories across npm, PyPI, Go, Packagist, and Chrome Web Store.

On 2026-09-09, an attacker held valid push/publish-equivalent access to the @dforge-core/dforge-mcp npm maintainer account for a 105-minute window. Fourteen minutes into that window the GitHub Actions CI workflow was rewritten to auto-publish on push to main without manual review, and a remote-code loader was injected as a single line (line 3320 of a 99KB configuration file) into otherwise-legitimate package source. Version 0.2.21 reached the npm registry and remained the latest release for 35 minutes 38 seconds before the maintainer reverted the account and published a clean v0.2.22. Because the malicious release was built through the repository's legitimate GitHub Actions OIDC trusted-publishing workflow, it carried a genuine npm provenance statement and an authentic Sigstore attestation naming the attacker's own commit -- CloudSEK's central finding is that provenance attests where an artefact was built, not whether its source was honest, so trusted publishing let a single compromised push credential produce a fully 'verified' malicious release.

The payload is a four-stage loader: an initial injected loader in package source, a rewritten CI workflow that forces unattended publishing, the published malicious package itself, and a final implant that deletes itself from disk the instant it executes, leaving no artefact for a post-incident file sweep to find. Instead of a traditional domain- or IP-based C2 channel, the PolinRider-linked payload reads its command source from an Ethereum blockchain wallet address: a twenty-byte recipient address is embedded in an otherwise-empty transaction costing roughly $0.20, giving the operator a C2 channel with no domain to suspend, no host to seize, and no account to disable. CloudSEK reports one socket endpoint and two delivery hostnames tied to the campaign (values withheld from the public blog behind a gated full report) and states that an exact-match sample of the payload was still beaconing as the report was being written, more than five days after the malicious npm version was withdrawn.

This blockchain dead-drop design mirrors -- and CloudSEK cites payload/infrastructure overlap with -- the DPRK-linked PolinRider campaign, tracked publicly since March 2026, which has grown from 675 to over 1,900 GitHub repositories by April 2026 and, per subsequent reporting, expanded across npm, PyPI, Go modules, Packagist, and a Chrome extension (108-111 unique packages, ~160-200 release artifacts) by July 2026, with a further compromised high-download Packagist package (visanduma/nova-two-factor, 700,000+ downloads) disclosed by Socket on 2026-09-17, three days before the GHAPPIER report. PolinRider's own blockchain C2 uses TRON and Aptos (with BNB Smart Chain observed) dead-drop addresses to fetch XOR-encrypted second-stage payloads executed via eval(), delivering RATs and infostealers (DEV#POPPER, OmniStealer) and, per OpenSourceMalware, a variant of the DPRK Beavertail malware family. A closely related technique named NullReceiver (published August 2026) encodes a C2 IP directly in the recipient-address bytes of an Ethereum wallet's most recent outbound transaction -- the same 'address-as-C2' primitive GHAPPIER's payload uses -- and was found in separate npm packages (bianira-ui, fluid-type-ui) tied to the same DPRK developer-targeting cluster (Contagious Interview / Famous Chollima).

CloudSEK is explicit that its North Korea attribution is inherited from NullReceiver researchers rather than independently re-verified, and that 'nothing here evidences a successful compromise of any organisation' downstream of the npm package -- the incident documents a successful supply-chain injection and a live, still-functional payload, not confirmed victim compromise. Because the final implant self-deletes, CloudSEK recommends defenders sweep for the artefacts the chain leaves behind (a rewritten CI workflow, the injected loader line, transient blockchain-RPC network connections) rather than searching for the implant file itself, which will not be there to find.

MITRE ATT&CK techniques by tactic:

Defense Evasion

T1027 Obfuscated Files or Information; T1070.004 File Deletion; T1140 Deobfuscate/Decode Files or Information

Execution

T1059.007 JavaScript

Command and Control

T1071.001 Web Protocols; T1102.002 Bidirectional Communication

Initial Access

T1078 Valid Accounts; T1195.002 Compromise Software Supply Chain; T1199 Trusted Relationship

Credential Access

T1552.001 Credentials In Files

Resource Development

T1583.001 Domains; T1585 Establish Accounts; T1587.001 Malware; T1608 Stage Capabilities

Remediation for GHAPPIER npm Ethereum C2 loader

Patches

  • Upgrade @dforge-core/dforge-mcp to v0.2.22, the clean release the maintainer published immediately after revoking attacker access

Immediate actions

  • Audit npm installs and lockfiles for @dforge-core/dforge-mcp version 0.2.21; pin/upgrade to 0.2.22 or later
  • Block CloudSEK's identified delivery hostnames and socket endpoint at network egress once the full report's IOC list is obtained
  • Rotate any credentials (git, npm, CI secrets) that were active on developer or CI systems during or after the 2026-09-09 105-minute compromise window
  • Audit GitHub Actions workflow files across trusted-publishing-enabled repositories for unauthorized auto-publish or auto-execute modifications

Workarounds

  • Sweep for the artefacts the four-stage chain leaves behind (rewritten workflow files, the injected loader line, transient blockchain-RPC connections) rather than for the self-deleting final implant, which leaves no file on disk to find

Longer-term hardening

  • Do not treat npm provenance or Sigstore attestation as a proxy for source-code integrity -- provenance proves where an artefact was built, not that its source was honest
  • Require branch protection and mandatory code review on any branch wired to a trusted-publishing OIDC workflow so a single compromised push credential cannot trigger an unreviewed publish
  • Monitor build and developer-environment egress for connections to public blockchain RPC endpoints (Ethereum/TRON/Aptos/BSC) as a dead-drop-resolver C2 detection signal
  • Apply install-time behavioral scanning and SBOM tracking to MCP/AI-developer-tooling npm dependencies given active DPRK targeting of the AI tooling supply chain

Affected and fixed versions

  • dforge-core — @dforge-core/dforge-mcp (npm package)
    Vulnerable versions: 0.2.21
    Fixed in: 0.2.22

Timeline of GHAPPIER npm Ethereum C2 loader

  • Earliest known locked-ioc-20 tradecraft observed retroactively: anti-dated commits later found in compromised Packagist repositories.
  • OpenSourceMalware publishes the first public locked-ioc-20 analysis, documenting 675 infected GitHub repositories across 352 owners.
  • locked-ioc-20 rotates its JavaScript obfuscator variant (marker 'Cot%3t=shtP') in apparent evasion of published YARA rules.
  • locked-ioc-20's documented footprint reaches 1,951 repositories across 1,047 owners, a 2.9x growth in five weeks.
  • The Visanduma GitHub organization's repositories, including the 700,000+ download locked-ioc-10 Packagist package, are compromised via the LaHiRu developer account (disclosed later by Socket).
  • An account takeover triggers bulk compromise of Go modules resolving directly from compromised Git repositories, around 10:00 UTC.
  • TheHackerNews reports North Korean-linked actors have published 108 unique malicious packages (162 release artifacts) across npm, Packagist, Go modules, and a Chrome extension, confirming cross-ecosystem spread.
  • OpenSourceMalware reports locked-ioc-20's blockchain second stage (TRON/Aptos/BSC) now delivers the locked-ioc-22 RAT and locked-ioc-18 infostealer.
  • Researchers (credited by CloudSEK as the source of DPRK attribution) publish the locked-ioc-17 technique: decoding a C2 IP from the recipient-address bytes of a DPRK-controlled Ethereum wallet's most recent outbound transaction.
  • The maintainer reverts the compromised account and publishes clean version 0.2.22, ending the 105-minute exposure window.
  • Fourteen minutes into the compromise window the GitHub Actions CI workflow is rewritten to auto-publish on push to main, the locked-ioc-24 loader is injected as a single line into package source, and malicious version 0.2.21 is published to npm with valid OIDC provenance and Sigstore attestation, remaining live for 35 minutes 38 seconds.
  • An attacker gains a 105-minute window of push/publish-equivalent access to the locked-ioc-11 npm maintainer account.
  • Five or more days after the malicious npm version's withdrawal, all four locked-ioc-24 loader stages remain functional when tested.
  • Socket discloses the locked-ioc-10 Packagist compromise, describing locked-ioc-20's continued spread via compromised GitHub accounts.
  • CloudSEK publishes the locked-ioc-24 research, citing payload and infrastructure overlap with locked-ioc-20 and noting an exact-match sample was still beaconing as the report was written.

Sources cited for GHAPPIER npm Ethereum C2 loader

More from PolinRider

Previous and next in supply chain

Further reading

Drafted by the Threadlinqs research pipeline from 10 sources; published ; last updated —.

Threadlinqs Intelligence — Real-Time Threat Detection Platform

[ 0 threats ] [ 0 det ] [ CRIT: 0 ] [ HIGH: 0 ]
// threat_feed
$ sort --newest
Showing all threats

Live intelligence console

Threat level

Latest Threats