Skip to main content

secureops_core/
check.rs

1//! The `Check` trait and the `run_audit` orchestrator.
2//!
3//! In the TS tool each audit *category* (`auditGateway`, `auditCredentials`, …)
4//! is one async function returning `AuditFinding[]`. Here that becomes one
5//! `Check` impl per category (PRODUCT.md A.4: "one Check impl per audit* fn"),
6//! living in the `secureops-checks` crate.
7
8use crate::context::AuditContext;
9use crate::scoring::{calculate_score, compute_summary, cross_layer_risk};
10use crate::types::{AuditFinding, AuditReport};
11use async_trait::async_trait;
12
13/// Options for running an audit (port of `AuditOptions`, minus `context` which
14/// is passed explicitly).
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
16pub struct AuditOptions {
17    pub deep: bool,
18    pub fix: bool,
19    pub json: bool,
20}
21
22/// One audit category. Implementors live in `secureops-checks`.
23#[async_trait]
24pub trait Check: Send + Sync {
25    /// Stable category id (e.g. `"gateway"`), used for logging/diagnostics.
26    fn category(&self) -> &'static str;
27
28    /// Run the category against the context, returning zero or more findings.
29    ///
30    /// A check must never panic the run: the orchestrator isolates failures,
31    /// but checks should also degrade to an INFO finding on missing inputs
32    /// (mirrors the TS "the run never aborts" guarantee, PRODUCT.md B.2).
33    async fn run(&self, ctx: &dyn AuditContext, opts: &AuditOptions) -> Vec<AuditFinding>;
34}
35
36/// Run every check against `ctx`, append the MAESTRO cross-layer compound-risk
37/// finding, then score and summarize — the faithful port of `runAudit`.
38///
39/// `timestamp` is injected (RFC3339) rather than read from a clock here so this
40/// stays pure and deterministic; callers stamp `new Date().toISOString()`'s
41/// equivalent. Findings are concatenated in `checks` order, matching the fixed
42/// category order of the TS `Promise.all` aggregation.
43pub async fn run_audit(
44    ctx: &dyn AuditContext,
45    checks: &[Box<dyn Check>],
46    opts: &AuditOptions,
47    timestamp: String,
48    secureops_version: &str,
49) -> AuditReport {
50    let mut findings: Vec<AuditFinding> = Vec::new();
51    for check in checks {
52        findings.extend(check.run(ctx, opts).await);
53    }
54
55    // Cross-layer threat detection runs after all checks (PRODUCT.md B.2).
56    let cross = cross_layer_risk(&findings);
57    findings.extend(cross);
58
59    let score = calculate_score(&findings);
60    let summary = compute_summary(&findings);
61
62    AuditReport {
63        timestamp,
64        openclaw_version: ctx.openclaw_version().to_string(),
65        secureops_version: secureops_version.to_string(),
66        platform: ctx.platform().to_string(),
67        deployment_mode: ctx.deployment_mode().to_string(),
68        score,
69        findings,
70        summary,
71    }
72}