When we first wrote about the Shai-Hulud worm in November 2025, it had already compromised over 180 packages. By the time the second and third waves hit, that number had grown to 796 packages with a combined 132 million monthly downloads. Then in early 2026, Glassworm proved these were not isolated incidents but the beginning of a new attack paradigm.
The JavaScript ecosystem is one of the most targeted software supply chains on earth. But the npm ecosystem has not been standing still, shipping meaningful defenses on multiple fronts. Understanding where each set of controls operates, and why you need both, is essential.
Two sides of the supply chain
Supply chain security has two fundamentally different surfaces: the publisher side (what happens when a package is built and uploaded to the registry) and the consumer side (what happens when a developer or CI pipeline installs that package). Securing only one side leaves the other wide open. npm has focused on making publishing safer. pnpm and other package managers have focused on making consumption safer. Together, they significantly raise the bar for attackers, though neither layer alone is a complete defense. Defense in depth reduces risk; it does not eliminate it.
Publisher side: what npm has done
npm deserves credit for substantial improvements that make it harder for attackers to publish malicious packages in the first place:
Trusted publishing and provenance attestations
These two features work together and are best understood through the lens of the SLSA framework (Supply-chain Levels for Software Artifacts), a widely adopted standard for reasoning about supply chain integrity.
Trusted publishing lets packages prove they were built inside a CI/CD environment (via GitHub Actions OIDC tokens) rather than from a developer's laptop. When a package opts into trusted publishing, npm will only accept new versions that carry a valid OIDC attestation from the configured CI provider. This makes stolen passwords and leaked tokens far less useful because attackers cannot replicate the trusted build environment.
Provenance attestations create a verifiable, cryptographically signed link (via Sigstore) between a published package version, the exact source commit that produced it, and the build system that ran the publish step. Consumers can inspect this chain and confirm that what they are installing matches what the maintainer intended to ship. The npm CLI shows provenance information during npm audit signatures, and the npm website displays a green checkmark for packages with valid provenance.
In SLSA terms, npm packages published with trusted publishing and provenance from GitHub Actions achieve roughly SLSA Build Level 2: builds run on a hosted platform (not a developer workstation) and provenance is cryptographically signed so it cannot be forged. This is a meaningful step up from the pre-provenance world (Build L0), where there was no verifiable connection between a published package and its source. Full SLSA Build L3 would additionally require hardened build isolation that prevents one build from influencing another, which depends on the specific CI/CD configuration.
It is important to understand what SLSA provenance does and does not guarantee. It proves where a package was built and from which source commit. It does not verify what the code does. A maintainer with malicious intent can publish harmful code through a fully trusted CI/CD pipeline with valid provenance, and every check will pass. SLSA increases confidence in the build process, not in the author's intentions.
Granular access tokens
The old npm tokens were all-or-nothing: one token could publish every package the user owned, with no expiry. Granular access tokens replace this with scoped, time-limited, IP-restricted credentials. A token can be limited to a single package, set to read-only, or configured to expire after a fixed period. This dramatically reduces blast radius when tokens leak in CI logs, .env files, or compromised machines.
Mandatory 2FA and account recovery improvements
npm now enforces two-factor authentication for maintainers of high-impact packages and has improved account recovery flows to prevent social engineering attacks that previously let attackers take over accounts by convincing npm support to reset credentials.
These are significant steps forward. They address the mechanics behind attacks like Shai-Hulud, where stolen npm tokens allowed the worm to publish infected versions of legitimate packages. But they all operate on the publishing side of the equation.
Consumer side: the gap that remains
Publishing controls protect the front door. But once a malicious package makes it into the registry, and some inevitably will, there is nothing on the consumer side to stop it.
When you run npm install, every package you pull down can execute arbitrary code on your machine through lifecycle scripts (preinstall, postinstall, prepare). These scripts run with your full user privileges: access to your filesystem, your credentials, your SSH keys, your cloud tokens, and your CI/CD secrets.
This is exactly how Shai-Hulud worked. Once a developer installed an infected package, the preinstall script harvested npm tokens and GitHub credentials, then used them to publish infected versions of every package that developer maintained. The worm spread exponentially, not by exploiting a vulnerability in npm itself, but by exploiting the implicit trust that npm install places in every dependency.
npm's publisher-side improvements make it harder to get malicious packages into the registry. But they do not prevent your machine from running malicious code that already made it through. For that, you need consumer-side defenses.
Consumer side: pnpm v11's three layers of defense
pnpm has been building consumer-side security controls since v10 and began shipping security-by-default behavior in late 2025, making it one of the first package managers to block lifecycle scripts and enforce release cooldowns out of the box. pnpm v11 (released April 2026) kept those defaults and refined the developer experience by unifying multiple configuration keys into a single, cleaner API. Three controls directly address the attack patterns we documented in our Shai-Hulud series. Each targets a different stage of the supply chain attack lifecycle. Critically, pnpm uses npm's registry and benefits from all of npm's publisher-side improvements while adding protection on the consumer side.
1. Lifecycle script management: stop running untrusted code
The most impactful control is strictDepBuilds, which has defaulted to true since late 2025. Instead of silently executing every dependency's preinstall and postinstall scripts, pnpm blocks them by default and fails the installation.
To allow specific packages to run build scripts, you explicitly allowlist them. In pnpm v11, you use the new allowBuilds map in pnpm-workspace.yaml:
YAML# pnpm-workspace.yamlallowBuilds:esbuild: truesharp: true
Earlier versions used separate settings (onlyBuiltDependencies, onlyBuiltDependenciesFile, neverBuiltDependencies, ignoredBuiltDependencies, and ignoreDepScripts). pnpm v11 replaced all five with allowBuilds, a single unified map. Set a package to true to allow its build scripts, or false to explicitly block them.
Packages not on the list cannot execute code during installation. Period.
This is a fundamental shift. Instead of trusting every dependency by default and hoping none of them are malicious, you make a conscious decision about which packages actually need to run code at install time. In practice, the number is surprisingly small. The Seattle Times, which piloted these controls, found that all three of their flagged packages (esbuild, @firebase/util, protobufjs) could be blocked with zero functionality impact for their use case.
Why this matters for Shai-Hulud specifically: The worm's entire propagation mechanism depended on preinstall scripts executing automatically. With strictDepBuilds: true, the worm's payload would never run.
2. Release cooldown: quarantine new versions
The minimumReleaseAge setting prevents pnpm from resolving package versions that were published within a configurable time window. In pnpm v11, this defaults to 1440 minutes (one day). By default, minimumReleaseAgeStrict is false, meaning pnpm will fall back to an older version that meets the age requirement rather than failing the install entirely. Setting it to true makes the check strict: if the only matching version is too new, installation fails.
YAMLminimumReleaseAge: 1440minimumReleaseAgeExclude:- react@19.1.0
The logic is simple: most malicious packages are detected and removed quickly. The Shai-Hulud attack was detected within about 12 hours. The September 2025 attack on debug and chalk was resolved in approximately 2.5 hours. A one-day cooldown would have blocked both.
This trades bleeding-edge freshness for safety. Your dependencies arrive a day behind the latest version, which for the vast majority of projects is not a meaningful tradeoff. When you genuinely need a critical security patch immediately, you add a specific exception and document why.
Why this matters: The Seattle Times avoided Shai-Hulud 2.0 purely by luck: they did not happen to run npm install during the attack window. Release cooldown replaces luck with policy.
3. Trust policy: detect credential compromise
The trustPolicy setting (introduced in pnpm v10.21) detects when a package version was published with weaker authentication than previous versions. When set to no-downgrade, pnpm blocks installation of any version where the trust level has decreased. Unlike strictDepBuilds and minimumReleaseAge, this feature is not yet enabled by default (the default is off), so you need to opt in explicitly.
YAMLtrustPolicy: no-downgradetrustPolicyExclude:- some-package@1.2.3
How it works: When a package is published with --provenance on npm, the registry stores SLSA provenance attestations and Sigstore signatures alongside the package tarball. These attestations are available via npm's attestation API (e.g., registry.npmjs.org/-/npm/v1/attestations/<package>@<version>). pnpm queries this data to determine how each version was published and assigns it a trust level:
- Trusted Publisher (strongest): Package published via GitHub Actions with OIDC tokens and npm provenance attestations
- Provenance: Published from CI/CD with attestation but without full trusted publisher setup
- No trust evidence (weakest): Published with username/password or token authentication, with no attestations present
pnpm then compares the trust level of the version being installed against previously published versions (ordered by publish date, not semver). If the new version has a lower trust level, installation fails.
Why this matters: When a package that has been consistently published through trusted CI/CD pipelines suddenly has a version published from a developer's laptop using basic credentials, that is a strong signal of credential compromise. This is exactly what happened in the August 2025 s1ngularity attack: attackers compromised maintainer credentials and published malicious versions from their own machines. Those versions would have lacked the provenance attestations present on earlier versions, and trustPolicy: no-downgrade would have blocked them.
How do other package managers compare?
pnpm is not the only package manager investing in consumer-side security. Yarn Berry (v4) and Bun have both shipped meaningful features, while the npm CLI still lags behind.
Lifecycle script blocking
Yarn Berry sets enableScripts: false by default (since Yarn v2), blocking third-party postinstall scripts. Per-package exceptions use dependenciesMeta in package.json:
JSON{"dependenciesMeta": {"esbuild": { "built": true }}}
Bun has blocked dependency lifecycle scripts by default since its first release, with a trustedDependencies array in package.json for exceptions.
The npm CLI is the outlier. Scripts run by default, and the only option is the blunt --ignore-scripts flag, which disables all scripts (including your own project scripts). There is no per-package allowlist.
Release cooldown
Yarn Berry ships npmMinimalAgeGate defaulting to three days, which is even more conservative than pnpm's one-day default. Exceptions are handled via npmPreapprovedPackages glob patterns.
Bun offers minimumReleaseAge in bunfig.toml, but it is opt-in (not enabled by default).
The npm CLI has no release cooldown feature at all.
Trust policy and provenance verification
This is where pnpm stands alone. Its trustPolicy: no-downgrade is the only consumer-side control that detects when a package's publishing authentication weakens between versions.
npm can publish provenance attestations and offers npm audit signatures for manual verification, but this is not enforced at install time. Nothing prevents you from installing a package version that dropped provenance.
Yarn Berry and Bun have no consumer-side provenance verification.
Other notable features
Yarn Berry's enableHardenedMode (enabled by default on public GitHub PRs) validates lockfile content against the remote registry, protecting against lockfile poisoning attacks where a malicious PR modifies the lockfile to point to a compromised package.
pnpm v11 also enables two additional security defaults:
blockExoticSubdepsblocks git repositories and tarball URLs in transitive dependencies, ensuring all transitive deps come from the registry. A direct dependency can still use a git source, but it cannot pull in further git-hosted or tarball subdependencies.verifyDepsBeforeRun(defaulting toinstall) ensures that dependencies are verified and up to date before any project scripts execute, preventing scenarios where stale or tamperednode_modulesare used silently.
The comparison at a glance
| Feature | pnpm v11 | npm CLI | Yarn Berry (v4) | Bun |
|---|---|---|---|---|
| Script blocking (default) | Yes | No | Yes | Yes |
| Per-package allowlist | Yes | No | Yes | Yes |
| Release cooldown (default) | Yes (1 day) | No | Yes (3 days) | Opt-in |
| Trust/provenance enforcement | Opt-in | Manual only | No | No |
| Exotic subdep blocking | Yes | No | Partial | No |
| Lockfile validation vs registry | No | No | Yes | No |
| Strict dep isolation | Yes | No | Yes (PnP) | Optional |
pnpm v11 has the most comprehensive consumer-side security posture, with script blocking and release cooldown enabled by default and trust policy available as an opt-in. Yarn Berry is a strong second, with script blocking and release cooldown also on by default. The npm CLI remains the least protected consumer, with none of these features available out of the box.
Defense in depth: why the layers matter together
These three controls are designed to complement each other. When you need to make an exception to one layer, the others still protect you.
Consider a real scenario: a critical security vulnerability is discovered in React, and you need the patch immediately. You add the new React version to minimumReleaseAgeExclude to bypass the cooldown. But you are still protected by:
- Lifecycle script management, which blocks any injected build scripts in the patched version
- Trust policy, which verifies the patch was published through React's normal trusted CI/CD pipeline
No single control is perfect. Attackers will find edge cases. The point is that an attacker now needs to defeat all three layers simultaneously, which is a fundamentally harder problem than defeating any one of them.
What you should do today
Regardless of which package manager your team uses, there are concrete steps you can take right now:
If you are on pnpm v11: You already have strictDepBuilds and minimumReleaseAge enabled by default. Review your allowBuilds map to make sure you are only allowing build scripts that are genuinely necessary, and consider opting into trustPolicy: no-downgrade for an additional layer of protection.
If you are on Yarn Berry (v4): You already have script blocking and a three-day release cooldown by default. Verify that enableHardenedMode is active and review your dependenciesMeta for unnecessary exceptions.
If you are on Bun: Script blocking is on by default. Consider enabling minimumReleaseAge in your bunfig.toml since it is not on by default.
If you are on the npm CLI: You have the fewest consumer-side protections available. At a minimum, audit which dependencies run lifecycle scripts (npm explain can help) and evaluate whether switching to a package manager with stronger defaults is practical for your team.
For all teams: Document every exception to your security controls. When you allowlist a package for build scripts or bypass a release cooldown for a critical patch, record why. This creates an audit trail and forces conscious decisions about trust rather than silent defaults.
Publisher + consumer: the complete picture
Supply chain security requires both sides working together. npm's publisher-side controls protect the registry. Consumer-side controls in pnpm, Yarn Berry, and Bun protect developer machines and CI/CD pipelines. The npm CLI is the only major package manager that has not shipped consumer-side defenses.
| Layer | What it protects | Key controls |
|---|---|---|
| Publisher (npm registry) | Registry integrity | Trusted publishing, provenance, granular tokens, mandatory 2FA |
| Consumer (pnpm, Yarn, Bun) | Developer machines and CI/CD | Lifecycle script blocking, release cooldown, trust policy |
Neither layer alone is sufficient. Trusted publishing does not help if your package manager blindly executes preinstall scripts from every dependency. Blocking lifecycle scripts does not help if an attacker can silently replace a legitimate package version at the registry level. You need both.
If you are still using the npm CLI, you are relying entirely on publisher-side defenses and hoping nothing slips through. Switching to pnpm, Yarn Berry, or Bun gives you the consumer-side protections that the npm CLI does not offer.
The Shai-Hulud worm, Glassworm, and the string of supply chain attacks throughout 2025 and 2026 have proven that the era of implicit trust in package installation is over. The tools to secure both sides of the supply chain exist right now. The only question is whether your team will enable them before the next worm arrives.
How Mondoo helps
Mondoo's Agentic Vulnerability Management goes beyond scanning for known CVEs. It analyzes your repositories, container images, CI/CD pipelines, and developer workstations to detect supply chain threats like Shai-Hulud, including compromised packages, unauthorized lifecycle scripts, and suspicious dependency changes. Combined with pnpm's client-side controls, Mondoo provides visibility into whether your supply chain defenses are actually configured and enforced across your organization.
Ready to see Mondoo in action? Schedule a demo today.
References
- SLSA: Supply-chain Levels for Software Artifacts — The framework for reasoning about build integrity levels
- pnpm in 2025: security by default — pnpm's December 2025 retrospective on shipping security-by-default behavior
- pnpm v11 release notes — Unified
allowBuildsconfig and continued security defaults - pnpm: npm supply chain security (Seattle Times case study) — Real-world implementation of pnpm's three-layer defense
- Introducing npm package provenance — GitHub's announcement of Sigstore-based provenance for npm
- npm: Generating provenance statements — Official npm documentation on provenance attestations
- Navigating the Sands of Dune: Protecting NPM From the Shai-Hulud Worm — Our initial Shai-Hulud analysis
- Shai-Hulud Strikes Back, with v3.0 — Second and third wave analysis
- Beyond Shai-Hulud: Why the Era of the Software Supply Chain Worm Has Just Begun — The broader supply chain worm threat landscape
- Glassworm Proves It: The Supply Chain Worm Era Is No Longer Theoretical — Glassworm analysis confirming the trend




