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