Back to Blog
critical SEVERITY7 min read

How Supply Chain Attacks Happen via pnpm Workspace Configuration and How to Fix Them

A pnpm workspace configuration was missing the `minimumReleaseAge` setting, leaving the project vulnerable to supply chain attacks from newly published malicious or compromised npm packages. By adding `minimumReleaseAge: 10080` (seven days in minutes), the fix ensures that only packages that have survived community scrutiny for at least a week are resolved during installation. This defensive hardening is especially critical for web applications where a compromised dependency could introduce XSS,

O
By Orbis AppSec
Published September 2, 2026Reviewed September 2, 2026

Answer Summary

This vulnerability is a supply chain hardening gap (CWE-829) in a pnpm workspace configuration where no `minimumReleaseAge` was set, allowing immediate resolution of newly published—and potentially malicious—npm packages. The fix adds `minimumReleaseAge: 10080` to `pnpm-workspace.yaml`, enforcing a seven-day quarantine period before any newly published package version can be installed. This is a defensive measure available since pnpm v10.16.0 that mitigates risks from typosquatting, account takeovers, and malicious package publications in the npm ecosystem.

Vulnerability at a Glance

cweCWE-829 (Inclusion of Functionality from Untrusted Control Sphere)
fixAdded `minimumReleaseAge: 10080` to enforce a 7-day waiting period before resolving new package versions
riskMalicious or unstable newly published packages can be immediately installed into the project
languageYAML (pnpm configuration)
root causepnpm-workspace.yaml lacked the `minimumReleaseAge` setting, defaulting to zero quarantine
vulnerabilityMissing supply chain protection (no minimum release age for package resolution)

Introduction

In this project's pnpm-workspace.yaml, we discovered a high-severity configuration gap: the workspace had no minimumReleaseAge setting, meaning that every time a developer ran pnpm install, the resolver could pull in npm packages published just seconds earlier. In a web application where XSS and injection vulnerabilities can directly affect end users, a single compromised dependency is all it takes for an attacker to inject malicious code into production.

The vulnerable configuration file was deceptively simple—just three lines defining workspace packages:

packages:
  - "."
  - "packages/*"

No guardrails. No quarantine. No time buffer between a package being published and your project consuming it. This is the exact window that supply chain attackers exploit.

This matters because the broader project includes sensitive components: an admin route handler at website/server/src/routes/admin.ts, OAuth token storage in plugins/auth-oauth2/src/store.ts, and a Rust/Tauri backend. A compromised dependency anywhere in this workspace could access tokens, inject scripts into the admin interface, or exfiltrate credentials—all before anyone notices the malicious package on npm.

The Vulnerability Explained

What Is minimumReleaseAge and Why Does It Matter?

Starting with pnpm v10.16.0, the minimumReleaseAge setting tells pnpm's dependency resolver to ignore package versions that were published less than N minutes ago. Without it, the default is effectively zero—any version published at any time is fair game.

Here's the vulnerable pnpm-workspace.yaml in its entirety:

packages:
  - "."
  - "packages/*"

That's it. No security settings whatsoever.

The Attack Scenario

Consider this realistic attack chain against this specific project:

  1. Reconnaissance: An attacker identifies that this workspace depends on a popular utility package (say, a markdown parser used by the website).

  2. Typosquatting or account takeover: The attacker publishes a malicious version of a dependency—either through a typosquatted package name or by compromising a maintainer's npm account. The malicious version includes a postinstall script that:
    - Reads OAuth tokens from plugins/auth-oauth2/src/store.ts's plaintext storage
    - Exfiltrates data from the admin routes defined in website/server/src/routes/admin.ts
    - Installs a persistent backdoor in the Tauri application

  3. Immediate resolution: A developer on the team runs pnpm install or pnpm update. Because there's no minimumReleaseAge, pnpm immediately resolves the malicious version.

  4. Compromise: The malicious code executes during installation or at runtime. Since this is a web application, the attacker could inject XSS payloads that affect every end user.

The critical window here is the time between publication and detection. Most malicious npm packages are caught and removed within hours to days. The minimumReleaseAge setting turns this detection window into a defense window.

Real-World Precedent

This isn't theoretical. The npm ecosystem has seen numerous supply chain attacks:

  • event-stream (2018): A maintainer handed off a popular package to an attacker who injected cryptocurrency-stealing code.
  • ua-parser-js (2021): A hijacked package with 8 million weekly downloads distributed cryptominers.
  • colors and faker (2022): A maintainer sabotaged their own widely-used packages.

In each case, a time-based quarantine would have given the community time to detect and flag the compromise before most projects consumed it.

The Fix

The fix is a single, surgical addition to pnpm-workspace.yaml:

Before (Vulnerable)

packages:
  - "."
  - "packages/*"

After (Hardened)

packages:
  - "."
  - "packages/*"

# Wait seven days before resolving a newly published version.
minimumReleaseAge: 10080

What Changed and Why

The value 10080 represents 10,080 minutes, which equals exactly seven days. This means:

  • When pnpm install resolves dependencies, any package version published less than 7 days ago is invisible to the resolver.
  • If a malicious package is published on Monday, your project won't even consider it until the following Monday—by which time it will almost certainly have been detected, reported, and removed from the npm registry.
  • The comment # Wait seven days before resolving a newly published version. makes the intent clear to every developer who reads the configuration.

Why Seven Days?

Seven days strikes a balance between security and practicality:

  • Too short (e.g., 1 day): Many malicious packages survive 24 hours before detection.
  • Too long (e.g., 30 days): Legitimate security patches would be delayed, potentially leaving known vulnerabilities unpatched.
  • Seven days: Aligns with the typical detection and response cycle for malicious npm packages, while still allowing timely access to legitimate updates.

Behavior Preservation

This change is purely additive and defensive. It does not:

  • Break any existing dependency resolution for already-published packages
  • Affect packages already in the lockfile
  • Change the workspace package structure
  • Require any code changes in the application

It only prevents the resolution of brand-new package versions that haven't had time to be vetted by the community.

Prevention & Best Practices

1. Always Set minimumReleaseAge in pnpm Workspaces

For any pnpm v10.16.0+ project, add this to your pnpm-workspace.yaml:

minimumReleaseAge: 10080

2. Layer Your Supply Chain Defenses

No single measure is sufficient. Combine:

Defense What It Protects Against
minimumReleaseAge Newly published malicious packages
Lockfiles (pnpm-lock.yaml) Unexpected version changes
npm audit / pnpm audit Known vulnerabilities in dependencies
Semgrep config rules Missing security settings
Dependency review (GitHub) Malicious code in PRs
Package signature verification Tampered packages

3. Scan Configuration Files, Not Just Code

Traditional SAST tools focus on source code. But as this vulnerability shows, configuration files are attack surface too. Use tools like Semgrep with rules that cover package manager configurations:

semgrep --config "p/supply-chain" pnpm-workspace.yaml

4. Monitor for New pnpm Security Features

pnpm is actively adding supply chain protections. Stay current with:
- pnpm Settings Documentation
- pnpm Release Notes

5. Apply Defense in Depth for Sensitive Components

This project stores OAuth tokens in plaintext (plugins/auth-oauth2/src/store.ts) and has admin routes (website/server/src/routes/admin.ts). A compromised dependency could target these directly. Beyond minimumReleaseAge, consider:
- Encrypting stored credentials (the project already has PBKDF2 available in Rust dependencies)
- Applying least-privilege principles to admin routes
- Sandboxing dependency installation scripts

Key Takeaways

  • pnpm-workspace.yaml without minimumReleaseAge is an exploit primitive: It allows attackers to have their malicious packages immediately consumed by your project, with zero quarantine period.
  • Configuration files are security-critical attack surface: This vulnerability wasn't in application code—it was in a 3-line YAML file that controls how every dependency in the workspace is resolved.
  • Seven days (10080 minutes) is the recommended quarantine threshold: It balances security (time for community detection of malicious packages) with practicality (timely access to legitimate updates).
  • Supply chain hardening is especially critical for web applications: This project's admin routes and OAuth token handling mean a compromised dependency could directly impact end users through XSS, credential theft, or backdoors.
  • Semgrep's pnpm-minimum-release-age rule catches this automatically: Static analysis isn't just for code—it can enforce security invariants in configuration files too.

How Orbis AppSec Detected This

  • Source: The npm package registry, where newly published (and potentially malicious) package versions are available for immediate resolution by pnpm.
  • Sink: The pnpm-workspace.yaml configuration at line 1, which controls dependency resolution for the entire workspace including sensitive components like website/server/src/routes/admin.ts and plugins/auth-oauth2/src/store.ts.
  • Missing control: No minimumReleaseAge setting was configured, meaning pnpm's resolver had no time-based quarantine for newly published package versions.
  • CWE: CWE-829 — Inclusion of Functionality from Untrusted Control Sphere.
  • Fix: Added minimumReleaseAge: 10080 to pnpm-workspace.yaml, enforcing a mandatory 7-day waiting period before any newly published package version can be resolved during installation.

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 security isn't glamorous, but it's foundational. A missing three-line configuration in pnpm-workspace.yaml left this entire workspace—admin routes, OAuth tokens, Tauri backend, and all—exposed to any malicious package published to npm. The fix was trivially simple: minimumReleaseAge: 10080. But the protection it provides is profound: a seven-day buffer that transforms the community's detection capability into your project's defense.

The lesson is clear: audit your configuration files with the same rigor you apply to your code. Tools like Semgrep and Orbis AppSec can catch these gaps automatically, but every developer should understand that pnpm-workspace.yaml, package.json, and similar files are part of your security boundary—not just your build system.

References

Frequently Asked Questions

What is a supply chain attack via package managers?

A supply chain attack via package managers occurs when an attacker publishes a malicious package (or compromises an existing one) on a registry like npm, and downstream projects automatically install it during dependency resolution. Without safeguards like minimum release age, projects can pull in compromised code within minutes of publication.

How do you prevent supply chain attacks in pnpm?

In pnpm v10.16.0+, you can set `minimumReleaseAge: 10080` in `pnpm-workspace.yaml` to enforce a 7-day quarantine on newly published package versions. This gives the community time to detect and report malicious packages before your project resolves them. Additionally, use lockfiles, enable package signature verification, and audit dependencies regularly.

What CWE is supply chain inclusion from untrusted sources?

CWE-829: Inclusion of Functionality from Untrusted Control Sphere. This CWE covers scenarios where software includes code or functionality from a source that is not sufficiently trusted, which directly applies to automatically resolving newly published, unvetted npm packages.

Is using a lockfile enough to prevent supply chain attacks?

No. A lockfile only pins versions at the time of resolution. When you run `pnpm install` to add or update dependencies, new malicious versions can still be resolved. The `minimumReleaseAge` setting adds a time-based quarantine that complements lockfiles by ensuring only packages that have existed for a minimum period are considered.

Can static analysis detect missing supply chain protections?

Yes. Tools like Semgrep have rules specifically for package manager configurations, such as `package_managers.pnpm.pnpm-missing-minimum-release-age.pnpm-minimum-release-age`, which flags when `minimumReleaseAge` is not set in pnpm workspace files. These rules catch configuration-level security gaps that code-level analysis would miss.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #44

Related Articles

critical

How Server-Side Request Forgery happens in Python FastAPI and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in app.py where the `/parse` and `/parse-video` endpoints accepted user-supplied URLs with only substring validation. The application checked if 'doubao.com' appeared anywhere in the URL string, allowing attackers to bypass this check and access internal services, cloud metadata endpoints, or scan the internal network. The fix implemented proper hostname parsing with an allowlist of legitimate domains.

critical

How a vulnerable websocket-driver dependency happens in Node.js lockfiles and how to fix it

A Trivy scan flagged `websocket-driver@0.7.4` in this repository's `bun.lock` as affected by CVE-2026-54466, a critical issue in a WebSocket protocol handler that parses untrusted HTTP upgrade requests and frame data. The fix upgrades the package to `0.7.5` and adds an explicit `websocket-driver` entry to the lockfile's override block so every transitive consumer — webpack-dev-server, sockjs, faye-websocket — resolves to the patched build instead of the pinned vulnerable one.

high

How Dependabot Missing Cooldown Periods Enable Supply Chain Attacks and How to Fix It

A critical security vulnerability in `.github/dependabot.yml` was exposing a Node.js library to supply chain attacks by automatically updating to newly published packages without a safety delay. By adding a 7-day cooldown period to each package ecosystem configuration, the project now protects against malicious or unstable package versions that could affect downstream consumers.

high

How Exponential-Time Complexity Causes Denial of Service in brace-expansion and How to Fix It

A critical vulnerability in brace-expansion versions 1.1.13 and earlier allowed attackers to cause denial of service through crafted brace pattern inputs. The fix upgrades to patched versions 1.1.16, 2.1.2, and 5.0.7, eliminating the exponential-time complexity that made exploitation possible.

high

How unrestricted file upload via extension-only validation happens in Deno/JavaScript and how to fix it

The review image upload handler in this Deno-based app trusted the client-supplied filename extension to decide whether a file was a "safe" image, without ever inspecting the actual file bytes. The fix adds magic-byte signature verification for PNG, JPEG, GIF, and WEBP formats before the file is written to disk, closing the door on disguised executables and malicious payloads.

critical

How Missing Authentication on DELETE Endpoints Happens in Node.js Express and How to Fix It

A critical authentication bypass vulnerability was discovered in the skill-cabinet server where the DELETE /api/skills/:id endpoint allowed any unauthenticated user to delete arbitrary skills from the filesystem. The fix implements loopback origin validation to ensure only requests from localhost can perform destructive operations, while also consolidating delete functionality into a single, protected endpoint.