Skip to main content

sdkt_audit/
types.rs

1//! Static security analysis types for Soroban contracts.
2//!
3//! These types are serializable so the CLI can emit them as JSON (the
4//! `AuditReport` is the stable output contract for `sdkt audit`).
5
6use serde::{Deserialize, Serialize};
7
8/// Severity of a finding. Ordered most-to-least severe for reporting.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
10#[serde(rename_all = "snake_case")]
11pub enum Severity {
12    Critical,
13    #[default]
14    Warning,
15    Info,
16}
17
18impl std::fmt::Display for Severity {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        let s = match self {
21            Severity::Critical => "critical",
22            Severity::Warning => "warning",
23            Severity::Info => "info",
24        };
25        f.pad(s)
26    }
27}
28
29/// Metadata describing an available audit rule.
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct RuleInfo {
32    /// Stable rule identifier, e.g. `AUTH-001`.
33    pub id: String,
34    /// Severity emitted by this rule.
35    pub severity: Severity,
36    /// Human-readable description of what the rule checks.
37    pub description: String,
38}
39
40/// A single audit finding produced by a rule.
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub struct Finding {
43    /// Stable rule identifier, e.g. `AUTH-001`.
44    pub rule_id: String,
45    pub severity: Severity,
46    pub message: String,
47    /// Originating source file for multi-file audits.
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub file: Option<String>,
50    /// Optional human-readable location (function name, or `fn:binding`).
51    pub location: Option<String>,
52}
53
54/// Aggregate counts by severity.
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
56pub struct AuditSummary {
57    pub critical: usize,
58    pub warning: usize,
59    pub info: usize,
60    pub total: usize,
61}
62
63/// The full result of an audit run.
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
65pub struct AuditReport {
66    pub findings: Vec<Finding>,
67    pub summary: AuditSummary,
68}
69
70impl AuditReport {
71    /// Append a finding and update the severity counters.
72    pub fn add(&mut self, f: Finding) {
73        match f.severity {
74            Severity::Critical => self.summary.critical += 1,
75            Severity::Warning => self.summary.warning += 1,
76            Severity::Info => self.summary.info += 1,
77        }
78        self.summary.total += 1;
79        self.findings.push(f);
80    }
81
82    /// True when no findings were produced.
83    pub fn is_clean(&self) -> bool {
84        self.findings.is_empty()
85    }
86}