Skip to main content
Guides·Published onJun 2, 2026

How Vendure keeps Shai-Hulud out of your stack

MB
Michael BromleyCTO & Co-Founder
Shai-Hulud showed how fast a single stolen npm token can poison thousands of production stores. Here is how the Vendure publishing pipeline is built so that no single failure can ship a malicious release of @vendure/* to your stack.
How Vendure keeps Shai-Hulud out of your stack

In September 2025, an npm worm called Shai-Hulud tore through the JavaScript ecosystem. It surfaced inside a compromised package, harvested every credential it could reach (npm tokens, cloud keys, CI secrets), and used those credentials to publish trojanized versions of still more packages. Each new victim became a launch point for the next. There was no human attacker pushing it forward, just stolen tokens publishing on autopilot.

That is the nightmare scenario for any package on npm, and @vendure/* is exactly the kind of target it looks for. Vendure is an enterprise commerce platform, and its packages are installed into thousands of production stores. The same pipeline publishes our open-source core under @vendure/* and the enterprise plugins under @vendure-platform/* that ship as part of Vendure Platform, so everything below protects both. A single malicious release published to the latest tag would not be one team's problem. It would propagate widely and fast, straight into the next npm install at every downstream store at once.

So the question we have to be able to answer, honestly, is this: what would it actually take for a malicious version of @vendure/core to reach your node_modules? What follows is the threat model we run our publishing pipeline against, written for the people who build on Vendure and need to trust what they install.

The one thing we are protecting

Strip away the detail and there is a single asset to defend: the integrity of what gets published to npm. Every artifact under @vendure/* and @vendure-platform/* must have been built from reviewed source, by our CI, and approved by a human. Nothing else should ever be able to reach the latest tag.

The guiding principle is defense in depth. No single control is treated as sufficient, because in a supply-chain attack any single control can fail. A leaked token, a compromised CI run, a tampered workflow file, an infected laptop: each one of these is survivable on its own. An attacker would have to defeat several independent layers at once, and each layer is designed assuming the one before it has already fallen.

There is a second principle worth stating up front. In enterprise procurement, open source is often treated as a supply-chain liability: unknown contributors, no vendor accountability, code you cannot personally vouch for. Here the opposite is true. Because the Vendure source is public, every control described below is auditable by anyone. The branch protections, the CODEOWNERS rules, the pinned action hashes, the signed provenance: none of it rests on taking our word for it. For an open-source platform, transparency is the control, not the gap.

There are no tokens to steal

The most common way packages get hijacked is a stolen publish token. Shai-Hulud spread precisely by harvesting long-lived npm tokens out of CI environments and developer machines. So the first move is to remove that target entirely.

Vendure publishes to npm using OIDC trusted publishing. Instead of a stored npm token sitting in a CI secret waiting to be exfiltrated, our CI authenticates with a short-lived identity token issued by GitHub at build time, scoped to that single run. There is no long-lived publish credential anywhere to leak. On top of that, mfa=publish is enforced, so even a manual publish requires a second factor. Credentials alone are never enough.

Every release also ships with build provenance: npm generates a signed, publicly verifiable attestation, recorded in a transparency log, that ties each published package back to the exact source commit and build workflow it came from. That is not just our own assurance. It is something you can verify yourself, which we will come back to at the end.

A human still has to say yes

Removing the token surface protects against a stolen credential. It does not protect against a compromised CI run, where a poisoned dependency or malicious build script alters the artifact before it is published.

For that, we use staged publishing. When CI publishes a release, the version lands in npm's staging area and is not installable. It does not exist on the latest tag, and npm install will not pull it. It stays there until a maintainer explicitly approves it, with a 2FA code, after reviewing what is about to go live. The automated pipeline can prepare a release, but a human is the one who lets it out the door.

Only two people, one workflow, from one place

Under trusted publishing, the publish workflow file is the security perimeter. Whoever can edit publish_to_npm.yml effectively controls what gets published and how. So that file, and the act of triggering it, is locked down from several directions at once:

  • The workflow can only be changed with code-owner review. A CODEOWNERS rule covers /.github/workflows/, backed by branch protection. The publish pipeline cannot be quietly modified, and the CODEOWNERS file protects itself.
  • Only two named maintainers can start a release. GitHub has no native per-user restriction on triggering a workflow or cutting a release, so the publish job itself checks the actor and rejects a release initiated by anyone else, including other team members who hold write access.
  • Publishing is bound to one workflow in one locked-down environment. A GitHub deployment environment, tied to the npm trust relationship with a branch-and-tag policy, means npm will only accept a release that originates from our specific workflow running from an approved branch. A run from a fork, another branch, or an ad-hoc script gets no valid claim and is rejected.
  • Release tags are equally restricted. A tag-protection ruleset limits who can create the v* version tags that releases are built from, scoped to the same two maintainers. A non-maintainer cannot even create the tag a malicious release would need.

The effect is one consistent two-person set governing tag creation, release triggering, and the workflow definition itself. There is no side door.

Nothing sneaks in through the build

A pipeline is only as trustworthy as the things it runs. Two classic supply-chain vectors live here: a hijacked third-party action, and an over-privileged automation token.

Third-party GitHub Actions are pinned to exact commit hashes, not mutable version tags. When an action is referenced as @v4, whoever controls that tag can repoint it at new code, which is exactly what happened in the tj-actions/changed-files compromise in 2025. By pinning to a full commit SHA, a repointed tag cannot silently flow into our builds. Pinning freezes a known-good commit; it does not vet the code, so we pair it with using only reputable actions and reviewing them periodically.

Automation tokens follow least privilege. Each CI token requests only the permissions its specific job needs, and that request is capped by what the underlying GitHub App actually grants. A token cannot ask for more than the App holds. If one were ever exposed, the blast radius is limited to that narrow scope rather than the full set of things the automation could theoretically do.

Hardening the install itself

Some attacks never wait for a release. They run the moment a malicious dependency is installed, through a lifecycle script, before anyone has a chance to notice.

Vendure uses Bun as its canonical package manager, configured with two relevant guards. A minimum-release-age cooldown means a freshly published version is not pulled in immediately, which closes the window that worms like Shai-Hulud depend on, where a malicious version is published and consumed within minutes. And an explicit trustedDependencies allow-list controls which packages are even permitted to run install-time scripts at all. A brand-new compromised version does not get to execute on our machines just because it appeared in the registry.

What if a maintainer's laptop is fully owned?

This is the scenario that bypasses the entire CI-side perimeter, so it is worth walking through honestly. Suppose a maintainer's machine is completely compromised and every local credential is harvested: npm tokens, the GitHub token, SSH keys, environment variables. The attacker now holds the maintainer's identity. What happens?

Each path the attacker tries runs into a wall that the stolen credentials cannot get past:

  • A stolen npm token from ~/.npmrc is useless for a direct publish, because mfa=publish demands a one-time code that is not on the disk.
  • The GitHub token deliberately lacks workflow scope, so it cannot modify .github/workflows/ and tamper with the publish pipeline.
  • Even cutting a release the legitimate way only gets the attacker as far as staging. Reaching the latest tag still requires npm stage approve with a 2FA code.

Everything an attacker needs is harvestable from the laptop except one thing: the npm second factor. The backstop holds precisely as long as that factor lives somewhere the laptop-resident code cannot reach, such as an authenticator app on a separate device or a hardware security key. That is the single load-bearing assumption, and naming it explicitly is part of taking it seriously.

What this means for you

If you build on Vendure, the integrity of @vendure/* does not rest on any one person, machine, or secret staying uncompromised. It rests on several independent controls that an attacker would have to defeat simultaneously. And if you are the one who has to justify that to a security team, none of it is a claim you have to relay on our behalf. It is a matter of public record:

  • The repository is public. The CODEOWNERS rules, branch protections, pinned action SHAs, and the publish workflow itself are all there to inspect at github.com/vendurehq/vendure.
  • Every release carries signed provenance. The @vendure/core page on npm shows the exact source commit and build workflow each version was produced from, no install required.

And if you already depend on the package, you can verify the whole chain end to end yourself:

Verify it yourself

Run npm audit signatures in a project that depends on @vendure/*. It checks the signed provenance attestations against npm's transparency log and confirms each package was built from the source commit and workflow it claims, by our pipeline, and not tampered with in transit.

If you are evaluating Vendure for a production deployment and want to walk through this threat model with our engineering team, get in touch.

Security is never finished

A threat model is a living document, not a finish line. There are residual risks we track openly: provenance only adds value if consumers actually verify it, staging is a human gate rather than an automatic malware scanner, and there are further hardening steps (hardware security keys, tighter branch protection across all release branches) on our list. Defense in depth means assuming each layer can fail, and that includes being honest about where the remaining edges are.

But the bar to ship a malicious @vendure/core@latest is no longer "steal one token." It is "defeat OIDC trusted publishing, a single bound workflow, an environment policy, a tag ruleset, code-owner review, SHA-pinned actions, scoped tokens, install-time gating, and a human 2FA approval, all at once." That is the bar we hold ourselves to, and it is why Shai-Hulud, and the next worm after it, has a very hard time reaching your stack.

Share this article