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