How Missing minimumReleaseAge in pnpm Workspaces Enables Supply Chain Attacks
Introduction
In a Node.js library's pnpm-workspace.yaml, we discovered a HIGH severity supply chain hardening gap at line 1. The workspace configuration defined packages and a catalog but completely lacked any supply chain protection settings — no minimumReleaseAge, no blockExoticSubdeps, and no trustPolicy. For a library consumed by downstream users, this means that any time a maintainer runs pnpm install or updates dependencies, freshly published (and potentially compromised) package versions could be pulled in without any quarantine period.
Here's what the vulnerable configuration looked like:
packages:
- "packages/*"
- "packages/create-ziko/templates/*"
catalog:
vite: "^8.2.2"
This configuration handles workspace package resolution and dependency cataloging, but it provides zero defense against supply chain poisoning — one of the most rapidly growing attack vectors in the JavaScript ecosystem.
The Vulnerability Explained
What's Actually Missing
The minimumReleaseAge setting, introduced in pnpm v10.16.0, tells pnpm to refuse installing any package version that was published less than a specified number of minutes ago. Without it, pnpm's default behavior is to install the latest matching version immediately — even if it was published seconds ago.
This matters because supply chain attacks follow a predictable pattern:
- An attacker gains access to a maintainer's npm account (credential theft, expired 2FA, social engineering)
- They publish a malicious version of the package
- Within minutes, thousands of CI/CD pipelines and developer machines pull the compromised version
- The malicious code exfiltrates secrets, installs backdoors, or compromises build artifacts
The window between publication and detection is typically hours to days. The ua-parser-js incident in 2021, the colors and faker incidents in 2022, and the xz-utils backdoor in 2024 all exploited this gap.
Attack Scenario Specific to This Project
This is a Node.js library with a monorepo structure (packages/* and packages/create-ziko/templates/*). Consider this attack path:
- The catalog pins
vite: "^8.2.2"— this means any version from 8.2.2 up to (but not including) 9.0.0 is acceptable - An attacker compromises the
vitenpm account and publishesvite@8.2.3with a postinstall script that exfiltrates~/.npmrctokens - A maintainer runs
pnpm update— the maliciousvite@8.2.3is installed immediately - The library's build artifacts could be poisoned, affecting every downstream consumer
- Template files in
packages/create-ziko/templates/*could generate projects with the compromised dependency baked in
Without minimumReleaseAge, there is literally zero delay between a malicious publish and its installation.
The Fix
The fix adds three supply chain hardening directives to pnpm-workspace.yaml:
Before (Vulnerable)
packages:
- "packages/*"
- "packages/create-ziko/templates/*"
catalog:
vite: "^8.2.2"
After (Hardened)
packages:
- "packages/*"
- "packages/create-ziko/templates/*"
catalog:
vite: "^8.2.2"
minimumReleaseAge: 10080
blockExoticSubdeps: true
trustPolicy: no-downgrade
Breakdown of Each Setting
minimumReleaseAge: 10080 (7 days in minutes)
This is the primary defense. pnpm will now refuse to install any package version published less than 7 days ago. This provides a quarantine window during which:
- The npm security team can detect and remove malicious packages
- The community can report suspicious versions
- Automated security scanners can flag compromised releases
- The legitimate maintainer can notice unauthorized publishes
blockExoticSubdeps: true
This prevents subdependencies from using exotic protocols like git:, file:, or link: references. Attackers sometimes compromise a package to add a subdependency pointing to a malicious git repository. This setting blocks that vector entirely.
trustPolicy: no-downgrade
This prevents version downgrades, which is an attack vector where an attacker publishes a "new" version with a lower semver to exploit resolution quirks, or where a compromised lockfile attempts to downgrade to a known-vulnerable version.
Prevention & Best Practices
1. Always Set minimumReleaseAge in Production Projects
For libraries consumed by others, 7 days (10080 minutes) is the recommended minimum. For internal applications with less tolerance for delay, even 3 days (4320 minutes) provides significant protection.
2. Layer Your Defenses
Supply chain security requires defense in depth:
- minimumReleaseAge: Quarantine new versions
- blockExoticSubdeps: Block exotic protocol attacks
- trustPolicy: no-downgrade: Prevent downgrade attacks
- Lockfiles: Pin exact versions in CI
- npm audit / pnpm audit: Check for known vulnerabilities
3. Audit Your Workspace Configuration
Run this check in your CI pipeline:
grep -q "minimumReleaseAge" pnpm-workspace.yaml || echo "WARNING: No minimumReleaseAge set!"
4. Use Semgrep for Configuration Scanning
Static analysis tools like Semgrep can detect missing security settings in configuration files, not just code. The rule package_managers.pnpm.pnpm-missing-minimum-release-age.pnpm-minimum-release-age specifically catches this pattern.
5. Monitor for pnpm Security Features
pnpm is actively adding supply chain hardening features. Stay current with their settings documentation and adopt new protections as they become available.
Key Takeaways
- The
pnpm-workspace.yamlcatalog pinningvite: "^8.2.2"is insufficient protection — semver ranges allow any matching version, including freshly published malicious ones - A 7-day quarantine (
minimumReleaseAge: 10080) would have blocked every major npm supply chain attack in recent history, as all were detected and removed within days - Node.js libraries are high-value targets because a single compromised library dependency propagates to all downstream consumers via
packages/create-ziko/templates/* blockExoticSubdeps: truecloses the git/file protocol attack vector that bypasses registry-level protections entirelytrustPolicy: no-downgradeprevents a subtle attack class where lockfile manipulation forces installation of older, vulnerable versions
How Orbis AppSec Detected This
- Source: npm registry packages resolved during
pnpm install, includingvite: "^8.2.2"from the catalog and all transitive dependencies of packages inpackages/* - Sink: The
pnpm-workspace.yamlconfiguration file at line 1, which controls dependency resolution behavior for the entire monorepo - Missing control: No
minimumReleaseAgesetting to quarantine newly published packages, noblockExoticSubdepsto prevent exotic protocol subdependencies, and notrustPolicyto prevent version downgrades - CWE: CWE-829 (Inclusion of Functionality from Untrusted Control Sphere)
- Fix: Added
minimumReleaseAge: 10080,blockExoticSubdeps: true, andtrustPolicy: no-downgradeto enforce a 7-day quarantine period and block exotic dependency protocols
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
Supply chain attacks are no longer theoretical — they're happening weekly across the npm ecosystem. The minimumReleaseAge setting in pnpm v10.16.0+ is one of the most impactful single-line changes you can make to protect your project. For a library like this one, where compromised dependencies propagate to every downstream consumer through templates and packages, the 7-day quarantine window is essential. Combined with blockExoticSubdeps and trustPolicy: no-downgrade, these three lines transform pnpm-workspace.yaml from a passive configuration file into an active supply chain defense layer.
Don't wait for an incident to harden your workspace configuration. The fix is three lines of YAML.