Skip to main content

libverify_core/controls/
mod.rs

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