How Missing Minimum Release Age Happens in pnpm Workspaces and How to Fix It
The Vulnerability at a Glance
| Field | Detail |
|---|---|
| Vulnerability | Missing Minimum Release Age in pnpm Workspace |
| CWE | CWE-1395 — Dependency on Vulnerable Third-Party Component |
| Language | YAML / Node.js (pnpm) |
| Risk | Malicious or unstable newly published packages installed automatically |
| Root Cause | pnpm-workspace.yaml lacked minimumReleaseAge setting |
| Fix | Added minimumReleaseAge: 10080, blockExoticSubdeps: true, trustPolicy: no-downgrade |
Summary
A pnpm workspace configuration was missing the minimumReleaseAge setting, meaning freshly published — and potentially malicious or unstable — npm packages could be installed immediately. By adding minimumReleaseAge: 10080 to pnpm-workspace.yaml, the project now enforces a seven-day waiting period before any newly released package version can be installed. This single configuration change significantly reduces the attack surface for supply chain attacks targeting this Node.js library and its downstream consumers.
Introduction
The pnpm-workspace.yaml file is the control center for a pnpm monorepo. It defines which directories are workspace packages and — critically — governs the security policies that apply when resolving and installing dependencies across all of apps/* and packages/*. In this project, the file was doing its job of mapping workspace directories, but it was leaving a significant supply chain door wide open: there was no minimumReleaseAge setting, meaning pnpm would happily install a package version the instant it appeared on the npm registry, with zero waiting period.
This matters because supply chain attacks increasingly rely on that narrow window between publication and detection. Whether it's a typosquatted package name, a compromised maintainer account, or a malicious version bump, attackers count on automated CI pipelines pulling fresh packages before anyone notices something is wrong.
The Vulnerability Explained
What Was Missing
Here is the original pnpm-workspace.yaml before the fix:
packages:
- "apps/*"
- "packages/*"
Three lines. Functional, but completely silent on package vetting policy. Without minimumReleaseAge, pnpm operates with the implicit assumption that any version on the npm registry is safe to install the moment it is published.
Why This Is a Real Risk
The npm registry publishes tens of thousands of new package versions every day. The security community — including automated scanners, maintainers, and researchers — needs time to review new releases. The gap between "published" and "detected as malicious" is often measured in hours to days.
Three concrete attack vectors exploit this gap:
1. Account Takeover (ATO) Attacks
An attacker compromises a popular package maintainer's npm credentials and publishes a malicious patch version (e.g., some-util@2.3.1 → some-util@2.3.2). Any project running pnpm install within the first few hours after publication will pull the compromised version before npm or the security community has time to yank it.
2. Dependency Confusion
An attacker publishes a public package with the same name as an internal private package used in this monorepo (which covers apps/* and packages/*). Without a release age gate, the first pnpm install after publication could resolve to the attacker's version.
3. Typosquatting
A developer makes a typo in a new dependency name. The attacker has pre-registered that typo on npm with a malicious payload. Without a minimum release age, the malicious package installs immediately.
Why This Project Is Particularly Exposed
The PR notes this is a Node.js library — meaning vulnerabilities don't just affect the project itself, but propagate downstream to every consumer of the library. A supply chain compromise here has a multiplier effect: one poisoned install can affect dozens or hundreds of downstream projects.
The Fix
What Changed
The fix added three security settings to pnpm-workspace.yaml:
Before:
packages:
- "apps/*"
- "packages/*"
After:
packages:
- "apps/*"
- "packages/*"
minimumReleaseAge: 10080
blockExoticSubdeps: true
trustPolicy: no-downgrade
Breaking Down Each Change
minimumReleaseAge: 10080
This is the core fix. The value 10080 is the number of minutes in seven days (7 × 24 × 60 = 10,080). With this setting, pnpm will refuse to install any package version that was published less than seven days ago.
This seven-day window is deliberate and meaningful:
- npm's security team typically identifies and yanks malicious packages within hours to a few days
- Community security researchers and automated scanners have time to flag suspicious releases
- The project's own developers are more likely to notice unusual dependency updates in code review
This setting was introduced in pnpm v10.16.0 and is referenced in the official pnpm documentation.
blockExoticSubdeps: true
This setting prevents installation of dependencies that use non-standard or exotic resolution mechanisms — a common vector in dependency confusion and supply chain attacks where attackers exploit unusual package specifiers.
trustPolicy: no-downgrade
This prevents pnpm from silently downgrading the trust level of packages already in the workspace. Combined with minimumReleaseAge, it ensures that once a package version has been vetted and installed, it cannot be quietly replaced with a less-trusted version.
The Combined Effect
These three settings work together as a layered defense:
New package published on npm
│
▼
┌─────────────────────────────┐
│ minimumReleaseAge: 10080 │ ← Blocks install for 7 days
│ (time-based vetting gate) │
└─────────────────────────────┘
│ (after 7 days)
▼
┌─────────────────────────────┐
│ blockExoticSubdeps: true │ ← Blocks non-standard resolution
└─────────────────────────────┘
│
▼
┌─────────────────────────────┐
│ trustPolicy: no-downgrade │ ← Prevents trust regression
└─────────────────────────────┘
│
▼
Package installed ✓
Prevention & Best Practices
1. Enable minimumReleaseAge in All pnpm Projects
Any project using pnpm v10.16.0 or later should add this to pnpm-workspace.yaml or .npmrc:
# pnpm-workspace.yaml
minimumReleaseAge: 10080 # 7 days in minutes
For projects where a 7-day wait is too long (e.g., rapid iteration environments), consider a shorter but non-zero value:
minimumReleaseAge: 1440 # 1 day minimum
Even a 24-hour window catches many automated malicious publish attacks.
2. Use a Lockfile and Commit It
A pnpm-lock.yaml committed to your repository ensures that pnpm install in CI always installs the exact same versions. The minimumReleaseAge setting protects you when you update the lockfile — the lockfile protects you when you use it.
3. Enable Provenance and Audit in CI
Add these to your CI pipeline:
pnpm audit --audit-level=high
And consider enabling npm provenance for packages you publish, so consumers can verify the build origin.
4. Monitor Dependencies with Automated Tools
Tools like Dependabot, Renovate, and Socket.dev provide ongoing monitoring of your dependency tree for newly introduced risks.
5. Apply Defense-in-Depth for Supply Chain
The OWASP Software Component Verification Standard (SCVS) and SLSA framework provide structured guidance for supply chain security beyond just package manager settings.
Relevant Standards
- CWE-1395: Dependency on Vulnerable Third-Party Component
- OWASP A06:2021: Vulnerable and Outdated Components
- SSDF PW.4: Reuse existing, well-secured software where feasible
Key Takeaways
pnpm-workspace.yamlis a security surface, not just a directory map. The absence ofminimumReleaseAgein this file meant allapps/*andpackages/*workspaces were exposed to zero-day package installations.- 10,080 minutes (7 days) is the recommended minimum. This specific value aligns with the typical detection window for malicious npm packages and is the value recommended by the pnpm team.
- Three settings work better than one. The combination of
minimumReleaseAge,blockExoticSubdeps, andtrustPolicy: no-downgradeprovides layered supply chain defense that no single setting achieves alone. - Library projects have amplified risk. Because this is a Node.js library with downstream consumers, a supply chain compromise here propagates further than it would in a standalone application — making this fix especially high-value.
- This is a configuration gap, not a code bug. Static analysis tools like Semgrep can detect missing security configurations just as effectively as they detect vulnerable code patterns — don't limit your scanning to source code only.
How Orbis AppSec Detected This
- Source: The
pnpm-workspace.yamlfile at line 1, which governs dependency resolution policy for all packages in theapps/*andpackages/*workspace directories. - Sink: Any
pnpm installorpnpm updateinvocation that resolves package versions — specifically, the absence of a time-based gate before newly published versions are accepted. - Missing control: The
minimumReleaseAgesetting was entirely absent frompnpm-workspace.yaml, meaning pnpm applied no waiting period to newly published package versions. - CWE: CWE-1395 — Dependency on Vulnerable Third-Party Component
- Fix: Added
minimumReleaseAge: 10080,blockExoticSubdeps: true, andtrustPolicy: no-downgradetopnpm-workspace.yaml, enforcing a 7-day vetting window and blocking non-standard dependency resolution.
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 one of the fastest-growing categories of security incidents in the npm ecosystem, and they specifically exploit the trust that package managers place in newly published versions. The missing minimumReleaseAge setting in this project's pnpm-workspace.yaml was a quiet but meaningful gap — one that required zero lines of malicious code to exploit, just a lucky timing window.
The fix is elegantly simple: three lines of YAML that enforce a 7-day waiting period, block exotic dependency resolution, and prevent trust downgrades. For a Node.js library with downstream consumers, this configuration change delivers outsized security value relative to its implementation cost.
The broader lesson for developers: your package manager configuration files are security policy documents. Treat them with the same scrutiny you'd apply to authentication middleware or input validation logic. A missing setting in pnpm-workspace.yaml can be just as dangerous as a missing bounds check in application code.