Sigstore signs the build. Who signed the decision to publish?
The software supply chain has excellent machine provenance and almost no human provenance. Sigstore and SLSA prove which pipeline built an artifact from which commit, which is real and valuable. Neither answers the question a self replicating worm exploits: did a person decide to release this version?
The email arrives on a Tuesday and it is not exciting, which is the point. It says your npm two factor enrollment needs to be renewed, and it comes from a domain that is one character away from the real one, and the login page it takes you to is a perfect copy because it is a perfect copy: a reverse proxy sitting in front of the genuine site, relaying every byte in both directions.
You type your password. You approve the two factor prompt. The real site authenticates you, correctly, because you really did authenticate. The proxy in the middle keeps the session token that comes back.
You maintain four packages. Nothing famous. A logging helper, a small date utility, a wrapper around an API you needed once, and a colour formatting library you wrote in 2019 that somehow ended up in the dependency tree of half the ecosystem. Roughly nine million weekly downloads between them, which is a number you stopped thinking about years ago.
Within the hour, malicious versions of all four are published. You did not publish them. Nobody published them, in the sense of a person deciding. A script held a valid token, and the registry does what it does when a valid token arrives: it accepts the upload and starts serving it.
Then it gets worse, in a way that supply chain attacks had not previously managed. The published code runs on install, harvests the developer credentials, cloud keys, and continuous integration secrets of everyone who installs it, and uses those stolen credentials to publish again. From other people's accounts. To other people's packages. Nobody has to phish anybody a second time. The thing propagates on its own.
What actually happened with the Shai-Hulud worm?
In September 2025 the npm ecosystem met the first widely documented self replicating supply chain worm. Security teams at Kaspersky, Elastic, Sygnia, Checkmarx and ReversingLabs published analyses through late 2025 and into 2026, and while the details differ at the edges, the shape they describe is consistent.
Hop one: a credential, not a vulnerability
The entry point was a compromised maintainer credential. Not a flaw in npm. Not a bug in a package. A person's account, obtained the way accounts are usually obtained now, which is by relaying a real login through a proxy and keeping the session that comes out the far end.
This matters for the whole argument, so it is worth sitting with. There was no software vulnerability to patch at hop one. The registry behaved exactly as designed. The token was real, the account was real, the upload was well formed. Every technical control in the path returned true.
Hop two: the payload that recruits
What made this different from every previous npm incident was what the malicious versions did once installed. Rather than simply stealing from the end user, the payload went looking for the things that let it publish: npm tokens, GitHub credentials, cloud provider keys, secrets sitting in continuous integration environments. Analyses describe it using workflow automation, including GitHub Actions, to spread into repositories that consumed an infected package.
Collect enough publishing credentials and you no longer need a phishing campaign. Each compromised developer machine or pipeline becomes a launch point for the next round of publishes, from accounts that have never been phished, to packages the original attacker never targeted.
That is the difference between an attack and a worm, and it is why the counts climbed the way they did. Published analyses describe hundreds of affected packages across the initial waves, and later variants in 2026, reported by Elastic Security Labs among others, reached co owned packages whose combined download volume was described as exceeding a billion installs per month.
The context it landed in
It did not arrive into a calm period. On 8 September 2025, a separate compromise hit eighteen widely used packages including debug and chalk, with a payload aimed at cryptocurrency activity in the browser. Two utility packages that most developers could not name the maintainer of, sitting underneath an enormous fraction of the JavaScript ecosystem.
Set alongside the earlier canonical incidents, a pattern emerges. In 2018 the event-stream package was compromised after a maintainer handed it to a volunteer. In 2021 ua-parser-js was hijacked through a maintainer account. In 2024 the xz backdoor was inserted by a contributor who spent years earning commit rights. Different techniques, one constant: the attacker acquires the right to publish, and then publishes.
Why did none of the existing controls stop it?
This is where the story becomes uncomfortable, because the ecosystem is not unprotected. It has spent years building genuinely good machinery. Let us take each piece seriously and ask exactly what it proves.
Registry two factor authentication
Two factor authentication protects a login event. Adversary in the middle phishing kits do not attack the second factor, they let you complete it and steal the session that results. The industry has watched this at scale: the disruption of the Tycoon 2FA phishing service announced in March 2026 by Microsoft, Europol and partners involved a platform reported to have affected more than ninety six thousand victims across hundreds of seized domains. We wrote about the mechanism in detail in your MFA worked perfectly, the attacker was already inside the session.
So two factor authentication answers "did someone complete a login challenge for this account". It does not answer "is the entity now holding this session the human who completed it".
Trusted publishing from continuous integration
Trusted publishing is a real improvement. Instead of a long lived token sitting in a repository secret waiting to be stolen, the continuous integration job proves its identity to the registry using short lived OpenID Connect credentials, and the registry issues publish rights scoped to that job.
It removes an entire class of static credential theft, and it should be adopted broadly. It also relocates the question rather than answering it. Trusted publishing proves that a particular workflow, in a particular repository, published this version. A worm that has compromised the repository or the workflow is a particular workflow in a particular repository. It publishes with impeccable credentials.
Provenance attestations
Sigstore, SLSA and npm provenance are the strongest work in this area and they deserve credit rather than a strawman. Sigstore solved a problem that had blocked artifact signing for a decade: key management. Nobody wanted to hold long lived signing keys, so almost nobody signed anything. Sigstore replaced held keys with ephemeral certificates bound to an identity from an OpenID Connect provider, and recorded the whole thing in a public transparency log so that a signature cannot be issued in secret.
What you get from that is a verifiable statement of the form: this artifact, with this digest, was built by this workflow, from this repository, at this commit. That is a genuine and substantial claim. It kills a large family of attacks where somebody hands you a binary and asserts it came from a repository you trust.
Now read the claim again and notice which word is missing. Every noun in it is a machine. A workflow. A repository. A commit. A digest. The claim is complete and accurate and it never mentions a person.
A worm that controls the repository produces provenance that verifies. Not forged provenance, real provenance, honestly describing a build that genuinely happened in the pipeline the attacker now controls. Provenance is not being defeated here. It is answering the question it was designed to answer, correctly, and that question is not the one under attack.
Software composition analysis
Scanners find known bad packages after somebody has identified them as bad. The window between publication and detection is when the installs happen, and for a fast moving worm that window is where the entire event takes place.
| Control | What it proves | What it cannot prove |
|---|---|---|
| Registry password | Someone knew a secret | Who they were |
| Two factor authentication | Someone completed a login challenge | That the session holder is still that person |
| Trusted publishing (OIDC) | This workflow, in this repository, published it | That a human wanted this version released |
| Sigstore and npm provenance | This artifact was built by this workflow from this commit | That a human decided to publish the result |
| Transparency log | The signature was issued publicly and cannot be hidden | That the signing was intended |
| Composition analysis | This version matches a known bad signature | Anything at all before somebody notices |
| Human signed release | An enrolled maintainer approved this exact version and digest | That the code is good, or that the maintainer read it |
The factory with immaculate records and no one in charge
Imagine a factory with the best logistics operation you have ever seen. Every item that leaves has a tamper evident label recording which production line built it, from which batch of raw material, at what time, under which quality process. The records are public, append only, and cryptographically verifiable. You can take any item off any shelf anywhere in the world and trace it back to the exact machine that made it.
Now ask the factory a simple question: who decided to ship this?
And the answer is that nobody knows, because that was never recorded, because the shipping decision was made by whoever was holding the door badge at the time, and the badge does not have a face on it.
That is the software supply chain in 2026. We have superb provenance for the manufacturing and no provenance at all for the decision. And decisions are what attackers actually want. They do not want to build your package. They want your package released with their code inside it, and every existing control is happy to help them do it as long as they are holding a badge.
This is the gap we call Session-Inherited Authorization, and the release pipeline is where it costs the most, because a single unauthorized decision propagates to every machine that runs an install.
What would a human signed publish actually look like?
The release of a new version becomes an action that requires a signature from the maintainer's enrolled device, over the content of that specific release. Not a login. Not a session. A signature over the thing being published.
The payload is small and boring, which is what you want in something that has to be identical on both sides of a verification:
{
"action": "package.publish",
"registry": "registry.npmjs.org",
"package": "colour-format",
"version": "4.2.1",
"tarball_digest": "sha512-9f2c4a...e18b",
"maintainer": "npm:aparna",
"issued_at": "2026-09-05T09:14:07Z",
"nonce": "7d41e0c2ab93"
}
Those bytes are canonicalised, hashed, and the hash becomes the challenge for a signature on a device the pipeline does not control. The maintainer sees the package name, the version, and the digest rendered on their own phone, confirms that yes, they meant to cut 4.2.1 right now, and approves. The resulting receipt travels with the package.
Verification is the part that has to be dull:
digest = sha512(tarball_bytes)
hash = sha256(canonical_json(payload))
assert payload.tarball_digest == digest # this exact artifact
assert receipt.challenge == hash # signed THIS release
assert verify_ed25519(receipt.sig,
receipt.challenge,
published_key(maintainer))
assert receipt.version == manifest.version # no substitution
accept(package)
Two properties of that verification matter. First, it composes with provenance rather than replacing it: you check the Sigstore attestation to learn which pipeline built the artifact, and you check the release receipt to learn which human released it. Two different claims, both useful, neither redundant.
Second, the check needs no network call. The receipt verifies against a published key, so a consumer, an internal scanner, or an auditor two years from now can establish that a human approved version 4.2.1 without asking the registry, without asking us, and without either of us still being around. That is the same property we describe for records generally in what is an attestation chain.
Now replay the worm against it. The stolen session still holds a valid token. The pipeline still builds. The provenance still verifies. And the publish is refused, because there is no receipt, because producing one requires a device in a maintainer's pocket that the worm has never touched. The worm cannot complete hop one, so there is no hop two, so there is no self replication.
Nobody is going to sign four hundred releases a week
Good. They should not.
This is the objection that kills naive versions of this proposal, and it deserves a real answer rather than an appeal to discipline. Modern release practice is automated on purpose. Dependency bumps cut patch releases. Merge queues trigger builds. Some healthy projects publish several times a day, and asking a volunteer maintainer to approve each one on their phone is how you get the control disabled in a fortnight, or worse, delegated to a bot, which returns you precisely to where you started.
The answer is to gate by blast radius rather than by event. The question is not "was this a release" but "how many machines does this reach if it is wrong".
| Release class | Example | Sensible policy |
|---|---|---|
| Very high reach | Package with millions of weekly downloads, or thousands of dependents | Human signature on every release, no exceptions |
| High reach, routine change | Popular package, automated patch or dependency bump | Pre signed delegation with narrow scope: patch versions only, expiring, revocable. Anything else escalates |
| Major or breaking change | Any package, major version or new maintainer | Human signature, always |
| Low reach | Internal or low dependent package | Provenance alone is proportionate |
| First release from a new device or account | Any | Human signature with liveness, because this is the takeover signal |
The delegation in row two is not a loophole, it is the same primitive with an explicit scope. A maintainer signs an object that says: this pipeline may publish patch versions of this package for the next thirty days, and nothing else. It carries fields for the permitted actions, the constraints, a validity window, a limit on further delegation, and an identifier that revokes it. That is a statement a consumer can verify and a maintainer can withdraw, which is categorically different from a token that simply exists until someone notices it should not.
An organisation that wants this today does not have to wait for a public registry. The place to start is your own private registry, where you set policy, and the same idea appears at the commit level in my agent shipped this. The signed code lab demonstrates the flow, and the integration docs cover the signing and verification calls.
What this does not fix
Several things, and pretending otherwise would be its own kind of supply chain problem.
- It does not make the code good. A signature proves a specific human approved the release of a specific artifact. It proves nothing about whether the code is correct, safe, or even read. A maintainer who approves a compromised build they never inspected has produced a perfectly valid receipt for a malicious package. What you get is accountability and a break in worm propagation, not code review.
- A compromised maintainer device defeats it. Moving the signature to a separate enrolled device means an attacker now needs the account and the phone. That is a large increase in cost. It is not infinity. Targeted attackers with device access remain a real threat, and the honest description of this control is that it removes the cheap, scalable path rather than every path.
- Manav has not shipped registry integrations. There is no plugin you can install into npm today. The signing, the receipts, and the offline verification exist and are in use elsewhere. Public registry adoption is a proposal, and the practical near term deployment is private registries that enterprises already control, plus a proposed extension so that a human signature can ride alongside a Sigstore attestation rather than competing with it.
- Maintainers do not owe you this. Open source maintenance is mostly unpaid, and the ecosystem has an established habit of responding to incidents by adding obligations to volunteers. Any proposal that makes maintaining a package more tiring will be rejected, and it should be. This only works if it is free for maintainers, close to instant, and demanded first of the organisations that depend on the packages rather than of the people who wrote them.
- Agents complicate the picture. Coding agents already open pull requests, and the natural next step is agents cutting releases. That is not automatically wrong, but it needs the release to carry a chain back to the human who delegated the authority, with scope and an expiry, rather than an agent holding a token indistinguishable from a person's.
What to do this week
- Count the packages your organisation installs that have exactly one maintainer with publish rights. That number is your real exposure, and almost nobody has it written down.
- Turn on trusted publishing wherever you publish. It is a genuine improvement and it costs an afternoon.
- Verify provenance in continuous integration rather than merely producing it. A large share of published attestations are never checked by anyone, which makes them decoration.
- Write your policy for what happens when a dependency publishes without provenance. Deciding during an incident is how organisations end up shipping the malicious version because the pipeline was red and the release was late.
- For internal packages in your private registry, require a human signature on any release of anything more than twenty other teams consume. Start with one package and measure the friction honestly.
- Rehearse credential rotation across npm, GitHub, and your cloud provider, timed. The Shai-Hulud payload targeted exactly these, and the remediation cost is dominated by how long rotation takes.
- If you maintain a widely used package, enroll a second device for signing now, so that a lost phone never becomes an argument for turning the control off.
The one sentence version
The supply chain security industry built excellent provenance for machines and quietly left the human out of the record, and a worm is simply the discovery that when nobody has to decide, nobody has to be compromised twice.
Frequently asked questions
How could the Shai-Hulud npm worm have been stopped? By requiring each release to be signed from the maintainer's own enrolled device, over the package name, version, and tarball digest. The worm held stolen tokens and stolen sessions, which is enough to satisfy a registry but not enough to produce a device bound signature. Without a valid release receipt at the first hop, there is no propagation.
Does npm provenance prevent supply chain attacks? It prevents an important class of them. Provenance proves an artifact was built by a specific workflow from a specific commit, which defeats attacks that substitute a binary for one you trust. It does not prove a human decided to publish, so an attacker who controls the repository or the pipeline produces provenance that verifies correctly.
What is trusted publishing? Trusted publishing lets a continuous integration job authenticate to a registry with short lived OpenID Connect credentials instead of a long lived token stored in repository secrets. It removes a large class of static credential theft and is worth adopting. It proves which workflow published, which is a different claim from which human released.
Is this a replacement for Sigstore? No, it composes with it. Sigstore answers which pipeline built this artifact from which commit. A release receipt answers which enrolled human approved publishing it. A consumer can check both offline, and the ideal outcome is a human signature extension that rides alongside a Sigstore attestation rather than a competing format.
Would this force maintainers to sign every release? No, and any proposal that does will be rejected, correctly. The policy should follow blast radius. Very high reach packages and all major or breaking releases get a signature. Routine automated patch releases run under a narrow, expiring, revocable delegation. Low reach packages are fine with provenance alone.
Does a human signed release mean the code is safe? It does not. A signature proves a specific human approved releasing a specific artifact. It says nothing about whether the code was reviewed or is correct. What it provides is accountability and, more importantly here, a break in worm propagation, because stolen credentials alone can no longer publish.
Can I use this today on public npm? Not on public registries. There is no npm plugin for this, and public registry adoption is a proposal rather than a shipped integration. The practical starting point is a private registry your organisation already controls, where you can set the policy for internal packages and measure the friction before advocating upstream.
Sources
- Securelist (Kaspersky), analysis of the Shai-Hulud worm infecting npm packages. securelist.com
- Elastic Security Labs, reporting on the later Shai-Hulud wave through a compromised maintainer and affected package download volumes. elastic.co/security-labs
- Checkmarx research describing Shai-Hulud as a self replicating supply chain attack using GitHub Actions. checkmarx.com/blog
- ReversingLabs and Sygnia analyses of the npm worm and maintainer credential compromise. reversinglabs.com/blog
- Sigstore project documentation on keyless signing and transparency logs. sigstore.dev
- SLSA supply chain integrity framework. slsa.dev
- npm documentation on provenance and trusted publishing. docs.npmjs.com
- Microsoft Security Blog on the disruption of the Tycoon 2FA phishing service, March 2026. microsoft.com/en-us/security/blog
- NIST Secure Software Development Framework, SP 800-218. csrc.nist.gov
Provenance proves the pipeline. The worm has a pipeline. The missing signature is the maintainer's, on this version, from a device the worm never touched.