Skip to main content

sdkt_audit_example_rule/
lib.rs

1//! Example `sdkt-audit` plugin rule — reference implementation only.
2//!
3//! This crate demonstrates the plugin author workflow:
4//! 1. Implement [`sdkt_audit::AuditRule`].
5//! 2. Register the rule into the process-wide registry via
6//!    [`sdkt_audit::register_rule`] (or the `register_rule!` macro).
7//! 3. Produce a [`sdkt_audit::Finding`] when your condition holds.
8//!
9//! It is compiled in only when `sdkt-cli` is built with the `plugins` feature,
10//! so default builds behave exactly like . See `docs/plugin-authoring.md`.
11
12#[cfg(not(target_arch = "wasm32"))]
13use sdkt_audit::{
14    register_rule, AuditContext, AuditReport, AuditRule, BoxedRule, Finding, FnScan, Severity,
15};
16
17/// EXAMPLE-001 — flags any function whose name contains `sdkt_example_trigger`.
18///
19/// Deliberately contrived so it never interferes with real audits or the
20/// built-in rules' outputs.
21#[cfg(not(target_arch = "wasm32"))]
22pub struct ExampleRule;
23
24#[cfg(not(target_arch = "wasm32"))]
25impl AuditRule for ExampleRule {
26    fn id(&self) -> &'static str {
27        "EXAMPLE-001"
28    }
29    fn severity(&self) -> Severity {
30        Severity::Info
31    }
32    fn description(&self) -> &'static str {
33        "Example rule: detects functions named sdkt_example_trigger"
34    }
35    fn check(&self, scans: &[FnScan], _ctx: &AuditContext, report: &mut AuditReport) {
36        for s in scans {
37            if s.fn_name.contains("sdkt_example_trigger") {
38                report.add(Finding {
39                    file: None,
40                    rule_id: self.id().to_string(),
41                    severity: self.severity(),
42                    message: format!("Example rule matched trigger function `{}`", s.fn_name),
43                    location: Some(s.fn_name.clone()),
44                });
45            }
46        }
47    }
48}
49
50/// Register this plugin's rule into the global `sdkt-audit` registry.
51#[cfg(not(target_arch = "wasm32"))]
52pub fn register() {
53    register_rule(Box::new(ExampleRule) as BoxedRule);
54}
55
56/// C-ABI exports for dynamic loading (, Phase B). Compiled only with the
57/// `plugins` feature — produces a loadable shared library artifact.
58#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
59mod plugin_abi;
60
61/// WASM JSON-ABI exports for sandboxed dynamic loading (, Phase C).
62#[cfg(all(feature = "wasm-plugins", target_arch = "wasm32"))]
63mod plugin_abi_wasm;
64
65#[cfg(all(test, not(target_arch = "wasm32")))]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn example_rule_fires_on_trigger_function() {
71        let scans = vec![FnScan {
72            fn_name: "sdkt_example_trigger_admin".to_string(),
73            require_auth: 0,
74            invoke_contract: 0,
75            bound: Default::default(),
76            usage: Default::default(),
77            division_before_multiplication: false,
78            external_call_before_state_write: false,
79        }];
80        let ctx = AuditContext { spec: None };
81        let mut report = AuditReport::default();
82        ExampleRule.check(&scans, &ctx, &mut report);
83        assert_eq!(report.summary.total, 1);
84        assert_eq!(report.findings[0].rule_id, "EXAMPLE-001");
85    }
86
87    #[test]
88    fn example_rule_silent_on_normal_function() {
89        let scans = vec![FnScan {
90            fn_name: "balance_of".to_string(),
91            require_auth: 0,
92            invoke_contract: 0,
93            bound: Default::default(),
94            usage: Default::default(),
95            division_before_multiplication: false,
96            external_call_before_state_write: false,
97        }];
98        let ctx = AuditContext { spec: None };
99        let mut report = AuditReport::default();
100        ExampleRule.check(&scans, &ctx, &mut report);
101        assert!(report.is_clean());
102    }
103}