How Missing Dependabot Cooldown Happens in GitHub Actions CI/CD and How to Fix It
Introduction
In a Node.js library's repository, we discovered a high-severity supply chain vulnerability hiding in plain sight—not in application code, but in the .github/dependabot.yml configuration file at line 8. The Dependabot configuration defined two package ecosystems (npm and github-actions) with daily and weekly update schedules, but neither included a cooldown block. This meant that the moment a new package version was published to npm or a new GitHub Action release appeared, Dependabot would immediately propose it for adoption—even if that version had been published seconds ago by an attacker who had compromised a maintainer's credentials.
For a Node.js library, this is particularly dangerous: any malicious dependency pulled in through an automated PR doesn't just affect the library itself—it propagates to every downstream consumer who installs the package.
The Vulnerability Explained
Here's what the vulnerable configuration looked like for the npm ecosystem entry:
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: daily
time: "05:00"
timezone: Asia/Shanghai
groups:
jest-dependencies:
patterns:
# ... grouped dependencies
And for the GitHub Actions ecosystem:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
day: monday
groups:
github-actions:
patterns:
# ... grouped actions
Notice what's missing: neither entry has a cooldown block. Without this configuration, Dependabot operates with zero delay between a package being published and it being proposed as an update.
The Attack Scenario
Consider this realistic attack timeline:
- T+0 minutes: An attacker compromises the npm credentials of a maintainer of
jestor one of the grouped dependencies in this project's Dependabot configuration. - T+1 minute: The attacker publishes a new patch version (e.g.,
29.7.1) containing a postinstall script that exfiltrates environment variables. - T+5 minutes: Dependabot's daily scan at 05:00 Asia/Shanghai detects the new version.
- T+6 minutes: Dependabot opens a PR updating
jestto29.7.1. The CI pipeline runs, executing the malicious postinstall script and potentially leakingNPM_TOKEN,GITHUB_TOKEN, and other secrets. - T+10 minutes: A maintainer, seeing green CI checks, merges the PR.
- T+11 minutes: A new release of the library ships with the compromised dependency to all downstream consumers.
This entire attack chain completes in under 15 minutes. With a 7-day cooldown, the community would have had a week to detect and report the compromised package before Dependabot even proposed the update.
Why This Matters for Node.js Libraries
This isn't just a theoretical risk. Real-world supply chain attacks like the event-stream incident (2018), ua-parser-js compromise (2021), and colors/faker sabotage (2022) all involved newly published versions that were identified as malicious within hours or days. A 7-day cooldown would have prevented automated adoption of all of these.
The Fix
The fix adds a cooldown block with default-days: 7 to both package ecosystem entries in .github/dependabot.yml:
Before (npm ecosystem, starting at line 8):
- package-ecosystem: npm
directory: /
schedule:
interval: daily
time: "05:00"
timezone: Asia/Shanghai
groups:
jest-dependencies:
After:
- package-ecosystem: npm
directory: /
schedule:
interval: daily
time: "05:00"
timezone: Asia/Shanghai
cooldown:
default-days: 7
groups:
jest-dependencies:
Before (github-actions ecosystem):
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
day: monday
groups:
github-actions:
After:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
day: monday
cooldown:
default-days: 7
groups:
github-actions:
How the Fix Works
The cooldown configuration tells Dependabot: "Even if you detect a new version, don't propose it until it's been published for at least 7 days." This creates a critical time buffer:
- For npm packages: New versions of
jest,eslint, or any other dependency won't be proposed until they've survived 7 days of community scrutiny. Malicious packages are typically flagged and removed within 24-72 hours. - For GitHub Actions: New action versions won't be adopted immediately, preventing scenarios where a compromised action could steal repository secrets during CI runs.
The default-days: 7 value applies to all packages within that ecosystem. GitHub also supports per-package overrides if certain trusted packages need faster adoption.
Prevention & Best Practices
-
Always configure cooldown periods: Every
package-ecosystementry independabot.ymlshould have acooldownblock. Seven days is a reasonable default; consider longer periods (14-30 days) for less critical dependencies. -
Combine with version pinning: Use exact version pins (
1.2.3) rather than ranges (^1.2.3) inpackage.jsonto prevent transitive dependency attacks. -
Enable Dependabot security updates separately: Security patches (which fix known vulnerabilities) can bypass the cooldown since they address actively exploited issues. Configure
security-updatesseparately fromversion-updates. -
Use lockfile verification: Ensure your CI pipeline verifies
package-lock.jsonintegrity and fails on unexpected changes. -
Audit grouped dependencies: This project uses Dependabot's
groupsfeature (e.g.,jest-dependencies). While convenient, grouped PRs mean a single malicious package update could be bundled with legitimate updates, making it harder to spot. -
Scan with static analysis tools: Use Semgrep or similar tools to validate your CI/CD configuration files, not just application code.
Key Takeaways
- The
.github/dependabot.ymlfile is a security-critical configuration—it controls what code enters your repository automatically, and missing acooldownblock leaves you exposed to same-day supply chain attacks. - A 7-day cooldown for the
npmecosystem at line 14 prevents immediate adoption of compromised packages like those seen in theua-parser-jsandevent-streamincidents. - GitHub Actions without cooldown (line 41) are equally dangerous—a compromised action version can exfiltrate
GITHUB_TOKENand repository secrets during CI execution. - Node.js libraries amplify the risk: because downstream consumers depend on this package, a single malicious transitive dependency propagates to potentially thousands of projects.
- Both ecosystem entries needed the fix—a partial cooldown (only npm, not github-actions) would still leave an attack vector open through compromised CI actions.
How Orbis AppSec Detected This
- Source: Newly published package versions on npm registry and GitHub Actions marketplace entering the dependency update pipeline
- Sink:
.github/dependabot.ymlupdate entries at lines 8 and 36 that trigger automated pull requests without any time-based validation - Missing control: No
cooldownblock configured for either thenpmorgithub-actionspackage ecosystem entries, allowing zero-day package adoption - CWE: CWE-1104 (Use of Unmaintained Third Party Components) — inadequate controls over third-party dependency adoption timing
- Fix: Added
cooldown: default-days: 7to both package ecosystem entries, creating a 7-day buffer before newly published versions are proposed
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
Supply chain security isn't just about scanning your application code for vulnerabilities—it's about controlling the pipeline through which new code enters your repository. A missing cooldown configuration in Dependabot is a subtle but high-impact vulnerability that turns your automated dependency management into a potential attack vector. By adding a simple 7-day cooldown period, you create a critical buffer that allows the open-source community to identify and flag malicious packages before they reach your codebase. For Node.js library maintainers, this isn't optional—it's a responsibility to your downstream consumers.