Skip to main content

libverify_core/controls/
dependency_update_tool.rs

1use crate::control::{Control, ControlFinding, ControlId, builtin};
2use crate::evidence::EvidenceBundle;
3
4/// Validates that a dependency update tool is configured.
5///
6/// Maps to OpenSSF Scorecard Dependency-Update-Tool (High risk).
7///
8/// Checks whether Dependabot (`.github/dependabot.yml`) or Renovate
9/// (`renovate.json`, `renovate.json5`, `.renovaterc`) is configured,
10/// indicating proactive dependency update management.
11pub struct DependencyUpdateToolControl;
12
13impl Control for DependencyUpdateToolControl {
14    fn id(&self) -> ControlId {
15        builtin::id(builtin::DEPENDENCY_UPDATE_TOOL)
16    }
17
18    fn description(&self) -> &'static str {
19        "A dependency update tool (Dependabot or Renovate) must be configured"
20    }
21
22    fn evaluate(&self, evidence: &EvidenceBundle) -> Vec<ControlFinding> {
23        let posture = match ControlFinding::extract_posture(self.id(), evidence) {
24            Ok(p) => p,
25            Err(findings) => return findings,
26        };
27
28        if posture.dependency_update_tool_configured {
29            vec![ControlFinding::satisfied(
30                self.id(),
31                "Dependency update tool is configured (Dependabot or Renovate)",
32                vec!["repository:dependency-update-tool".into()],
33            )]
34        } else {
35            vec![ControlFinding::violated(
36                self.id(),
37                "No dependency update tool detected — configure .github/dependabot.yml or renovate.json",
38                vec!["repository:dependency-update-tool".into()],
39            )]
40        }
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::control::ControlStatus;
48    use crate::evidence::{EvidenceState, RepositoryPosture};
49
50    fn bundle_with(configured: bool) -> EvidenceBundle {
51        EvidenceBundle {
52            repository_posture: EvidenceState::complete(RepositoryPosture {
53                dependency_update_tool_configured: configured,
54                ..Default::default()
55            }),
56            ..Default::default()
57        }
58    }
59
60    #[test]
61    fn satisfied_when_configured() {
62        let findings = DependencyUpdateToolControl.evaluate(&bundle_with(true));
63        assert_eq!(findings[0].status, ControlStatus::Satisfied);
64    }
65
66    #[test]
67    fn violated_when_not_configured() {
68        let findings = DependencyUpdateToolControl.evaluate(&bundle_with(false));
69        assert_eq!(findings[0].status, ControlStatus::Violated);
70        assert!(findings[0].rationale.contains("dependabot.yml"));
71    }
72
73    #[test]
74    fn indeterminate_when_posture_missing() {
75        let findings = DependencyUpdateToolControl.evaluate(&EvidenceBundle {
76            repository_posture: EvidenceState::missing(vec![]),
77            ..Default::default()
78        });
79        assert_eq!(findings[0].status, ControlStatus::Indeterminate);
80    }
81
82    #[test]
83    fn not_applicable_when_posture_not_applicable() {
84        let findings = DependencyUpdateToolControl.evaluate(&EvidenceBundle {
85            repository_posture: EvidenceState::not_applicable(),
86            ..Default::default()
87        });
88        assert_eq!(findings[0].status, ControlStatus::NotApplicable);
89    }
90}