Skip to main content

libverify_core/controls/
mod.rs

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