Skip to main content

libverify_core/controls/
mod.rs

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