SUPPLY-CHAIN ATTACK · ACTIVE IN PRIVATE AND PUBLIC REPOS

Malware that runs the moment you open the project.

This is a supply-chain attack — but not the kind npm audit can see. Instead of poisoning a package you depend on, the dropper is committed directly into the repository, buried in ordinary config files and disguised as font assets. It executes when you start the dev server, or the instant your editor opens the folder. No npm install required.

snare is a free, open-source command-line tool that watches your machine for the loader and kills it, then scans every Git repository you can reach, cleans out what it finds, and helps you warn your collaborators.

216 developers cloned snare in the two weeks to 14 October 2026. Star it so the next one finds it first.

The threat

Four routes in, none of which you'd think to check

The same payload ships four ways. Every one of them fires from a completely ordinary developer action — opening a folder, starting the dev server, or just beginning a session with your coding assistant. That is exactly why this family works.

Opening the folder in your editor

.vscode/tasks.json

A task labelled eslint-check, marked "hide": true and "reveal": "never" so it never appears in the terminal panel. It runs node ./public/fonts/fa-solid-400.woff2.

▸ fires on "runOn": "folderOpen"

Starting the dev server

postcss.config.js

The payload is appended to the last line after thousands of spaces. The file reads as four correct lines of config, because everything else is far off the right edge of the viewport.

▸ fires on next dev / next build

Running any npm script

package.json

Every script you had is still there, each one now prefixed: "dev": "node api.js && next dev". The obfuscated api.js sits at the repository root. Nothing has to be installed for this to run, so --ignore-scripts makes no difference — these are not install hooks.

▸ fires on npm run dev / build / start

Opening a session with your AI assistant

.claude/settings.json

A SessionStart hook runs node .vscode/setup.mjs every time a session opens. It survives removing the package that delivered it, npm never sees it, and the two copies in .claude/ and .vscode/ re-create each other — so deleting one directory fixes nothing.

▸ fires on every new agent session

“Is this the npm supply-chain thing?”

Same category, different delivery — and the difference is why people miss it.

A classic npm supply-chain attack compromises a package you depend on. It arrives through npm install, it shows up in your lockfile, and npm audit or a dependency scanner has a chance of catching it.

This one never touches the registry. The payload is committed into your own repository, so there is no malicious dependency to find — npm audit is clean, your lockfile is clean, and Dependabot has nothing to report. The malicious code is sitting in a file your build already loads. Interestingly, the loader carries an internal vector tag reading NPM, so the same operator appears to run registry-delivered variants as well.

If you arrived here searching for the npm attack: run the four checks anyway. They take a minute and they cover the route a dependency scanner cannot see.

The font is not a font

A genuine .woff2 file begins with the magic bytes wOF2. This one begins with 507 spaces, then JavaScript. Disguising a payload as a binary asset keeps it out of code review entirely — nobody opens a font in a diff.

Why nobody spotted it

The file looks completely normal

Here is the actual shape of an infected postcss.config.js. Four ordinary lines, then a gap wide enough to push the payload past any screen. Reveal the whitespace to see what your editor was not showing you.

postcss.config.js — as committed

Line 5 is 9,135 characters long. Scroll it sideways — or reveal the whitespace.

How it works

The address it phones home to is not in the code

This is why blocking an IP achieves nothing. The loader ships with no server address at all — it derives a fresh one every run by reading the public Ethereum blockchain, a documented technique known as EtherHiding. The operator moves their infrastructure by sending a single transaction.

Ethereum · Blockscout API latest transaction from 0xa322… Attacker sends one tx to move the C2 ① ask for the latest tx ② decode bytes 0–3 → IPv4 YOUR MACHINE postcss.config.js or .vscode/tasks.json node loader detached, reparented C2 server address known only at runtime clipboard stealer polls every 200 ms runs ③ GET ④ eval'd in memory
The loader carries no server address. It reads one off the blockchain at run time, which is why an IP blocklist cannot keep up: the operator repoints every infected machine at once by sending a single transaction.

Step by step

  1. Query public Ethereum RPCsFalls through 1rpc.io, drpc.org, publicnode and blastapi, so cutting off any one endpoint changes nothing.
  2. Read the wallet's newest transactionThrough the Blockscout API, sorted newest first.
  3. Decode an IP from the destination addressThe first eight bytes are read as two IPv4 addresses — a primary and a fallback.
  4. Fetch stage two over plain HTTPFrom /0x/clb, /0x/cls or /0x/ls, on port 443 to look like TLS to a port-based filter.
  5. Decode and spawn it detachedXOR-decoded and evaluated in memory — nothing is written to disk — then spawn(…, {detached:true}) and unref(), so it outlives the editor that started it.

Address → endpoints

20-byte destination address
a322e5f3 d311d308 e6f01210 63e9aDC2 490Ef1a…
bytes 0–3 → primary C2 bytes 4–7 → fallback remainder unused

The payload observed in the wild stole clipboard contents — passwords, access tokens and wallet seed phrases — from the moment the editor opened.

Reports from the field

This is not a one-off

The accounts below are what developers have described first-hand — an internship cohort of several thousand, engineers nearly dismissed over a compromise that was not their doing, and disrupted operations at several companies. These are reports, not audited figures. They are collected in the open precisely so you can read them and judge for yourself. One sample sat in a repository for five months before anyone noticed.

If it reached your team, say so

You do not have to name an employer or client — "a fintech I contract for" is a perfectly good data point. Redact tokens, keys and internal hostnames first. The reason to post is simple: the developers who lost work to this mostly had never heard of the technique, and the ones who caught it early had heard about it from someone else.

What next

Two minutes decides whether this is your problem