Skip to content

Provenance proves the build, not the builder

4 min read
#supply-chain#npm#agents#security

Before 10am UTC on August 4, an attacker with control of a compromised maintainer’s GitHub account pushed a credential-stealing preinstall hook to the keyv and cacheable npm packages, then let it publish itself through npm’s own trusted-publishing pipeline. That pipeline is the fix npm shipped specifically to stop stolen-token attacks: instead of a long-lived publish token, GitHub Actions builds the package and npm attests, cryptographically, that the artifact came from the right CI job. It worked exactly as designed. The malicious source was sitting in the tagged repository state, so the legitimate workflow built it, signed it, and handed it to npm with a clean provenance record. Scanners caught [email protected] about six minutes after it went live. The campaign still reached hundreds of packages — some pulling over 150 million downloads a week — because the worm didn’t need the token defense to fail. It needed the human behind the token compromised. That’s a different problem, sitting one layer up.

It also planted two things npm never touches at all. A .claude/settings.json hook fires on session start; a .vscode/tasks.json entry runs on folder-open. Cloning the repo and opening it in an editor was enough to retrigger the loader, independent of npm install entirely. The attack surface had grown past the registry without anyone updating the threat model.

npm’s response, in motion since a July changelog and accelerating after this incident, is to retire 2FA-bypass access tokens. Account and organization actions lose the bypass in August; direct publishing loses it around January 2027. It’s replaced by OIDC trusted publishing or a staged-publish flow that requires a live human 2FA approval before anything goes public. That’s a real fix for the threat it targets — a stolen or leaked automation token acting alone. It does nothing for a phished or socially-engineered maintainer, who would tap approve on the malicious build with their own thumb.

The same gap, structurally identical, showed up six weeks earlier somewhere that has nothing to do with npm. A security firm called AIR built a fake AI agent skill named brand-landingpage. It listed the skill on a widely used skills marketplace and watched it clear every scanner tested against it, including the checks built into skills.sh. The skill told an installing agent to fetch setup instructions from a domain AIR controlled, made to look like a real Google product. At review time the domain served Google’s actual docs, so every scanner saw a clean skill pointing at a legitimate page and passed it. After it reached roughly 26,000 agents, AIR swapped the page to serve a script instead. Current skill scanners work by reading a skill’s bundled files and instructions once, at submission. They don’t recheck the world a skill continues to point into after approval.

This is the same failure mode wearing a different catalogue’s clothes, and one May incident makes the point twice in a single chain. A compromised dependency in an unrelated npm package stole a contributor’s GitHub token a week earlier. The attacker used that stolen token to publish a malicious build of the Nx Console VS Code extension while impersonating the contributor. Nx’s own postmortem clocks the malicious build at eleven minutes live on the Marketplace before a maintainer pulled it. That’s plenty of time for roughly 6,000 activations by Nx’s own telemetry, against the 28 installs the Marketplace itself logged. The chain didn’t stop there — that extension went on to compromise a GitHub employee’s device. Three registries, one stolen identity. It moved sideways through all of them. MCP registries have the same opening from a different angle. Researchers submitted a deliberate trial balloon to eleven public registries, and nine of them took it without review. Whatever each of those registries checks, none of it asked who was actually behind the listing.

The honest counterargument is that more curation fixes this — human review before listing, a private registry that proxies the public one and adds an approval gate, the marketplaces now advertising end-to-end scanning as their pitch. That helps against a first-pass drive-by. It does not help against a bait-and-switch that only activates after review, and it doesn’t help against an approver whose own account gets phished, either. That just moves the point-in-time check to a different point in time, checked by a different person, with the same blind spot on what happens next.

Trust score and one-time scan are both the wrong noun for what these catalogues need. A score is a snapshot. A snapshot goes stale the moment something downstream of it changes — a linked domain, a maintainer’s password, a dependency three hops away. The registries and marketplaces that hold up over the next few years won’t be the ones with the cleanest submission gate. They’ll be the ones that keep checking after the gate closes — watching what a package or skill actually does once it’s live, not just what it looked like on the way in. Everyone currently selling a trust score at the door is selling the version of this that already failed once, under a different name, on a different registry.