The last two weeks of March 2026 were brutal for software supply chains. A cascading campaign by a threat actor tracked as TeamPCP started with a compromised credential in Trivy (Aqua Security’s vulnerability scanner), which was then used to hijack Checkmarx KICS and then LiteLLM (an LLM proxy with ~97M monthly PyPI downloads). On top of that, axios – the most popular HTTP client on npm with ~100M weekly downloads – was compromised through a stolen maintainer token, with Google’s Threat Intelligence Group attributing the attack to North Korea’s UNC1069.
In both cases, the malicious versions were live on the registry for only 2-3 hours before being pulled. That’s a tiny window – but if your CI happened to run npm install or pip install during those hours, you’d have pulled a RAT dropper into your build.
These attacks are genuinely pretty scary. One simple defense tactic could be summarized as do not install a package version until it has been on the registry at least for some amount of time.
William Woodruff’s analysis found that of 10 recent supply chain attacks examined, 8 had exploitation windows under one week. A 7-day cooldown would have blocked most of them. Andrew Nesbitt’s “Package Managers Need to Cool Down” provides the most comprehensive survey of the landscape.
The good news is that nearly every major package manager now supports this natively.
Python#
uv (since v0.9.17, Dec 2025) has the most ergonomic implementation – it accepts relative durations like "7 days", absolute timestamps, and even per-package overrides via exclude-newer-package:
# ~/.config/uv/uv.toml
exclude-newer = "7 days"pip (since v26.0, Jan 2026) supports cooldowns via uploaded-prior-to in pip.conf, but only accepts absolute timestamps. You have to keep updating the value manually, or use Seth Larson’s crontab workaround to automate it:
# pip.conf (under [global] or [install])
uploaded-prior-to = 2026-03-24T00:00:00ZPoetry and Conda have open proposals but nothing shipped yet.
JavaScript#
All five major JS package managers now support this, though they each picked a different unit – a running joke in the HN thread.
npm (since v11.10.0, Feb 2026) – in days:
# ~/.npmrc
min-release-age=7
ignore-scripts=trueThe ignore-scripts=true is worth adding too – the axios attack relied on a postinstall script to execute the RAT dropper, and this would have independently blocked it regardless of cooldown.
pnpm (since v10.16, Sep 2025) – in minutes, with an exclude list for packages you trust:
# ~/Library/Preferences/pnpm/rc (or ~/.npmrc for pnpm)
minimum-release-age=10080Bun (since v1.3, Oct 2025) – in seconds, with an exclude list:
# ~/.bunfig.toml
[install]
minimumReleaseAge = 604800Yarn (since v4.10.0, Sep 2025) – also minutes, but with a completely different setting name:
# .yarnrc.yml
npmMinimalAgeGate: 10080Deno (since v2.6) – human-readable durations:
// deno.json
{
"install": {
"minimumDependencyAge": "7d"
}
}Dependency update bots#
If you use Renovate or Dependabot to automate dependency updates, they have their own cooldown settings that work across ecosystems.
Renovate has long supported minimumReleaseAge with human-readable durations. Mend’s security:minimumReleaseAgeNpm preset enforces a 3-day minimum for npm by default:
// renovate.json
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"minimumReleaseAge": "7 days"
}Dependabot (GA since July 2025) has a cooldown block with semver-level overrides – security updates bypass cooldown entirely:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
cooldown:
default-days: 7
semver-major-days: 30
semver-minor-days: 7
semver-patch-days: 3Snyk applies a non-configurable 21-day cooldown by default.
Other ecosystems#
- Ruby: gem.coop implements a registry-level 48-hour delay on newly published gems – no client-side changes needed.
- Rust/Cargo: crates.io now publishes
created_attimestamps in the index (v1.94). There’s no built-in config-file cooldown yet, but the third-partycargo-cooldownwrapper enforces one. Cargo’s approach leans toward explicit opt-in viacargo update --precise. - Go, Composer, NuGet: Open proposals (Go #76485, Composer #12633, NuGet #14657) but nothing shipped.
What cooldowns don’t cover#
The Trivy attack worked differently – it force-pushed Git tags to redirect trusted references to malicious commits, exploiting Git’s mutable tag system. Package manager cooldowns wouldn’t have helped there. The defense for that vector is pinning GitHub Actions to full commit SHAs rather than mutable tags:
# Bad: tag can be force-pushed
- uses: aquasecurity/trivy-action@v0.69.4
# Good: immutable reference
- uses: aquasecurity/trivy-action@a3e1b43e...Cooldowns also won’t help against a patient attacker who publishes a clean version, waits out the cooldown, and then inserts malicious code in a later update. But the counter-argument is compelling: security scanners like Socket and StepSecurity are actively monitoring new publishes and tend to flag malicious packages within hours. The cooldown buys time for that “herd immunity” to kick in.
Further reading#
- HN discussion: “PSA: npm/bun/pnpm/uv now all support setting a minimum release age”
- William Woodruff: “We should all be using dependency cooldowns”
- Andrew Nesbitt: “Package Managers Need to Cool Down”
- Socket: “Axios npm Package Compromised”
- StepSecurity: “Axios Compromised on npm”
- Wiz: “TeamPCP Trojanizes LiteLLM”
- CrowdStrike: “From Scanner to Stealer – Inside the Trivy Action Compromise”
- Arctic Wolf: “TeamPCP Supply Chain Attack Campaign”