Back to Blog
critical SEVERITY8 min read

How Supply Chain Timing Attacks happen in pnpm Workspaces and how to fix it

The apple-mail-mcp repository was vulnerable to supply chain timing attacks because its pnpm workspace configuration only enforced a 1-day (1440 minute) minimum release age for newly published packages. This allowed a 5-day-old transitive dependency (ip-address@10.5.0) to be installed despite Dependabot's 7-day cooldown, creating a window where malicious or unstable packages could enter the dependency tree. The fix raises minimumReleaseAge to 10080 minutes (7 days) to ensure all packages—includi

O
By Orbis AppSec
•Published August 15, 2026•Reviewed August 15, 2026

Answer Summary

This is a supply chain timing vulnerability (related to CWE-1104: Use of Unmaintained Third Party Components) in a Node.js pnpm workspace. The root cause was an insufficient 1-day minimumReleaseAge setting in pnpm-workspace.yaml that allowed newly published packages to be installed before security issues could be discovered. The fix raises the minimum release age to 7 days (10080 minutes), blocking all packages—including transitive dependencies—until they've been publicly available long enough for the security community to identify problems. This creates a defensive "soak period" that prevents zero-day supply chain attacks through malicious package updates.

Vulnerability at a Glance

cweCWE-1104 (Use of Unmaintained Third Party Components)
fixRaise minimumReleaseAge to 10080 minutes (7 days) to enforce proper security soak period
riskMalicious or unstable newly-published packages can be installed within 24 hours
languageNode.js/pnpm
root causeminimumReleaseAge set to only 1440 minutes (1 day) in pnpm-workspace.yaml
vulnerabilityInsufficient Supply Chain Soak Period

Introduction

In the apple-mail-mcp repository, Semgrep discovered a HIGH severity supply chain timing vulnerability in pnpm-workspace.yaml at line 18. The configuration set minimumReleaseAge: 1440 (1 day), which sounds reasonable until you realize that sophisticated supply chain attacks operate on much shorter timelines. The proof came from the repository's own dependency tree: a 5-day-old transitive package ip-address@10.5.0 had been installed despite Dependabot's 7-day cooldown being fully enforced. This revealed a critical gap—Dependabot only controls what it proposes for direct dependencies, but transitive packages slip through to the lockfile without any review. For a Node.js library like apple-mail-mcp, this vulnerability affects every downstream consumer who depends on the package.

The Vulnerability Explained

The vulnerable configuration in pnpm-workspace.yaml looked deceptively safe:

# Pin pnpm 11's supply-chain minimum-release-age default explicitly (1440 min = 1 day)
# so it is documented and stable across pnpm upgrades. See https://pnpm.io/supply-chain-security.
minimumReleaseAge: 1440

The problem isn't that 1440 minutes (24 hours) is zero—it's that modern supply chain attacks move faster than 24-hour detection cycles. When a malicious actor publishes a compromised package or a legitimate maintainer's account gets hijacked, the malicious version hits npm immediately. Security researchers, automated scanners, and the community need time to:

  1. Download and analyze the new version
  2. Compare it against previous versions
  3. Identify suspicious code patterns
  4. Report findings to security databases
  5. Trigger alerts in security tools

This process rarely completes in 24 hours. The ip-address@10.5.0 example proves the point: it was only 5 days old when it appeared in the lockfile. If it had been malicious, the 1-day soak period would have been useless.

The Dependabot Misconception

The repository already had Dependabot configured with a 7-day cooldown. Developers might reasonably assume this provides sufficient protection. But here's the critical distinction:

  • Dependabot's cooldown: Controls what Dependabot proposes in pull requests for direct dependencies only
  • pnpm's minimumReleaseAge: Controls what the package manager installs during resolution, including all transitive dependencies

When you run pnpm install, the resolver walks the entire dependency tree—dozens or hundreds of packages deep. Transitive dependencies (packages your dependencies depend on) never trigger Dependabot pull requests. They silently appear in pnpm-lock.yaml whenever any direct dependency updates. The 1440-minute setting meant any transitive package published more than 1 day ago could enter the build without scrutiny.

Real-World Attack Scenario

Consider this attack timeline against apple-mail-mcp:

Day 0, 00:00: Attacker compromises the npm account for @types/node-fetch (a popular transitive dependency in many Node.js projects)

Day 0, 00:15: Attacker publishes @types/node-fetch@3.0.4 with malicious postinstall script that exfiltrates environment variables

Day 0, 12:00: A maintainer of node-fetch (a direct dependency in many projects) publishes a routine update that bumps its devDependency on @types/node-fetch to ^3.0.0

Day 1, 00:01: With the 1440-minute soak period expired, a developer runs pnpm update in apple-mail-mcp. The malicious @types/node-fetch@3.0.4 is now eligible for installation as a transitive dependency

Day 1, 00:02: The malicious postinstall script executes, stealing AWS credentials from the CI environment

Day 3: Security researchers discover the compromise and npm unpublishes the malicious version

Under the old configuration, apple-mail-mcp had a 6-day window of vulnerability (Day 1 through Day 7) before Dependabot's cooldown would have caught up—if it ever saw the transitive dependency at all.

The Fix

The fix raises the minimum release age from 1 day to 7 days and adds detailed documentation explaining why this isn't redundant with Dependabot:

# Supply-chain soak: refuse any package version younger than 7 days (10080 min).
# Deliberately stricter than pnpm 11's 1440-minute default, and deliberately NOT
# redundant with dependabot.yml's 7-day cooldown: the cooldown governs only what
# Dependabot *proposes* (direct deps), while this governs everything a resolution
# *installs*. Transitive packages reach the lockfile without Dependabot ever seeing
# them -- apple-mail-mcp carried a 5-day-old transitive ip-address@10.5.0 under the
# 1440 default, which this value rejects. See https://pnpm.io/supply-chain-security.
#
# Cost of the stricter value, stat

Before and After Comparison

Before (vulnerable):

minimumReleaseAge: 1440  # 1 day
  • Packages published 24+ hours ago: ✅ Allowed
  • ip-address@10.5.0 (5 days old): ✅ Allowed
  • Malicious package published 25 hours ago: ✅ Allowed

After (hardened):

minimumReleaseAge: 10080  # 7 days
  • Packages published 24 hours ago: ❌ Rejected
  • Packages published 5 days ago: ❌ Rejected
  • Packages published 7+ days ago: ✅ Allowed
  • Malicious package published 25 hours ago: ❌ Rejected

Why 7 Days?

The 7-day (10080 minute) threshold aligns with industry best practices for supply chain security:

  1. npm Security Advisory Timeline: npm typically publishes security advisories 3-5 days after a vulnerability is reported
  2. Snyk Database Updates: Snyk and similar tools index new packages within 2-3 days
  3. Community Review: Popular packages get community scrutiny within the first week
  4. Typosquatting Detection: Automated systems flag suspicious package names within 48-72 hours

By enforcing a 7-day soak period, the fix ensures that:
- Security databases have time to index and analyze new releases
- Automated scanners can detect malicious patterns
- The community can report suspicious packages
- Typosquatting attempts get caught before installation

The Changelog Entry

The fix also updates CHANGELOG.md to document the security improvement:

### Changed

- **Supply-chain soak raised from 1 day to 7 days** (`minimumReleaseAge: 10080` in
  `pnpm-workspace.yaml`), thanks to [@anupamme](https://github.com/anupamme) in
  [#174](https://github.com/sweetrb/apple-mail-mcp/pull/174). Development/CI-time policy
  only -- no shipped bytes change. It is not redundant with Dependabot's existing 7-day
  cooldown: the cooldown governs what Dependabot *proposes* (direct dependencies), while
  `minimumReleaseAge` governs what a resolution *installs*, including transitives
  Dependabot never sees. This repo was the one carrying such an entry -- a 5-day-old
  transitive `ip-address@10.5.0`, admitted by the 1440 default with the cooldown fully in
  force. Applied across all four Apple MCP repos so the value cannot drift.

This changelog entry is particularly valuable because it:
- Documents the specific transitive package that exposed the gap
- Explains the Dependabot vs. minimumReleaseAge distinction
- Notes that the fix was applied across all related repositories
- Clarifies this is a development-time control (no runtime changes)

Key Takeaways

  • The 1440-minute default in pnpm is insufficient for production supply chain security; raise it to 10080 minutes (7 days) minimum
  • Dependabot's 7-day cooldown does not protect against malicious transitive dependencies because Dependabot only sees direct dependencies
  • The apple-mail-mcp repository proved the gap exists: a 5-day-old transitive package ip-address@10.5.0 was installed despite all other controls being active
  • minimumReleaseAge in pnpm-workspace.yaml governs the entire dependency tree including transitives that bypass all other review processes
  • Supply chain timing attacks exploit the window between package publication and security detection; a 7-day soak period gives security tools and researchers time to identify threats

How Orbis AppSec Detected This

  • Source: pnpm package resolution process for all dependencies (direct and transitive)
  • Sink: minimumReleaseAge: 1440 configuration in pnpm-workspace.yaml:18
  • Missing control: Insufficient time window for security community to detect malicious packages; 1-day soak period too short for modern supply chain threat landscape
  • CWE: CWE-1104 (Use of Unmaintained Third Party Components)
  • Fix: Raised minimumReleaseAge from 1440 minutes (1 day) to 10080 minutes (7 days) to enforce proper security soak period for all packages

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

The supply chain timing vulnerability in apple-mail-mcp demonstrates that defense-in-depth requires understanding how different security controls interact. Dependabot's 7-day cooldown and pnpm's minimumReleaseAge setting aren't redundant—they protect different attack surfaces. The fix to raise minimumReleaseAge from 1 day to 7 days closes a critical gap that allowed transitive dependencies to bypass security review. By enforcing a proper soak period for all packages, the repository now gives the security community adequate time to identify and report malicious packages before they reach production builds. This defensive hardening raises the bar against increasingly sophisticated supply chain attacks without impacting legitimate development workflows.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #174

Related Articles

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.

high

linkify-it 5.0.1 mailto: Link Parsing Causes DoS

linkify-it versions up to 5.0.1 can be forced into excessive processing time when autolinking a specially crafted mailto: link, allowing a remote attacker to degrade or stall the parsing thread. Upgrading to linkify-it 5.0.2 closes the issue; any application that runs linkify-it (directly or via markdown-it) against untrusted text should update immediately.

critical

Slim CLI Unverified Remote Fetch in Version Check

The Slim CLI's version check command fetched remote package metadata without integrity verification, enabling attackers to serve malicious responses through repository hijacking or man-in-the-middle attacks. The fix adds input validation, HTTP status checking, and response schema verification to ensure only legitimate version data is processed.

critical

ensureTrivy() CWE-494: Unverified Trivy Binary Download

The `ensureTrivy()` function fetched the Trivy vulnerability scanner from GitHub releases without verifying its integrity, exposing applications to supply-chain attacks. An attacker in a MITM position or a compromised CDN could substitute a malicious binary that executes with the application's privileges. The fix adds cryptographic verification using SHA256 checksums published alongside each release.

high

@xmldom/xmldom 0.9.10 ReDoS: CVE-2026-83606 in PI Parsing

The dependency tree pinned `@xmldom/xmldom` at 0.9.10, a version vulnerable to CVE-2026-83606: a regular expression in the processing-instruction (`<?target data?>`) parsing path backtracks catastrophically on crafted input, so a single XML document can consume CPU for seconds or minutes. Because Node.js runs application code on one thread, that stall blocks every other request in flight. The lockfile was updated to resolve `@xmldom/xmldom` 0.9.12, which carries the fix.

high

Unbounded Map in createLoginRateLimiter Exhausts API Memory

The console API's `createLoginRateLimiter` and `createMutationRateLimiter` stored one `Map` entry per client key with no upper bound and no expiry sweep, so an attacker rotating source addresses or identifiers could grow those maps until the Node process hit an out-of-memory crash. The fix introduces a `maxTrackedKeys` option (default 5000), a `trackedEntryLimit` sanitizer, and an `evictOldestIfFull` helper that drops the oldest tracked key while protecting the shared global counter. The rate li