How missing Dependabot cooldown happens in Node.js 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 fast your project adopts new versions of every dependency it relies on. In this Node.js library, that file contained a high-severity misconfiguration: neither the npm ecosystem entry nor the github-actions ecosystem entry defined a cooldown period. As a result, Dependabot was free to open update pull requests the instant a new package version appeared on the registry — a window that sophisticated supply-chain attackers deliberately exploit.
Because this project is a Node.js library, the blast radius extends beyond the repository itself. Every downstream consumer that installs this package inherits whatever dependencies it ships with. A compromised transitive dependency merged today can silently reach thousands of production environments within hours.
The Vulnerability Explained
What a cooldown period does (and what happens without one)
When Dependabot has no cooldown configuration, it evaluates new package versions as soon as its scheduler fires — in this case, daily for both ecosystems. That means a package published at midnight could have an open pull request in this repository by the following morning, long before:
- The npm security team or community has reviewed the release.
- Automated malware scanners have processed the new tarball.
- The original maintainer has noticed their account was compromised.
The vulnerable configuration (before the fix) looked like this for the npm ecosystem:
# BEFORE — no cooldown at all on the first npm entry
- package-ecosystem: npm
directory: /
schedule:
interval: daily
commit-message:
prefix: "fix(deps):"
prefix-development: "chore(deps):"
labels:
- npm dependencies
And the github-actions entry was identical in its omission:
# BEFORE — no cooldown on github-actions
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
commit-message:
prefix: "chore(action):"
labels:
- dependencies
A second pair of entries did have a cooldown block, but it was set to only 2 days — far too short to provide meaningful protection:
cooldown:
default-days: 2 # ← 2 days is not enough
The net result: some ecosystem entries had zero protection, and others had a cooldown so short it offered only marginal defence.
How an attacker exploits this
Supply-chain attacks against npm packages follow a well-documented playbook:
- Account takeover — An attacker compromises a maintainer's npm credentials (often via credential stuffing or phishing).
- Malicious publish — They publish a new patch or minor version of a popular package containing a backdoor, credential harvester, or cryptominer.
- Race the bots — Automated dependency update tools like Dependabot pick up the new version within hours and open pull requests across thousands of repositories.
- Auto-merge or fast merge — Projects with auto-merge enabled, or maintainers who trust Dependabot implicitly, merge the PR before any advisory is published.
With interval: daily and no cooldown, this repository sat squarely in that race window. The 2-day cooldown on the duplicate entries barely helped: many high-profile supply-chain incidents (e.g., the event-stream compromise, the ua-parser-js hijack) were identified within 24–72 hours — but only after many projects had already merged the malicious version.
Real-world impact for this project
Because this is a Node.js library (not just an application), a compromised dependency merged here would be:
- Bundled into the library's own published npm package.
- Silently distributed to every consumer on their next
npm installornpm update. - Potentially executed in CI pipelines, servers, and developer machines with no further action required by the consumer.
The github-actions ecosystem carries its own risk: a malicious action version could exfiltrate GITHUB_TOKEN, repository secrets, or source code from every workflow run.
The Fix
The fix consolidates and corrects the dependabot.yml configuration in two ways:
- Removes duplicate entries — The file previously had four
package-ecosystemblocks (twonpmentries and twogithub-actionsentries). The fix reduces this to one entry per ecosystem, eliminating the confusion that allowed some entries to exist without a cooldown. - Sets
default-days: 7on every entry — Seven days is the threshold recommended by GitHub's own documentation and gives the security community a meaningful window to identify and disclose compromised releases.
Before and after
npm ecosystem — before:
- package-ecosystem: npm
directory: /
schedule:
interval: daily
# ← no cooldown whatsoever
commit-message:
prefix: "fix(deps):"
prefix-development: "chore(deps):"
labels:
- npm dependencies
npm ecosystem — after:
- package-ecosystem: npm
directory: /
schedule:
interval: daily
cooldown:
default-days: 7 # ← wait 7 days after publish
commit-message:
prefix: "fix(deps):"
prefix-development: "chore(deps):"
labels:
- npm dependencies
github-actions ecosystem — before:
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
# ← no cooldown
commit-message:
prefix: "chore(action):"
labels:
- build dependencies
github-actions ecosystem — after:
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
cooldown:
default-days: 7 # ← wait 7 days after publish
commit-message:
prefix: "chore(action):"
labels:
- dependencies
The cooldown block also supports more granular control. For example, if you want to apply a longer hold to production dependencies but a shorter one to development tooling:
cooldown:
default-days: 7
semver-patch-days: 3 # patch versions wait only 3 days
semver-minor-days: 5 # minor versions wait 5 days
This level of tuning is optional but worth considering for teams that need faster patch cycles while still protecting against major/minor version supply-chain attacks.
Why 7 days specifically?
GitHub's own documentation recommends 7 days as the default cooldown. Analysis of historical supply-chain incidents shows that the majority of malicious package versions are identified and removed from registries within 3–7 days of publication. A 7-day window therefore catches most incidents before they reach your package-lock.json.
Prevention & Best Practices
1. Always define a cooldown in dependabot.yml
Every package-ecosystem entry should have a cooldown block. Treat its absence as a misconfiguration, not a default that is safe to skip.
# Minimum recommended configuration
cooldown:
default-days: 7
2. Audit for duplicate ecosystem entries
The original file had four entries where two were needed. Duplicate entries create confusion and make it easy for one copy to drift out of sync with security settings. Lint your dependabot.yml as part of CI.
3. Do not rely on auto-merge without a cooldown
Auto-merge for Dependabot PRs is a productivity win, but it is dangerous without a cooldown. If you use auto-merge (via gh CLI, GitHub's built-in feature, or a third-party action), pair it with default-days: 7 at a minimum.
4. Enable npm provenance and Sigstore verification
For npm packages, provenance attestations (available since npm 9.5) allow you to verify that a package was built from a specific commit in a specific repository. Combined with a cooldown, this significantly raises the bar for supply-chain attacks.
5. Use Semgrep in CI to catch future regressions
The Semgrep rule that detected this issue — package_managers.dependabot.dependabot-missing-cooldown.dependabot-missing-cooldown — can be run in CI to prevent the cooldown from being accidentally removed in future PRs:
# .github/workflows/semgrep.yml (excerpt)
- name: Run Semgrep
run: semgrep --config "p/supply-chain" .github/dependabot.yml
6. Monitor the npm advisory database
Subscribe to GitHub Advisory Database notifications for your key dependencies. Even with a cooldown, staying informed lets you act immediately when a critical advisory drops.
OWASP and CWE context
This vulnerability maps to:
- OWASP A06:2021 — Vulnerable and Outdated Components: Specifically the risk of automatically adopting unvetted component versions.
- CWE-1104: Use of Unmaintained Third-Party Components — the broader category covering inadequate controls over third-party dependency ingestion.
Key Takeaways
- A
cooldownblock is not optional — everypackage-ecosystementry in.github/dependabot.ymlmust have one. An entry withoutcooldownis a zero-delay pipeline from the npm registry to your codebase. default-days: 2is not enough — the original partial fix used a 2-day cooldown, which is shorter than the average time it takes for malicious packages to be detected and removed. Seven days is the community-recommended minimum.- Duplicate ecosystem entries are a maintenance hazard — having four entries (two
npm, twogithub-actions) meant security settings could diverge silently. Consolidate to one entry per ecosystem. - Daily schedule + no cooldown = maximum exposure — the
interval: dailyschedule is aggressive. Without a cooldown it means Dependabot checks for new versions every day and proposes them immediately, maximising the chance of racing a supply-chain attack. - Library projects have amplified blast radius — because this is a Node.js library, a compromised dependency doesn't just affect this repository; it propagates to every consumer on their next install.
How Orbis AppSec Detected This
- Source: The
dependabot.ymlconfiguration file at.github/dependabot.yml, line 3 — specifically thepackage-ecosystem: npmandpackage-ecosystem: github-actionsentries that control automated dependency update behaviour. - Sink: The absence of a
cooldownblock means Dependabot's scheduler acts as an unrestricted pipeline, proposing any newly published package version without delay. - Missing control: No
cooldown: default-days: 7(or higher) on any ecosystem entry, and a critically shortdefault-days: 2on the duplicate entries that did have a cooldown. - CWE: CWE-1104 — Use of Unmaintained Third-Party Components (inadequate supply-chain ingestion controls).
- Fix: Added
cooldown: default-days: 7to everypackage-ecosystementry and removed duplicate entries to prevent configuration drift.
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 single missing cooldown block in a YAML file might look like a minor oversight, but in the context of software supply-chain security it represents a real and exploitable gap. This Node.js library was one fast-moving supply-chain attack away from shipping a backdoored dependency to its entire user base — not because of any flaw in the application code, but because of a misconfiguration in the automation that manages its dependencies.
The fix is simple, low-friction, and has zero impact on developer velocity: a 7-day wait before Dependabot proposes an update. That one week is the difference between your project being an early victim of a supply-chain attack and being protected by the collective vigilance of the security community.
Review your own dependabot.yml today. If you don't see a cooldown block on every package-ecosystem entry, add one.