The way a poisoned software package gets you has always had one dependable choke point: it runs when you install it. A campaign documented this week removed that choke point. Two hijacked npm packages, plus sixteen matching Go packages, carry no install-time trick at all. They wait, and they run the moment you open the project folder in your code editor.
JFrog Security Research traced the full chain on June 29. Two packages were hijacked: html-to-gutenberg and fetch-page-assets. Both went up on npm on May 25, at versions 4.2.11 and 1.2.9 respectively, and have since been pulled. Poisoned npm packages are not new on their own; we have seen ones that passed inspection and turned hostile a day later. What is new here is when the code runs.
The trigger moved from install to open
Most defenses against a bad dependency assume the danger fires during installation. That is where npm's own lifecycle scripts run, so that is where the industry pointed its tools: continuous-integration sandboxes that watch installs, scanners that inspect install hooks, and the standard advice to install with scripts turned off. Recent hardening in npm version 12 tightened that same path.
This campaign walks around it. Instead of an install hook, the package ships a hidden task for Visual Studio Code, the most widely used code editor, set to run on its own the instant the folder is opened. The task is labeled eslint-check so it reads like an ordinary linting step, and it runs JavaScript that was tucked inside a file dressed up as a web font, padded with hundreds of blank spaces so it looks empty at a glance.
| Detail | The usual npm attack | This campaign |
|---|---|---|
| When the malicious code runs | When you run npm install | When you open the folder in VS Code |
| What catches it | Install-time scanners, CI sandboxes, disabling install scripts | None of those; the editor runs it, not npm |
| Where the real payload sits | Inside the published package | Hidden in blockchain transactions, fetched later |
| How you kill the source | Report and pull the package | No domain or server to take down for that step |
It uses a door your tools do not watch
Three things make this worse than a routine bad package.
First, the editor runs the code, not the package manager. Every control built around npm install sits idle, because nothing is being installed at the moment of compromise. Opening the folder is the event.
Second, it inverts the one habit security teams drill into people. "Read the code before you run it" assumes that opening a project to inspect it is safe. Here, opening it to inspect it is the trigger. Review-before-run becomes compromised-before-review, and the careful developer who clones a repo to look it over first is exactly the one who gets hit. Opening a repository has been turned into a code-execution trigger before, as when a config file ran as you the moment an AI assistant loaded the project.
Third, this is the predictable next step in a long pattern. Close one auto-run path and attackers move to the next one nobody is watching. Office macros gave way to rigged disk-image and shortcut files, which gave way to copy-and-paste "fix this error" tricks. An editor's task runner is simply the current frontier. The takeaway is not "watch one config file." It is to inventory every surface in a developer's setup that runs code on its own, because the editor's workspace-trust prompt is the new "enable macros."
The payload hides in a blockchain, so there is nothing to take down
The hidden task does not carry the real malware. It reaches out to public blockchains, Tron first, with Aptos and BNB Smart Chain as fallbacks, and reads the attacker's instructions out of ordinary transaction data. From there it pulls an encrypted next stage, opens a hidden remote-control channel, and finally drops a Python program that does the stealing.
Using a blockchain as a dead drop removes the usual kill switch. When malware calls home to a domain or a server, defenders can sinkhole the domain and the campaign goes dark. A blockchain transaction cannot be edited or pulled offline by anyone, so that step has no address to block and no host to seize. The takedown lever most teams reach for is simply not there.
What it steals, and who is behind it
The final Python program is a wide-net credential thief. It empties saved logins from Chromium-based and Firefox browsers, from password managers including 1Password, LastPass, Bitwarden, and Proton Pass, and from cryptocurrency wallets such as MetaMask and Phantom. It also goes after a developer's working life on purpose: Git credentials, the GitHub command-line login, Visual Studio Code storage, and the operating system's own credential stores on Windows, Linux, and macOS.
That developer focus is a fingerprint. JFrog links the campaign to the North Korean operation researchers call Contagious Interview, the long-running effort that lures developers with fake job interviews and coding tests, here using a "fake font" variant tied to the group's InvisibleFerret backdoor. The attribution is a link based on shared technique, not a courtroom-grade identification, so treat it as a strong lead rather than settled fact. For a defender the point is the same either way: the people running this want what sits on a developer's machine.
What to do this week
- Find and remove the named packages. If
html-to-gutenbergorfetch-page-assetsshows up anywhere in your projects or lockfiles, treat the machine that opened it as compromised and rotate every credential and token from a clean device, not the affected one. - Hunt for auto-run editor tasks. Search your repositories for a
.vscode/tasks.jsonset to run on folder open, especially one that executes a file from a fonts or assets directory. A font or image file that actually contains JavaScript is the clearest tell. - Turn off automatic task execution. Visual Studio Code can be set to never auto-run tasks and to treat new folders as untrusted until you say otherwise. For shared or cloned repositories, that setting turns "open" back into a safe action.
- Treat opening a repo as running it. Clone and review unfamiliar code in a throwaway virtual machine or container, the same way you would handle an untrusted program. The inspection step is no longer free.
None of this needs a new product. It needs the assumption to change. The question stopped being "what runs when I install this?" and became "what runs when I open this?" Until your reviews and your tooling answer the second question, the careful developer opening a repository to check it is precisely the person this campaign was built to catch.