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)

Prevention & Best Practices

1. Always Set minimumReleaseAge Explicitly

Don't rely on pnpm's default. Pin the value in pnpm-workspace.yaml:

minimumReleaseAge: 10080  # 7 days minimum

2. Understand Your Dependency Boundaries

Map out which security controls apply to which dependency types:

Dependency Type Dependabot minimumReleaseAge Manual Review
Direct
Transitive

3. Use Semgrep for Policy Enforcement

Add Semgrep's supply chain rules to your CI:

# .semgrep.yml
rules:
  - id: pnpm-minimum-release-age
    patterns:
      - pattern: minimumReleaseAge
    message: Ensure minimumReleaseAge is at least 10080 minutes

4. Monitor Transitive Dependencies

Use tools that track transitive dependency changes:

# Generate a full dependency tree
pnpm list --depth=Infinity

# Check for recently added packages
pnpm audit

5. Layer Your Supply Chain Defenses

No single control is sufficient. Combine:

  • minimumReleaseAge: Blocks newly-published packages
  • Dependabot: Proposes updates for direct dependencies
  • npm audit: Scans for known vulnerabilities
  • Semgrep: Enforces configuration policies
  • SBOM generation: Tracks all dependencies for compliance

6. Apply Settings Consistently

The fix notes that the 10080-minute setting was "applied across all four Apple MCP repos so the value cannot drift." This prevents configuration drift where different repositories in the same organization have different security postures.

7. Document Your Reasoning

The detailed comments in the fixed pnpm-workspace.yaml serve as inline documentation for future maintainers. Always explain:
- Why you chose a specific value
- What attacks it prevents
- How it relates to other security controls
- What trade-offs it involves

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.

References

Frequently Asked Questions

What is a supply chain timing attack in package managers?

A supply chain timing attack exploits the window between when a malicious package is published and when security tools detect it. Attackers publish compromised packages and hope they'll be installed before detection. The minimumReleaseAge setting in pnpm creates a mandatory waiting period that gives the security community time to identify threats.

How do you prevent supply chain timing attacks in pnpm?

Set minimumReleaseAge to at least 10080 minutes (7 days) in pnpm-workspace.yaml. This forces pnpm to reject any package version published less than 7 days ago, giving security researchers time to discover and report malicious packages before they reach your lockfile.

What CWE is supply chain timing vulnerability?

CWE-1104 (Use of Unmaintained Third Party Components) covers supply chain risks including insufficient vetting of dependencies. While CWE-1104 traditionally focuses on outdated packages, the principle extends to newly-published packages that haven't been adequately reviewed by the security community.

Is Dependabot's cooldown enough to prevent supply chain timing attacks?

No. Dependabot's cooldown only governs what Dependabot proposes for direct dependencies. Transitive dependencies bypass Dependabot entirely and reach your lockfile without review. The apple-mail-mcp repository proved this: a 5-day-old transitive package (ip-address@10.5.0) was installed despite Dependabot's 7-day cooldown being fully active.

Can static analysis detect supply chain timing vulnerabilities?

Yes. Semgrep's package_managers.pnpm.pnpm-missing-minimum-release-age rule detects missing or insufficient minimumReleaseAge settings. The rule flagged pnpm-workspace.yaml:18 in this repository, identifying that the 1440-minute default was inadequate for production supply chain security.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #174

Related Articles

high

How Supply Chain Risk from Missing Package Age Validation Happens in pnpm and How to Fix It

A pnpm workspace configuration was missing the `minimumReleaseAge` setting, creating a supply chain vulnerability where newly published (and potentially malicious) packages could be installed immediately. By adding a 10,080-minute (7-day) minimum release age to `pnpm-workspace.yaml`, the project now enforces a critical delay that allows the security community time to identify and report malicious or unstable packages before they reach production environments.

high

How run-shell-injection happens in GitHub Actions and how to fix it

A high-severity shell injection vulnerability was discovered in `action.yml` at line 68, where GitHub Actions `${{ inputs.* }}` expressions were directly interpolated into `run:` shell scripts. An attacker who controls input values (like a URL or app name) could inject arbitrary shell commands into the CI runner, potentially stealing secrets and source code. The fix replaces all direct interpolations with intermediate environment variables, properly quoted to prevent injection.

medium

How GitHub Actions Mutable Action Tags Enable Supply-Chain Attacks and How to Fix Them

A GitHub Actions workflow was using `actions/checkout@v1`, a mutable tag reference that could be silently repointed by the action owner to inject malicious code. This supply-chain vulnerability was fixed by pinning the action to a specific commit SHA (`11bd71901bbe5b1630ceea73d27597364c9af683`), ensuring the workflow always executes verified, immutable code.

high

How package_managers.pnpm.pnpm-missing-minimum-release-age.pnpm-minimum-release-age happens in pnpm and how to fix it

A pnpm workspace configuration in `site-astro/pnpm-workspace.yaml` was missing critical supply chain security settings including `minimumReleaseAge`, `trustPolicy`, and `blockExoticSubdeps`. Without these protections, the project could install freshly published malicious packages within minutes of their release. The fix adds a 7-day quarantine period, downgrade protection, and exotic subdependency blocking.

high

How Path Traversal and Security Policy Bypass Happens in Node.js Dependencies and How to Fix It

A high-severity vulnerability in the fast-uri package (CVE-2026-6321) allowed attackers to bypass security policies through improper Unicode hostname canonicalization and path traversal. This issue affected the @apralabs/apra-fleet project through its dependency tree, and was resolved by upgrading fast-uri from version 3.1.0 to 4.1.2 using npm overrides.

critical

How Insufficient Input Validation happens in TypeScript and how to fix it

A critical input validation vulnerability was discovered in `src/mcp/presets/commerce/inputs.ts` where the `normalizeCommerceAccountInput` function accepted loosely-typed `Record<string, any>` arguments without verifying field types or object structure. This allowed attackers to inject malicious payloads through MCP tool invocations. The fix adds explicit type guards and structural validation to ensure only properly-typed string values reach downstream consumers.