Skip to content

Back to the catalog

PolinRider Jumps the Fence to Go, Packagist, npm, PyPI
opensourcemalware.comwriteup

PolinRider Jumps the Fence to Go, Packagist, npm, PyPI

OpenSourceMalware documents how PolinRider, a DPRK/Lazarus Group supply-chain campaign tied to the Contagious Interview cluster, spread from GitHub account takeovers into Go modules, Packagist/Composer packages, npm libraries and Chrome extensions without any change to the malware itself. Because Go and Packagist resolve packages directly from git tags rather than a separately-uploaded artifact, a compromised GitHub account automatically becomes a compromised registry release — unlike npm/PyPI, which require a separate publishing credential and largely stalled the takeover at the registry boundary. The post gives ecosystem-by-ecosystem evidence (10 Packagist packages, 80+ Go modules, an NPM/Chrome cluster), IOCs (TRON/Aptos dead-drop addresses, XOR keys, loader signatures, temp_auto_push.bat), a campaign timeline, and remediation guidance for Go/Composer maintainers.

TL;DR DPRK's PolinRider campaign jumped from GitHub account takeovers into Go and Packagist package registries "for free" because those ecosystems treat a git tag as the published artifact, while npm/PyPI's separate publish-credential model stalled the compromise.
Document

What it is

PolinRider, a DPRK-linked supply-chain campaign (Lazarus Group / Contagious Interview cluster) that takes over legitimate GitHub accounts and injects an obfuscated JavaScript loader into repos the maintainer already owns. Originally a GitHub-only problem (675 repos in the first report, then 1,951 repos across 1,047 owners), it has now surfaced in package registries: ~200 malicious release artifacts across 111 packages/extensions — 80+ Go modules, 10 Packagist packages, an NPM cluster, and a couple of Chrome extensions.

How it works — why the jump was "free"

The page's core actionable insight is a distinction between two resolution models:

  • Model A (npm, PyPI): the registry hosts its own artifact (tarball/wheel), uploaded with a separate publishing token. Install pulls the registry's copy, not GitHub. The registry credential is a different key from the GitHub credential.
  • Model B (Go, Packagist): the registry resolves directly from the git repo. Go — a version is just a git tag; go get ...@v1.4.0 makes proxy.golang.org fetch that commit. No publish token exists. Packagist — a GitHub webhook makes Packagist re-read the repo on push; composer require downloads a zipball of the tagged commit straight from GitHub.

In Model B, repository write access is publishing access. The GitHub takeover the campaign already had at scale automatically compromised every backing Go module and Composer package — no stolen npm/PyPI token needed.

flowchart TD
    A[GitHub account takeover + malicious commit] --> B{Resolution model?}
    B -->|Model A: npm, PyPI| C[Needs separate publish token<br/>— NOT auto-compromised]
    B -->|Model B: Go, Packagist| D[Repo IS the artifact<br/>— auto-published on push/tag]

The injection technique (JS-in-PHP)

DPRK uses VS Code to run JavaScript payloads regardless of the host repo's language. The actor force-pushes two new directories: a VS Code config dir (extension.json, launch.json, spellright.dict, tasks.json — payload usually in tasks.json), and a public/fonts folder where one or more fake .woff2 fonts is actually malicious JS. Example: 7span/react-list had the loader appended to vite.config.js via force-push (commit 1c1d61b). Payloads use blockchain dead-drops to fetch the Lazarus stealer toolkit; a Windows script rewrites git history to disguise the commit.

What to do

Check for and purge these 10 confirmed compromised Packagist packages:

thiio/kubernetes-php-sdk
arsl/optima-class
olc/olc-php
sevenspan/laravel-whatsapp
adxio/twig-hmvc
sevenspan/code-generator
lambda-platform/moqup
roberts/leads
sevenspan/laravel-chat
plusinfolab/logstation

Because Go/Packagist pull from tags, treat any Go module or Composer dependency as trusted only as far as its backing GitHub repo — version-pinning to a tag does not protect you if the tag was force-pushed. Look for unexpected .vscode/tasks.json, fake font files under public/fonts, and JS appended to config files.

Caveats

Go module counts (80+) and package names are enumerated only for Packagist; the specific NPM libraries and Chrome extensions are referenced but not named in the fetched text. The page cuts off mid-walkthrough ("If you look at line 16").

Technologies named here

Techniques named here

  • GitHub account takeover for supply-chain compromise
  • Obfuscated JavaScript appended to config files
  • VS Code tasks.json trigger
  • Fake .woff2 font files hiding JavaScript
  • Malicious npm dependency postinstall hooks
  • Git history rewriting / anti-dated force push
  • Blockchain dead drop C2
  • Domain-takeover-based account recovery abuse