Skip to main content

libverify_core/controls/
mod.rs

1pub mod branch_history_integrity;
2pub mod branch_protection_enforcement;
3pub mod build_isolation;
4pub mod build_provenance;
5pub mod change_request_size;
6pub mod codeowners_coverage;
7pub mod conventional_title;
8pub mod dependency_completeness;
9pub mod dependency_provenance;
10pub mod dependency_signature;
11pub mod dependency_signer_verified;
12pub mod description_quality;
13pub mod hosted_build_platform;
14pub mod issue_linkage;
15pub mod merge_commit_policy;
16pub mod provenance_authenticity;
17pub mod release_traceability;
18pub mod required_status_checks;
19pub mod review_independence;
20pub mod scoped_change;
21pub mod secret_scanning;
22pub mod security_file_change;
23pub mod security_policy;
24pub mod source_authenticity;
25pub mod stale_review;
26pub mod test_coverage;
27pub mod two_party_review;
28pub mod vulnerability_scanning;
29
30use crate::control::{Control, builtin};
31use crate::slsa::{SlsaLevel, SlsaTrack};
32
33use self::branch_history_integrity::BranchHistoryIntegrityControl;
34use self::branch_protection_enforcement::BranchProtectionEnforcementControl;
35use self::build_isolation::BuildIsolationControl;
36use self::build_provenance::BuildProvenanceControl;
37use self::change_request_size::ChangeRequestSizeControl;
38use self::codeowners_coverage::CodeownersCoverageControl;
39use self::conventional_title::ConventionalTitleControl;
40use self::dependency_completeness::DependencyCompletenessControl;
41use self::dependency_provenance::DependencyProvenanceControl;
42use self::dependency_signature::DependencySignatureControl;
43use self::dependency_signer_verified::DependencySignerVerifiedControl;
44use self::description_quality::DescriptionQualityControl;
45use self::hosted_build_platform::HostedBuildPlatformControl;
46use self::issue_linkage::IssueLinkageControl;
47use self::merge_commit_policy::MergeCommitPolicyControl;
48use self::provenance_authenticity::ProvenanceAuthenticityControl;
49use self::release_traceability::ReleaseTraceabilityControl;
50use self::required_status_checks::RequiredStatusChecksControl;
51use self::review_independence::ReviewIndependenceControl;
52use self::scoped_change::ScopedChangeControl;
53use self::secret_scanning::SecretScanningControl;
54use self::security_file_change::SecurityFileChangeControl;
55use self::security_policy::SecurityPolicyControl;
56use self::source_authenticity::SourceAuthenticityControl;
57use self::stale_review::StaleReviewControl;
58use self::test_coverage::TestCoverageControl;
59use self::two_party_review::TwoPartyReviewControl;
60use self::vulnerability_scanning::VulnerabilityScanningControl;
61
62/// Instantiates a control by its string ID.
63fn instantiate(id: &str) -> Option<Box<dyn Control>> {
64    match id {
65        builtin::SOURCE_AUTHENTICITY => Some(Box::new(SourceAuthenticityControl)),
66        builtin::REVIEW_INDEPENDENCE => Some(Box::new(ReviewIndependenceControl)),
67        builtin::BRANCH_HISTORY_INTEGRITY => Some(Box::new(BranchHistoryIntegrityControl)),
68        builtin::BRANCH_PROTECTION_ENFORCEMENT => {
69            Some(Box::new(BranchProtectionEnforcementControl))
70        }
71        builtin::TWO_PARTY_REVIEW => Some(Box::new(TwoPartyReviewControl)),
72        builtin::BUILD_PROVENANCE => Some(Box::new(BuildProvenanceControl)),
73        builtin::REQUIRED_STATUS_CHECKS => Some(Box::new(RequiredStatusChecksControl)),
74        builtin::HOSTED_BUILD_PLATFORM => Some(Box::new(HostedBuildPlatformControl)),
75        builtin::PROVENANCE_AUTHENTICITY => Some(Box::new(ProvenanceAuthenticityControl)),
76        builtin::BUILD_ISOLATION => Some(Box::new(BuildIsolationControl)),
77        builtin::DEPENDENCY_SIGNATURE => Some(Box::new(DependencySignatureControl)),
78        builtin::DEPENDENCY_PROVENANCE_CHECK => Some(Box::new(DependencyProvenanceControl)),
79        builtin::DEPENDENCY_SIGNER_VERIFIED => Some(Box::new(DependencySignerVerifiedControl)),
80        builtin::DEPENDENCY_COMPLETENESS => Some(Box::new(DependencyCompletenessControl)),
81        builtin::CHANGE_REQUEST_SIZE => Some(Box::new(ChangeRequestSizeControl)),
82        builtin::TEST_COVERAGE => Some(Box::new(TestCoverageControl)),
83        builtin::SCOPED_CHANGE => Some(Box::new(ScopedChangeControl)),
84        builtin::ISSUE_LINKAGE => Some(Box::new(IssueLinkageControl)),
85        builtin::STALE_REVIEW => Some(Box::new(StaleReviewControl)),
86        builtin::DESCRIPTION_QUALITY => Some(Box::new(DescriptionQualityControl)),
87        builtin::MERGE_COMMIT_POLICY => Some(Box::new(MergeCommitPolicyControl)),
88        builtin::CONVENTIONAL_TITLE => Some(Box::new(ConventionalTitleControl)),
89        builtin::SECURITY_FILE_CHANGE => Some(Box::new(SecurityFileChangeControl)),
90        builtin::RELEASE_TRACEABILITY => Some(Box::new(ReleaseTraceabilityControl)),
91        builtin::CODEOWNERS_COVERAGE => Some(Box::new(CodeownersCoverageControl)),
92        builtin::SECRET_SCANNING => Some(Box::new(SecretScanningControl)),
93        builtin::VULNERABILITY_SCANNING => Some(Box::new(VulnerabilityScanningControl)),
94        builtin::SECURITY_POLICY => Some(Box::new(SecurityPolicyControl)),
95        _ => None,
96    }
97}
98
99/// Returns the SARIF-friendly description for a built-in control ID.
100/// Falls back to "Custom control" for unknown IDs.
101pub fn control_description(id: &str) -> &'static str {
102    match instantiate(id) {
103        Some(c) => c.description(),
104        None => "Custom control",
105    }
106}
107
108/// Returns all SLSA controls required for the given track up to the given level.
109pub fn slsa_controls_for_level(track: SlsaTrack, level: SlsaLevel) -> Vec<Box<dyn Control>> {
110    crate::slsa::controls_for_level(track, level)
111        .into_iter()
112        .filter_map(|id| instantiate(id.as_str()))
113        .collect()
114}
115
116/// Returns all SLSA controls across both tracks up to the given levels.
117pub fn slsa_controls(source_level: SlsaLevel, build_level: SlsaLevel) -> Vec<Box<dyn Control>> {
118    let mut controls = slsa_controls_for_level(SlsaTrack::Source, source_level);
119    controls.extend(slsa_controls_for_level(SlsaTrack::Build, build_level));
120    controls
121}
122
123/// Returns all SLSA controls (Source L4 + Build L3 + Dependencies L4).
124pub fn all_slsa_controls() -> Vec<Box<dyn Control>> {
125    let mut controls = slsa_controls(SlsaLevel::L4, SlsaLevel::L3);
126    controls.extend(slsa_controls_for_level(
127        SlsaTrack::Dependencies,
128        SlsaLevel::L4,
129    ));
130    controls
131}
132
133/// Returns compliance controls (non-SLSA, SOC2/ASPM mapped).
134pub fn compliance_controls() -> Vec<Box<dyn Control>> {
135    vec![
136        Box::new(ChangeRequestSizeControl),
137        Box::new(TestCoverageControl),
138        Box::new(ScopedChangeControl),
139        Box::new(IssueLinkageControl),
140        Box::new(StaleReviewControl),
141        Box::new(DescriptionQualityControl),
142        Box::new(MergeCommitPolicyControl),
143        Box::new(ConventionalTitleControl),
144        Box::new(SecurityFileChangeControl),
145        Box::new(ReleaseTraceabilityControl),
146        Box::new(CodeownersCoverageControl),
147        Box::new(SecretScanningControl),
148        Box::new(VulnerabilityScanningControl),
149        Box::new(SecurityPolicyControl),
150    ]
151}
152
153/// Returns repository-posture controls only (no PR-scoped compliance controls).
154///
155/// These evaluate repository-level security configuration:
156/// CODEOWNERS, secret scanning, vulnerability scanning, and security policy.
157pub fn posture_controls() -> Vec<Box<dyn Control>> {
158    vec![
159        Box::new(CodeownersCoverageControl),
160        Box::new(SecretScanningControl),
161        Box::new(VulnerabilityScanningControl),
162        Box::new(SecurityPolicyControl),
163    ]
164}
165
166/// Returns all controls (all SLSA + compliance).
167pub fn all_controls() -> Vec<Box<dyn Control>> {
168    let mut controls = all_slsa_controls();
169    controls.extend(compliance_controls());
170    controls
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use crate::control::builtin;
177    use crate::slsa::control_slsa_mapping;
178
179    #[test]
180    fn slsa_l1_returns_l1_controls_only() {
181        let controls = slsa_controls(SlsaLevel::L1, SlsaLevel::L1);
182        for c in &controls {
183            let mapping = control_slsa_mapping(&c.id()).expect("should be SLSA-mapped");
184            assert!(
185                mapping.level <= SlsaLevel::L1,
186                "{:?} is L{:?} but should be L1 or below",
187                c.id(),
188                mapping.level
189            );
190        }
191    }
192
193    #[test]
194    fn all_slsa_includes_l3_build_and_l4_source() {
195        let controls = all_slsa_controls();
196        let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
197        assert!(
198            ids.iter()
199                .any(|id| id.as_str() == builtin::TWO_PARTY_REVIEW)
200        );
201        assert!(ids.iter().any(|id| id.as_str() == builtin::BUILD_ISOLATION));
202    }
203
204    #[test]
205    fn all_controls_includes_compliance() {
206        let controls = all_controls();
207        let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
208        assert!(
209            ids.iter()
210                .any(|id| id.as_str() == builtin::CHANGE_REQUEST_SIZE)
211        );
212        assert!(ids.iter().any(|id| id.as_str() == builtin::ISSUE_LINKAGE));
213    }
214
215    #[test]
216    fn compliance_plus_slsa_equals_all() {
217        use crate::control::builtin;
218        let compliance = compliance_controls();
219        let slsa = all_slsa_controls();
220        assert_eq!(
221            compliance.len() + slsa.len(),
222            builtin::ALL.len(),
223            "compliance + SLSA controls must cover all built-in controls"
224        );
225    }
226
227    #[test]
228    fn compliance_controls_are_not_slsa_mapped() {
229        use crate::slsa::control_slsa_mapping;
230        let controls = compliance_controls();
231        for c in &controls {
232            assert!(
233                control_slsa_mapping(&c.id()).is_none(),
234                "{:?} should not be SLSA-mapped",
235                c.id()
236            );
237        }
238    }
239
240    #[test]
241    fn compliance_controls_have_unique_ids() {
242        let controls = compliance_controls();
243        let mut ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
244        let original_len = ids.len();
245        ids.sort_by_key(|id| id.as_str().to_string());
246        ids.dedup();
247        assert_eq!(
248            ids.len(),
249            original_len,
250            "all compliance control IDs must be unique"
251        );
252    }
253
254    #[test]
255    fn all_controls_count() {
256        let slsa = all_slsa_controls();
257        let compliance = compliance_controls();
258        let all = all_controls();
259        assert_eq!(
260            all.len(),
261            slsa.len() + compliance.len(),
262            "all_controls = SLSA + compliance"
263        );
264    }
265
266    #[test]
267    fn slsa_controls_for_level_source_l2() {
268        let controls = slsa_controls_for_level(SlsaTrack::Source, SlsaLevel::L2);
269        let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
270        assert!(
271            ids.iter()
272                .any(|id| id.as_str() == builtin::BRANCH_HISTORY_INTEGRITY)
273        );
274        assert!(
275            !ids.iter()
276                .any(|id| id.as_str() == builtin::BRANCH_PROTECTION_ENFORCEMENT)
277        );
278    }
279
280    #[test]
281    fn slsa_controls_for_level_build_l2() {
282        let controls = slsa_controls_for_level(SlsaTrack::Build, SlsaLevel::L2);
283        let ids: Vec<_> = controls.iter().map(|c| c.id()).collect();
284        assert!(
285            ids.iter()
286                .any(|id| id.as_str() == builtin::HOSTED_BUILD_PLATFORM)
287        );
288        assert!(
289            ids.iter()
290                .any(|id| id.as_str() == builtin::PROVENANCE_AUTHENTICITY)
291        );
292        assert!(!ids.iter().any(|id| id.as_str() == builtin::BUILD_ISOLATION));
293    }
294}