Automated Dependency Updates with Supply Chain Security
How to set up Renovate or Dependabot with cooldown policies that complement pnpm's minimumReleaseAge — automated patches without opening the door to compromised packages.
The previous post covered static supply chain defenses — tool pinning, pnpm workspace settings, CI hardening. But one gap remained: dependency updates. I was updating packages manually, which meant either falling behind on security patches or adopting new releases the moment they shipped. Neither worked.
Automated dependency bots solve the velocity problem. Without cooldown policies, they can become the delivery mechanism for the next supply chain attack.
The Problem
In September 2025, the Shai-Hulud attack compromised over 500 npm packages in a coordinated campaign. Malicious versions were published, and projects with automated updates that had no cooldown window adopted them within minutes. The attack was discovered and the malicious versions removed within hours — but for projects using auto-merge, the damage was already done.
Update automation needs a cooling-off period. Most compromised packages are discovered and removed from the registry within 24 hours. A short delay between publication and adoption catches the majority of attacks without meaningfully slowing down legitimate updates.
How Cooldowns Work
There are three layers of cooldown in a typical JavaScript project:
| Layer | Tool | Coverage | What it does |
|---|---|---|---|
| Package manager | pnpm minimumReleaseAge | Full tree (transitive too) | Blocks install of packages published less than N minutes ago |
| Update bot | Renovate minimumReleaseAge or Dependabot cooldown | Direct dependencies only | Delays PR creation until a package version is N days old |
| CI gate | StepSecurity npm Package Cooldown | PR-level | Blocks merge if a PR introduces a version published within N days |
The package manager setting catches transitive dependencies that the update bot doesn’t manage. The CI gate catches manual pnpm add commands that bypass the bot entirely.
Renovate Configuration
Renovate’s cooldown is configured with minimumReleaseAge:
{
$schema: "https://docs.renovatebot.com/renovate-schema.json",
extends: ["config:recommended"],
minimumReleaseAge: "3 days",
minimumReleaseAgeBehaviour: "timestamp-required",
}
Renovate will not create a PR for any package version published less than 3 days ago. If the latest release is too fresh, it picks the most recent version that satisfies the age requirement.
For security-specific updates, I can create a package rule that bypasses the cooldown:
{
packageRules: [
{
matchUpdateTypes: ["pin", "digest"],
minimumReleaseAge: "0 days",
description: "Allow immediate pin and digest updates",
},
],
}
Renovate’s minimumReleaseAge only applies to direct dependencies. Transitive dependencies — the ones pulled in by your dependencies — are not covered. That’s where pnpm’s minimumReleaseAge fills the gap, since it applies to the full dependency tree during resolution.
Dependabot Configuration
Dependabot’s equivalent is the cooldown option:
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
cooldown:
default-days: 3
semver-major-days: 30
semver-minor-days: 7
semver-patch-days: 3
The semver-specific overrides are worth setting — major version bumps warrant a longer cooling-off period since they’re more likely to introduce breaking changes or unexpected behavior.
Dependabot’s cooldown also only applies to direct dependencies. Like Renovate, it complements pnpm’s minimumReleaseAge rather than replacing it.
The Gap: Transitive Dependencies
Neither Renovate nor Dependabot manages transitive dependencies. If a package you depend on adds a new transitive dependency that was published minutes ago, neither bot will catch it. pnpm’s minimumReleaseAge does — it applies during resolution, regardless of whether the dependency is direct or transitive.
The two layers need each other:
- Renovate/Dependabot → creates PRs, manages direct dependency updates, respects its own cooldown
- pnpm
minimumReleaseAge→ catches transitive dependencies, applies at install time, covers the full tree
Putting It Together
A minimal setup that covers all three layers:
- pnpm-workspace.yaml —
minimumReleaseAge: 1440(covers full tree) - Renovate or Dependabot — cooldown of 3+ days (manages direct deps)
- CI step —
pnpm audit --prod --audit-level high(catches known vulnerabilities)
The cooldown values should be long enough for the community to discover attacks (24+ hours) but short enough that security patches don’t sit in limbo for weeks. I started with 3 days and adjusted from there.
What I’d Do Differently
I’d set up Renovate earlier. The manual update workflow meant I was either behind on patches or anxiously watching for reports of compromised packages after every pnpm update. The cooldown setting removes that tension — updates happen at a pace where the community has time to catch problems.
The one tradeoff: security patches for transitive dependencies still depend on pnpm’s minimumReleaseAge alone. There’s no bot creating PRs for those. A pnpm audit step in CI is the safety net.
What’s Next
Automated updates handle the “get patches faster” problem. But the config that supports them — exclusion lists, allowlists, trust policies — can become stale as the dependency tree evolves. The next post covers keeping supply chain exclusions honest.
References
- Renovate
minimumReleaseAge— cooldown for Renovate-managed updates - Dependabot
cooldown— cooldown for Dependabot-managed updates - pnpm
minimumReleaseAge— full-tree cooldown at install time - pnpm
minimumReleaseAgeExclude— exceptions for packages that need immediate updates - StepSecurity npm Package Cooldown — CI-level merge gate
- GitGuardian: Renovate & Dependabot supply chain analysis — attack case studies
- Shai-Hulud attack overview — coordinated npm compromise campaign