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:
- Download and analyze the new version
- Compare it against previous versions
- Identify suspicious code patterns
- Report findings to security databases
- 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:
- npm Security Advisory Timeline: npm typically publishes security advisories 3-5 days after a vulnerability is reported
- Snyk Database Updates: Snyk and similar tools index new packages within 2-3 days
- Community Review: Popular packages get community scrutiny within the first week
- 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.0was 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: 1440configuration inpnpm-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.