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, 2026Reviewed 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

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.

critical

How Remote Code Execution Happens in Handlebars Template Compilation and How to Fix It

CVE-2026-33937 is a critical remote code execution vulnerability in Handlebars.js that allows attackers to execute arbitrary code by passing maliciously crafted Abstract Syntax Tree (AST) objects to the compile() function. The vulnerability was patched in version 4.7.9, and we've upgraded to protect against this threat vector.