The Risk Hidden in a Single Zero
In a Node.js library's pnpm-workspace.yaml, a single configuration line read:
minimumReleaseAge: 0
That zero is not a neutral default—it is an open door. It tells pnpm: install any package version the moment it appears on the npm registry, no matter how new it is. For a project that downstream consumers depend on, this setting means that a supply chain attack targeting a transitive dependency could propagate to production systems within minutes of the malicious package being published.
This post explains what the minimumReleaseAge setting does, why the value 0 is dangerous, and how setting it to 10080 (seven days in minutes) closes a real attack vector that has been exploited in the wild.
The Vulnerability Explained
What minimumReleaseAge Controls
pnpm v10.16.0 introduced the minimumReleaseAge setting in workspace configuration. It accepts a value in minutes and instructs pnpm to refuse resolving any package version that was published to the npm registry more recently than that threshold.
When set to 0, the feature is effectively disabled—pnpm will happily resolve and install a package version published 30 seconds ago.
The Vulnerable Configuration
Here is the configuration as it existed before the fix, in pnpm-workspace.yaml at line 5:
allowBuilds:
esbuild: true
unrs-resolver: true
vue-demi: true
minimumReleaseAge: 0
The allowBuilds section is correctly configured, but the minimumReleaseAge: 0 line actively opts out of the quarantine window.
How This Gets Exploited
The npm ecosystem has seen a consistent pattern of attacks that this setting directly mitigates:
-
Account takeover / credential stuffing: An attacker compromises a maintainer's npm credentials and publishes a new patch version (e.g.,
some-util@2.3.1) containing malicious code. WithminimumReleaseAge: 0, the nextpnpm installor CI run picks it up immediately. -
Typosquatting and dependency confusion: A new package with a name close to a popular one is published. Automated bots and CI pipelines that run installs frequently are the first victims.
-
Protestware / supply chain sabotage: A maintainer intentionally injects malicious code into a new version. The 7-day window gives the community time to discover, report, and for registries to yank the package.
Real-World Impact for This Repository
This file is in a Node.js library that downstream consumers install. If a malicious version of a dependency were installed into this library's build or test pipeline, it could:
- Exfiltrate environment variables (CI secrets, npm tokens, cloud credentials) during
pnpm install - Tamper with the built output that gets published to npm, affecting all downstream users
- Execute arbitrary scripts via pnpm's
allowBuildslifecycle hooks (which are already enabled foresbuild,unrs-resolver, andvue-demi)
The combination of allowBuilds being enabled and minimumReleaseAge: 0 is particularly risky: build scripts run during install, so a malicious package that lands in the dependency tree can execute code immediately.
The Fix
The change is minimal but meaningful:
-minimumReleaseAge: 0
+minimumReleaseAge: 10080
Before:
allowBuilds:
esbuild: true
unrs-resolver: true
vue-demi: true
minimumReleaseAge: 0
After:
allowBuilds:
esbuild: true
unrs-resolver: true
vue-demi: true
minimumReleaseAge: 10080
Why 10080?
10080 is the number of minutes in seven days (7 × 24 × 60 = 10,080). This is the value recommended by the pnpm documentation and the value flagged by the Semgrep rule. Seven days is considered a reasonable window because:
- Most malicious packages are detected and removed by npm's security team or community reporters within hours to days
- Legitimate patch releases rarely need to be consumed within minutes; most projects can tolerate a week's delay on transitive dependency updates
- Security researchers and automated scanning tools (like Socket.dev and Snyk) typically surface issues within this window
What pnpm Does With This Setting
When minimumReleaseAge: 10080 is set, pnpm checks the time field in a package's npm registry metadata during resolution. If the version's publish timestamp is less than 10,080 minutes ago, pnpm skips that version and resolves to the next-oldest version that satisfies the semver range. This happens transparently during pnpm install—no error, no manual intervention needed.
Prevention & Best Practices
1. Always Set minimumReleaseAge in New pnpm Workspaces
When creating a new pnpm-workspace.yaml, include this setting from day one:
minimumReleaseAge: 10080
It was added in pnpm v10.16.0, so ensure your team is on a recent enough version.
2. Combine With Other Supply Chain Controls
minimumReleaseAge is one layer. Pair it with:
- Lockfiles committed to source control: Prevents unexpected resolution changes between installs
pnpm auditin CI: Catches known CVEs in the resolved dependency tree- Provenance attestation: Use packages published with npm provenance where available
- Dependency review in PRs: Tools like Dependabot or Renovate can flag new dependency additions for human review
3. Audit Your allowBuilds List
This project enables build scripts for esbuild, unrs-resolver, and vue-demi. Each of these is a potential code execution point during install. Periodically review this list and remove entries for packages that no longer need it.
4. Static Analysis in CI
The Semgrep rule package_managers.pnpm.pnpm-missing-minimum-release-age.pnpm-minimum-release-age detects this pattern automatically. Add it to your CI pipeline to catch regressions:
semgrep --config "p/supply-chain" pnpm-workspace.yaml
Relevant Standards
- CWE-1357: Reliance on Insufficiently Trustworthy Component
- OWASP A06:2021: Vulnerable and Outdated Components
- SLSA Supply Chain Threats: Compromised dependency (threat D)
Key Takeaways
minimumReleaseAge: 0is not a safe default—it actively disables a security control that pnpm provides specifically to combat supply chain attacks.- The combination of
allowBuildsandminimumReleaseAge: 0in thispnpm-workspace.yamlwas especially risky: build scripts execute during install, so a newly published malicious package could run code immediately. - 10,080 minutes (7 days) is the recommended quarantine window because it aligns with the typical detection and response time for malicious npm packages.
- Downstream consumers of this Node.js library were indirectly protected by this fix—a compromised build pipeline could have resulted in a tainted published package.
- Static analysis tools like Semgrep can detect this in seconds—there is no reason to leave this misconfiguration undetected in a CI pipeline.
How Orbis AppSec Detected This
- Source: The
pnpm-workspace.yamlconfiguration file, which governs dependency resolution for all packages in the workspace - Sink: The
minimumReleaseAge: 0setting at line 5, which instructs pnpm to resolve and install package versions with no age restriction—including versions published seconds before an install run - Missing control: No minimum release age threshold was enforced, meaning newly published (potentially malicious) package versions were immediately eligible for installation
- CWE: CWE-1357 — Reliance on Insufficiently Trustworthy Component
- Fix: Changed
minimumReleaseAgefrom0to10080inpnpm-workspace.yaml, enforcing a 7-day quarantine window on all newly published package versions
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
A single zero in a YAML configuration file was all it took to leave this Node.js library's dependency resolution wide open to supply chain attacks. The fix—changing minimumReleaseAge: 0 to minimumReleaseAge: 10080—took one line but added a meaningful, automated defense layer against one of the most active attack categories targeting the npm ecosystem today.
Supply chain attacks are not theoretical. The npm registry sees malicious packages published regularly, and the attack window between publish and detection is often measured in hours. A 7-day quarantine window, enforced automatically by pnpm, is a low-friction, high-value control that every workspace should have enabled.
If your project uses pnpm, open your pnpm-workspace.yaml right now and check whether minimumReleaseAge is set. If it is missing or set to 0, you have the same vulnerability this PR just fixed.