diff --git a/crates/zizmor/src/audit/dependabot_cooldown.rs b/crates/zizmor/src/audit/dependabot_cooldown.rs index d4b767bf..465a009c 100644 --- a/crates/zizmor/src/audit/dependabot_cooldown.rs +++ b/crates/zizmor/src/audit/dependabot_cooldown.rs @@ -85,7 +85,7 @@ impl Audit for DependabotCooldown { async fn audit_dependabot<'doc>( &self, dependabot: &'doc crate::models::dependabot::Dependabot, - _config: &crate::config::Config, + config: &crate::config::Config, ) -> Result>, AuditError> { let mut findings = vec![]; @@ -111,24 +111,24 @@ impl Audit for DependabotCooldown { .fix(Self::create_add_default_days_fix(update)) .build(dependabot)?, ), - // We currently (arbitrarily) consider cooldowns under 7 days - // to be insufficient. - // - // TODO(ww): This should probably be configurable. - Some(default_days) if default_days < 7 => findings.push( - Self::finding() - .add_location( - update - .location() - .with_keys(["cooldown".into(), "default-days".into()]) - .primary() - .annotated("insufficient default-days configured"), - ) - .confidence(Confidence::Medium) - .severity(Severity::Low) - .fix(Self::create_increase_default_days_fix(update)) - .build(dependabot)?, - ), + Some(default_days) + if default_days < config.dependabot_cooldown_config.days.get() as u64 => + { + findings.push( + Self::finding() + .add_location( + update + .location() + .with_keys(["cooldown".into(), "default-days".into()]) + .primary() + .annotated("insufficient default-days configured"), + ) + .confidence(Confidence::Medium) + .severity(Severity::Low) + .fix(Self::create_increase_default_days_fix(update)) + .build(dependabot)?, + ) + } Some(_) => {} }, None => findings.push( diff --git a/crates/zizmor/src/config.rs b/crates/zizmor/src/config.rs index edb23df4..9af773ba 100644 --- a/crates/zizmor/src/config.rs +++ b/crates/zizmor/src/config.rs @@ -11,7 +11,10 @@ use thiserror::Error; use crate::{ App, CollectionOptions, - audit::{AuditCore, forbidden_uses::ForbiddenUses, unpinned_uses::UnpinnedUses}, + audit::{ + AuditCore, dependabot_cooldown::DependabotCooldown, forbidden_uses::ForbiddenUses, + unpinned_uses::UnpinnedUses, + }, finding::Finding, github::{Client, ClientError}, models::uses::RepositoryUsesPattern, @@ -151,6 +154,20 @@ impl RawConfig { } } +#[derive(Clone, Debug, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub(crate) struct DependabotCooldownConfig { + pub(crate) days: NonZeroUsize, +} + +impl Default for DependabotCooldownConfig { + fn default() -> Self { + Self { + days: NonZeroUsize::new(7).expect("impossible"), + } + } +} + #[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "kebab-case", untagged)] pub(crate) enum ForbiddenUsesConfig { @@ -344,6 +361,7 @@ impl TryFrom for UnpinnedUsesPolicies { #[derive(Clone, Debug, Default)] pub(crate) struct Config { raw: RawConfig, + pub(crate) dependabot_cooldown_config: DependabotCooldownConfig, pub(crate) forbidden_uses_config: Option, pub(crate) unpinned_uses_policies: UnpinnedUsesPolicies, } @@ -353,6 +371,10 @@ impl Config { fn load(contents: &str) -> Result { let raw = RawConfig::load(contents)?; + let dependabot_cooldown_config = raw + .rule_config(DependabotCooldown::ident())? + .unwrap_or_default(); + let forbidden_uses_config = raw.rule_config(ForbiddenUses::ident())?; let unpinned_uses_policies = { @@ -367,6 +389,7 @@ impl Config { Ok(Self { raw, + dependabot_cooldown_config, forbidden_uses_config, unpinned_uses_policies, }) diff --git a/crates/zizmor/tests/integration/snapshot.rs b/crates/zizmor/tests/integration/snapshot.rs index f2564008..3eb54e3f 100644 --- a/crates/zizmor/tests/integration/snapshot.rs +++ b/crates/zizmor/tests/integration/snapshot.rs @@ -1421,6 +1421,82 @@ fn dependabot_cooldown() -> Result<()> { 1 findings (1 fixable): 0 informational, 1 low, 0 medium, 0 high "); + // dependabot-cooldown audit config is invalid. + insta::assert_snapshot!( + zizmor() + .expects_failure(true) + .input(input_under_test("neutral.yml")) + .config(input_under_test("dependabot-cooldown/configs/invalid-cooldown-not-number.yml")) + .output(OutputMode::Stderr) + .run()?, + @r#" + 🌈 zizmor v@@VERSION@@ + fatal: no audit was performed + error: configuration error in @@CONFIG@@ + | + = help: check the configuration for the 'dependabot-cooldown' rule + = help: see: https://docs.zizmor.sh/audits/#dependabot-cooldown-configuration + + Caused by: + 0: configuration error in @@CONFIG@@ + 1: invalid syntax for audit `dependabot-cooldown` + 2: invalid type: string "lol", expected a nonzero usize + "# + ); + + insta::assert_snapshot!( + zizmor() + .expects_failure(true) + .input(input_under_test("neutral.yml")) + .config(input_under_test("dependabot-cooldown/configs/invalid-cooldown-zero-days.yml")) + .output(OutputMode::Stderr) + .run()?, + @r" + 🌈 zizmor v@@VERSION@@ + fatal: no audit was performed + error: configuration error in @@CONFIG@@ + | + = help: check the configuration for the 'dependabot-cooldown' rule + = help: see: https://docs.zizmor.sh/audits/#dependabot-cooldown-configuration + + Caused by: + 0: configuration error in @@CONFIG@@ + 1: invalid syntax for audit `dependabot-cooldown` + 2: invalid value: integer `0`, expected a nonzero usize + " + ); + + insta::assert_snapshot!( + zizmor() + .expects_failure(true) + .input(input_under_test("neutral.yml")) + .config(input_under_test("dependabot-cooldown/configs/invalid-cooldown-negative-days.yml")) + .output(OutputMode::Stderr) + .run()?, + @r" + 🌈 zizmor v@@VERSION@@ + fatal: no audit was performed + error: configuration error in @@CONFIG@@ + | + = help: check the configuration for the 'dependabot-cooldown' rule + = help: see: https://docs.zizmor.sh/audits/#dependabot-cooldown-configuration + + Caused by: + 0: configuration error in @@CONFIG@@ + 1: invalid syntax for audit `dependabot-cooldown` + 2: invalid value: integer `-1`, expected a nonzero usize + " + ); + + // A very short cooldown, but permitted by config. + insta::assert_snapshot!( + zizmor() + .input(input_under_test("dependabot-cooldown/default-days-too-short/dependabot.yml")) + .config(input_under_test("dependabot-cooldown/configs/cooldown-one-day.yml")) + .run()?, + @"No findings to report. Good job!" + ); + Ok(()) } diff --git a/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/cooldown-one-day.yml b/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/cooldown-one-day.yml new file mode 100644 index 00000000..a072c7f0 --- /dev/null +++ b/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/cooldown-one-day.yml @@ -0,0 +1,4 @@ +rules: + dependabot-cooldown: + config: + days: 1 diff --git a/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/invalid-cooldown-negative-days.yml b/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/invalid-cooldown-negative-days.yml new file mode 100644 index 00000000..5b45e804 --- /dev/null +++ b/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/invalid-cooldown-negative-days.yml @@ -0,0 +1,4 @@ +rules: + dependabot-cooldown: + config: + days: -1 diff --git a/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/invalid-cooldown-not-number.yml b/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/invalid-cooldown-not-number.yml new file mode 100644 index 00000000..f1476700 --- /dev/null +++ b/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/invalid-cooldown-not-number.yml @@ -0,0 +1,4 @@ +rules: + dependabot-cooldown: + config: + days: lol diff --git a/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/invalid-cooldown-zero-days.yml b/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/invalid-cooldown-zero-days.yml new file mode 100644 index 00000000..24f79d97 --- /dev/null +++ b/crates/zizmor/tests/integration/test-data/dependabot-cooldown/configs/invalid-cooldown-zero-days.yml @@ -0,0 +1,4 @@ +rules: + dependabot-cooldown: + config: + days: 0 diff --git a/docs/audits.md b/docs/audits.md index ebbe97e5..1ae8c73d 100644 --- a/docs/audits.md +++ b/docs/audits.md @@ -386,7 +386,7 @@ Some general pointers: | Type | Examples | Introduced in | Works offline | Auto-fixes available | Configurable | |----------|-------------------------|---------------|----------------|--------------------| ---------------| -| Dependabot | [dependabot-cooldown/] | v1.15.0 | ✅ | ✅ | ❌ | +| Dependabot | [dependabot-cooldown/] | v1.15.0 | ✅ | ✅ | ✅ | [dependabot-cooldown/]: https://github.com/zizmorcore/zizmor/blob/main/crates/zizmor/tests/integration/test-data/dependabot-cooldown/ @@ -414,13 +414,23 @@ enable them. Other resources: * [Dependabot options reference - `cooldown`](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#cooldown-) +* [We should all be using Dependency cooldowns](https://blog.yossarian.net/2025/11/21/We-should-all-be-using-dependency-cooldowns) + +### Configuration + +#### `rules.dependabot-cooldown.config.days` + +Type: number + +The `rules.dependabot-cooldown.config.days` setting controls the minimum acceptable +`default-days` value for Dependabot's `cooldown` setting. Settings beneath this +value will produce findings. + +The default value is `7`. ### Remediation -In general, you should enable `cooldown` for all updaters. The audit currently -enforces the following minimums: - -* `default-days`: must be at least `7`. +In general, you should enable `cooldown` for all updaters. !!! example diff --git a/docs/release-notes.md b/docs/release-notes.md index 1a1e3b11..aaab2643 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -16,6 +16,10 @@ of `zizmor`. * The [dependabot-cooldown] audit now flags cooldown periods of less than 7 days by default (#1375) + +* The [dependabot-cooldown] audit can now be configured with a custom + minimum cooldown period via `rules.dependabot-cooldown.config.days` + (#1377) ### Bug Fixes 🐛