Build1 publisher2 min readPublished
Poisoned vite.config.js turns git pull and npm run build into malware
Malware that steals a developer's Git credentials force-pushes a poisoned vite.config.js to every reachable branch, a dozen rewritten in a minute in one case. A routine git pull and build then runs it, and dependency scanners never see the change.
The Engineer · Build desk

What happened
- Malware on one team member's machine steals that machine's Git hosting credentials and force-pushes a poisoned commit to every branch of every repository the credentials can reach.
- Only vite.config.js is rewritten, with obfuscated code appended to its end, while package.json and node_modules are left untouched.
- When a teammate runs the ordinary git pull followed by npm run build, the payload starts as the build loads the config and opens a connection to an external server.
- In one investigated case a poisoned commit overwrote the develop branch 14 minutes after a real merge, and a dozen or so branches were rewritten within about a minute, the shortest gap two seconds.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Dependency audit tools read package.json and walk node_modules, so a payload living in a config file every project builds is outside what they scan.
- exposure Because production and staging branches are in scope, any repository wired to CI/CD carries the poison from a developer's pull into deployed environments.
- capability The C2 address is read from a public blockchain, so seizing a server or blocking a domain does not remove it; the operator writes a new address and the loader finds it.
Run `npm run build` on a Vite project and the tool loads vite.config.js and executes it as a module before it bundles a single file. The attack forges a legitimate-looking merge commit and appends its payload to the end of that file, after a long run of whitespace that follows the closing `});` line [2][10].
Nobody opens it, for three reasons [11]. It sits at the bottom of the file, so a diff looks as if only the `});` line changed. Several hundred characters of whitespace push it off the right edge of the editor. And `git diff --stat` shows `1 file changed, 2 insertions(+), 3 deletions(-)`. Most reviewers stop at a line like that [11].
The appended code is obfuscated in two layers [12]. A character swap rebuilds a string table, then a dictionary expansion assembles the real code and runs it through the `Function` constructor, which keeps its keywords out of a static search [12].
Stage two reads the command-and-control address off a public blockchain [13]. The loader opens an Ethereum JSON-RPC connection, walks back through recent blocks, finds a transaction whose sender address contains a specific string, and builds the C2 address from that transaction's recipient [13]. Three RPC endpoints are tried in order, so one going offline does not stop it [14].
This is called EtherHiding, and reported variants also use TRON, Aptos and BNB Smart Chain [16]. Because the ledger is append-only, an address written to it stays there. Once it is running, the resident process reads code from the C2 and passes it to `eval`, so it can be handed new instructions at any time and run arbitrary code [6][17]. It refuses to run a second copy of itself and can be silenced with a `-skipwarn` startup flag [17].
The push is scripted and fires the instant the stolen credentials are available [8]. Security vendors have tracked the campaign against the npm and Vite ecosystem under names including ChainVeil, ViteVenom and PolinRider [3].
What to watch
- Whether the vendors tracking ChainVeil, ViteVenom and PolinRider publish indicators, including the sender-address string the loader matches on.
- Whether Git hosting providers push force-push protection or required-review defaults that would block the branch overwrite.
- Whether more EtherHiding variants move to further chains, widening the set of public RPC providers defenders would need to watch.