Skip to main content

libverify_core/controls/
mod.rs

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