How Dependabot Missing Cooldown Happens in GitHub Actions and How to Fix It
Introduction
The .github/dependabot.yml file is one of the most quietly powerful security controls in a modern repository. It decides when and how your project adopts new versions of every dependency — from production npm packages to the GitHub Actions that build and deploy your code. In this Node.js library, Dependabot was configured to check for updates on a daily schedule for both the npm and github-actions ecosystems. That sounds responsible. The problem is that "daily" with no cooldown means Dependabot could open a pull request for a package version published just hours ago — before anyone in the open-source community has had a chance to inspect it for malice or instability.
The Semgrep rule package_managers.dependabot.dependabot-missing-cooldown.dependabot-missing-cooldown flagged line 3 of .github/dependabot.yml because neither package-ecosystem entry contained a cooldown block. This post explains what that means, why it matters for a Node.js library whose vulnerabilities flow downstream to every consumer, and exactly how the fix works.
The Vulnerability Explained
What the vulnerable configuration looked like
Before the fix, both ecosystem entries in .github/dependabot.yml looked structurally like this:
# BEFORE — no cooldown block
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
groups:
eslint-dependencies:
patterns:
- "eslint*"
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: "daily"
There is nothing in either entry that tells Dependabot to wait before proposing a newly published version. The moment a package maintainer — or an attacker who has compromised a maintainer's account — publishes a new version, Dependabot's next daily run will open a pull request for it.
Why this is dangerous for a Node.js library
Supply-chain attacks against npm have become a well-documented threat vector. Attackers use several techniques:
- Account takeover: Compromise a maintainer's npm credentials and publish a malicious patch release.
- Dependency confusion: Register a package on npm with the same name as a private internal package.
- Typosquatting: Publish a package whose name is one character away from a popular one and wait for automated tooling to pick it up.
In all three scenarios, the attack window is the gap between publication and community detection. Security researchers, automated scanners, and vigilant users typically need at least 24–72 hours to notice and report a malicious release. Without a cooldown, Dependabot operates entirely inside that window — it will surface the malicious version before the community has had a chance to flag it.
Because this is a Node.js library, the blast radius extends beyond this single repository. Every downstream project that depends on this library is also at risk if a compromised dependency is merged and shipped in a new release.
The specific attack scenario
- An attacker compromises the npm account of a maintainer of a package listed in the project's
eslint-dependenciesgroup (e.g.,eslint-plugin-*). - The attacker publishes
eslint-plugin-example@3.2.1containing a postinstall script that exfiltrates environment variables. - Within 24 hours, Dependabot opens a PR: "Bump eslint-plugin-example from 3.2.0 to 3.2.1".
- A developer, seeing a routine eslint plugin bump, approves and merges without deep inspection.
- The malicious postinstall script runs on every developer machine and CI runner that installs dependencies.
A 3-day cooldown means this PR would not even appear until day 3 — by which time npm's security team or community researchers would very likely have yanked the package.
The Fix
What changed
Two cooldown blocks were added — one to each package-ecosystem entry:
# AFTER — cooldown added to both ecosystems
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
cooldown: # <-- NEW
default-days: 3 # <-- NEW
groups:
eslint-dependencies:
patterns:
- "eslint*"
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: "daily"
cooldown: # <-- NEW
default-days: 3 # <-- NEW
Before vs. after
| Aspect | Before | After |
|---|---|---|
| npm updates proposed after publish | Immediately (next daily run) | After 3 days |
| github-actions updates proposed after publish | Immediately (next daily run) | After 3 days |
| Exposure window to zero-day supply-chain attack | Up to 24 hours | Reduced to near-zero for attacks detected within 3 days |
| Dependabot PR noise from unstable releases | High (yanked packages still generate PRs) | Significantly reduced |
Why 3 days?
GitHub's own documentation recommends default-days: 7 as a conservative baseline. This project chose default-days: 3 — a pragmatic middle ground that still covers the majority of supply-chain attack detection timelines while keeping the project reasonably up to date. Teams with stricter security requirements or slower review cadences should consider the full 7-day window.
The cooldown block also supports semver-patch-days and semver-minor-days keys, allowing finer-grained control (e.g., letting patch releases through faster while holding minor and major updates longer). For this project, a single default-days: 3 rule was sufficient.
Prevention & Best Practices
Always include a cooldown block in new Dependabot configurations
When creating a new dependabot.yml, treat cooldown as a required field rather than an optional one. A minimal, secure template looks like:
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
cooldown:
default-days: 7
Use Semgrep to enforce the policy in CI
The rule that caught this issue — package_managers.dependabot.dependabot-missing-cooldown.dependabot-missing-cooldown — can be run in your CI pipeline to prevent the configuration from regressing:
semgrep --config "p/default" .github/dependabot.yml
Adding this as a required check means a future edit that accidentally removes the cooldown block will be caught before it merges.
Layer your supply-chain defences
A cooldown period is one layer, not a complete defence. Combine it with:
- Lockfile verification: Commit
package-lock.jsonand verify it in CI withnpm cirather thannpm install. - Provenance attestations: Use npm's
--auditflag and check for packages with SLSA provenance. - Dependency review: Enable GitHub's Dependency Review action to block PRs that introduce known vulnerabilities.
- Pinned Actions versions: For
github-actions, pin to a full commit SHA rather than a mutable tag to prevent tag-moving attacks.
Relevant standards
- CWE-1104: Use of Unmaintained Third-Party Components — the closest CWE mapping for supply-chain configuration weaknesses.
- OWASP A06:2021 – Vulnerable and Outdated Components: Covers risks from unvetted third-party dependencies.
- SLSA Supply Chain Levels: A framework for hardening the entire software supply chain, of which dependency update hygiene is a foundational step.
Key Takeaways
- Both ecosystem entries needed fixing independently. The
npmandgithub-actionsentries in.github/dependabot.ymlare separate configurations; acooldownblock must be added to each one explicitly — there is no global default. - A daily update schedule without a cooldown is more dangerous than a weekly schedule with one. Frequency of checking is irrelevant if there is no buffer between publication and proposal.
- The
eslint-dependenciesgroup makes this especially risky. Grouping alleslint*packages means a single compromised eslint plugin could be bundled into a grouped PR that looks routine and gets less scrutiny. - Three days is a minimum, not a ceiling. For projects with slower review cycles or higher security requirements,
default-days: 7(GitHub's recommendation) is the safer choice. - This is a Node.js library — downstream consumers inherit the risk. Configuration weaknesses in a library's own toolchain can propagate malicious code to every project that depends on it.
How Orbis AppSec Detected This
- Source: The
.github/dependabot.ymlconfiguration file, specifically the twopackage-ecosystementries beginning at line 3, which control how and when Dependabot proposes dependency updates. - Sink: The absence of a
cooldownblock in either entry, meaning Dependabot's update proposals are not gated by any publication-age check before being surfaced to developers. - Missing control: No
cooldown: default-daysvalue was set for either thenpmorgithub-actionsecosystem, leaving zero buffer between package publication and PR creation. - CWE: CWE-1104 — Use of Unmaintained Third-Party Components (extended here to cover unvetted newly published components).
- Fix: Added
cooldown: default-days: 3to both thenpmandgithub-actionsentries in.github/dependabot.yml, introducing a three-day waiting period before Dependabot proposes updates to newly published versions.
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
A missing cooldown block in Dependabot configuration is easy to overlook — it doesn't cause a build failure, doesn't appear in runtime logs, and doesn't trigger any obvious warning. But for a Node.js library with a daily update schedule, it represents a real and specific supply-chain risk: the window between a malicious package publication and a Dependabot-generated PR can be measured in hours. Adding cooldown: default-days: 3 to both the npm and github-actions entries closes that window for the vast majority of supply-chain attacks, which are typically detected and reported within 72 hours of publication. Treat the cooldown block as a required field in every Dependabot configuration you write, and back it up with lockfile verification, dependency review, and pinned Action versions for a defence-in-depth posture.