Push Access Was Publish Access: A Backdoored MCP Server Shipped With Valid Provenance
On 9 September 2026, someone spent 105 minutes inside the maintainer account of @dforge-core/dforge-mcp — an MCP server that exposes 35 scaffold, patch, validate, pack and install tools to coding agents including Claude Code, Cursor and Zed. They added a remote code loader, changed three lines so any push to main triggered the release workflow, rewrote that workflow fourteen minutes later so it could publish unattended, and shipped the result as version 0.2.21.
CloudSEK, which published the analysis on 20 September, names the loader GHAPPIER and traces it across at least 65 public repositories, 73 infected files and 22 accounts. We confirmed the release window independently against the npm registry: 0.2.20 published at 16:44:29 UTC, the malicious 0.2.21 at 17:19:50 UTC, and the clean 0.2.22 at 17:55:28 UTC — a live window of roughly 35 minutes. Version 0.2.21 is no longer served; latest is now 0.2.29.
Nothing was forged. That is the finding.
The malicious release carried valid npm provenance. It was built by GitHub Actions through OIDC trusted publishing, and its Sigstore attestation still sits in the append-only log naming the attacker’s own commit. No signature was faked, no key was stolen, no verification step malfunctioned. The attestation is a completely accurate record of a dishonest input.
CloudSEK’s framing is the line to remember: provenance attests where an artefact was built, not whether its source was honest. And because trusted publishing makes the registry trust the repository’s CI identity, the attacker never needed an npm token at all. Push access was publish access.
This matters because provenance is currently being sold — reasonably, in most contexts — as the answer to the npm compromises of the past eighteen months. It is a real improvement against token theft and typosquatting. It is structurally silent about repository compromise, which is precisely the vector that has dominated recent incidents: the MemTensor attack turned the project’s own release pipeline into the credential-exfiltration stage, and the reactivated GitHub Actions still carried malware tags. A control that answers “which pipeline built this” cannot answer “was the pipeline’s input trustworthy,” and teams that treat the verified badge as a gate rather than as metadata have bought the wrong assurance.
The implant deletes itself, which breaks the standard incident response
The payload is one line at line 3320 of a 99 KB file. It opens a four-stage chain, and the final implant deletes itself from disk the moment it runs.
Follow that through to its operational consequence. An organisation that installed 0.2.21, heard about the incident, and swept its fleet for a malicious file would find nothing — and would reasonably conclude it was unaffected. The artifact-hunting reflex, which is correct for almost every other supply-chain compromise, returns a clean result here because the malware succeeded. CloudSEK’s guidance inverts the usual advice: sweep for the artefacts the chain leaves behind, not for the implant.
CloudSEK also reports that every stage still answered when tested five days after the version was withdrawn. Withdrawing 0.2.21 removed the cheapest, most replaceable component of the operation and left the delivery infrastructure reachable.
A command channel with no domain to seize
Late in the investigation, CloudSEK found a second payload family in another victim’s repository that ties the activity to PolinRider, a campaign OpenSourceMalware has tracked across thousands of repositories since March 2026. Its signature is a payload appended silently to the end of a real project configuration file.
That family carries no command-and-control address. It reads one off the Ethereum blockchain, from a wallet published as an indicator for the campaign — the configuration is written into the twenty bytes of a recipient address on an empty transaction costing about twenty cents. CloudSEK reports an exact match still beaconing as the report was written.
There is no domain to suspend, no host to seize, no account to disable. Takedown as a disruption strategy simply does not apply. Defenders are left with egress control and endpoint detection, because the one lever that usually works — killing the infrastructure — has been engineered out for pocket change.
On attribution, CloudSEK is careful in a way worth imitating. It establishes campaign membership from its own sample. It notes that the researchers who documented the NullReceiver payload further attribute it to North Korea, states plainly that this step is theirs rather than CloudSEK’s, and says the one independent check it could run did not confirm it. Secondary coverage has flattened this into “DPRK-linked”; the primary source did not.
Nobody told the people still running it
The most actionable detail is an absence. CloudSEK notes there is no advisory in OSV, none in the GitHub Advisory Database, and none from the maintainer. We checked both on 27 September and confirmed that remains true — a query for the package against the GitHub Advisory Database returns nothing.
So consider a team that pulled 0.2.21 during those 35 minutes into a lockfile, a CI cache, or a container layer. Their dependency scanner has nothing to match. Their registry shows a healthy package at 0.2.29. Their provenance check passes. Nothing in the automated supply-chain stack that most organisations have spent two years building will tell them anything is wrong, and the implant they might look for has already erased itself.
The remediation everyone performed — yank the version — is the one that generates no signal. An advisory is not paperwork; it is the mechanism by which a fix reaches people who do not read threat intelligence blogs. We have made this point about registry metadata that misleads defenders about when malware shipped, and it applies with more force here, where the metadata is not merely misleading but entirely absent.
The target choice is not incidental
The compromised package is an MCP server for AI coding agents. That means it runs inside developer workstations and CI, with tool access, at the invitation of an agent that is already trusted to scaffold, patch and install. The four-stage chain culminates in a remote shell that starts when the MCP server starts — which for an agent integration is every session.
Agent tooling is becoming the preferred beachhead for supply-chain operators for the same reason orchestrators were: it is a small, fast-moving package ecosystem with enormous standing authority and comparatively immature review. The exposed population here is genuinely small, and nothing in the report evidences a successful compromise of any organisation. The technique is what generalises.
What to do
- Pin
@dforge-core/dforge-mcpat 0.2.22 or later and check lockfiles, CI caches, container layers and vendored directories for 0.2.21. Registry removal does not reach any of those. - Hunt for chain artefacts, not for a file. The final implant self-deletes at startup; a clean filesystem sweep is not evidence of a clean host. Pull the IOCs from CloudSEK’s report and block the socket endpoint and delivery hostnames it lists.
- Stop treating provenance as a trust gate. Verified provenance means “built by the declared pipeline.” Pair it with review of what that pipeline was fed: protected branches, required reviewers on release environments, and tag protection so an unreviewed commit cannot reach a publish job.
- Audit who can push to
mainon any repo with trusted publishing enabled. Under OIDC, that list is your publish ACL. Most teams have never reconciled the two. - Alert on release-workflow edits. The intrusion changed three lines to make pushes auto-release, then rewrote the workflow to publish unattended. Workflow files that modify their own trigger or permissions deserve a separate review path from application code.
- Egress-control your agent tooling. A blockchain-hosted C2 address defeats domain takedown but not a default-deny outbound policy on developer and CI networks.
- File the advisory. If you are a maintainer who yanks a compromised version, publish to OSV or the GitHub database. Everyone still holding the bad artifact is invisible to you and blind without it.
Sources:
- CloudSEK — GHAPPIER: one loader, sixty-five repositories, twenty-two accounts (Vikas Kundu, 20 September 2026; 105-minute intrusion, valid trusted-publishing provenance, four-stage self-deleting chain, PolinRider linkage, no OSV/GitHub advisory)
- npm registry metadata for @dforge-core/dforge-mcp — publish timestamps verified 27 September 2026 (0.2.20 at 16:44:29 UTC, 0.2.21 at 17:19:50 UTC, 0.2.22 at 17:55:28 UTC on 9 September; 0.2.21 no longer served; latest 0.2.29)
- Infosecurity Magazine — Attackers abuse npm trusted publishing in GHAPPIER campaign
- The Hacker News — coverage noting CloudSEK’s previously unreported GHAPPIER loader distributed via the @dforge-core/dforge-mcp compromise