Skip to main content

agentshield/rules/
finding.rs

1use serde::{Deserialize, Serialize};
2
3use crate::ir::{data_surface::TaintPath, SourceLocation};
4
5/// A security finding produced by a detector.
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Finding {
8    /// Unique rule identifier (e.g., "SHIELD-001").
9    pub rule_id: String,
10    /// Human-readable rule name.
11    pub rule_name: String,
12    /// Severity level.
13    pub severity: Severity,
14    /// Confidence level (how certain we are this is a real issue).
15    pub confidence: Confidence,
16    /// MITRE ATT&CK-style category.
17    pub attack_category: AttackCategory,
18    /// Human-readable description of the finding.
19    pub message: String,
20    /// Primary source location.
21    pub location: Option<SourceLocation>,
22    /// Evidence supporting the finding.
23    pub evidence: Vec<Evidence>,
24    /// Taint path (if applicable).
25    pub taint_path: Option<TaintPath>,
26    /// Suggested remediation.
27    pub remediation: Option<String>,
28    /// CWE identifier (if applicable).
29    pub cwe_id: Option<String>,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
33#[serde(rename_all = "lowercase")]
34pub enum Severity {
35    Info,
36    Low,
37    Medium,
38    High,
39    Critical,
40}
41
42impl Severity {
43    pub fn from_str_lenient(s: &str) -> Option<Self> {
44        match s.to_lowercase().as_str() {
45            "info" => Some(Self::Info),
46            "low" => Some(Self::Low),
47            "medium" | "med" => Some(Self::Medium),
48            "high" => Some(Self::High),
49            "critical" | "crit" => Some(Self::Critical),
50            _ => None,
51        }
52    }
53}
54
55impl std::fmt::Display for Severity {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        match self {
58            Self::Info => write!(f, "info"),
59            Self::Low => write!(f, "low"),
60            Self::Medium => write!(f, "medium"),
61            Self::High => write!(f, "high"),
62            Self::Critical => write!(f, "critical"),
63        }
64    }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
68#[serde(rename_all = "lowercase")]
69pub enum Confidence {
70    Low,
71    Medium,
72    High,
73}
74
75impl std::fmt::Display for Confidence {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        match self {
78            Self::Low => write!(f, "low"),
79            Self::Medium => write!(f, "medium"),
80            Self::High => write!(f, "high"),
81        }
82    }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
86#[serde(rename_all = "snake_case")]
87pub enum AttackCategory {
88    CommandInjection,
89    CodeInjection,
90    CredentialExfiltration,
91    Ssrf,
92    ArbitraryFileAccess,
93    SupplyChain,
94    SelfModification,
95    PromptInjectionSurface,
96    ExcessivePermissions,
97    DataExfiltration,
98}
99
100impl std::fmt::Display for AttackCategory {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        match self {
103            Self::CommandInjection => write!(f, "Command Injection"),
104            Self::CodeInjection => write!(f, "Code Injection"),
105            Self::CredentialExfiltration => write!(f, "Credential Exfiltration"),
106            Self::Ssrf => write!(f, "SSRF"),
107            Self::ArbitraryFileAccess => write!(f, "Arbitrary File Access"),
108            Self::SupplyChain => write!(f, "Supply Chain"),
109            Self::SelfModification => write!(f, "Self-Modification"),
110            Self::PromptInjectionSurface => write!(f, "Prompt Injection Surface"),
111            Self::ExcessivePermissions => write!(f, "Excessive Permissions"),
112            Self::DataExfiltration => write!(f, "Data Exfiltration"),
113        }
114    }
115}
116
117/// Evidence supporting a finding.
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct Evidence {
120    pub description: String,
121    pub location: Option<SourceLocation>,
122    pub snippet: Option<String>,
123}
124
125/// Metadata about a detector rule, used for `list-rules` output.
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct RuleMetadata {
128    pub id: String,
129    pub name: String,
130    pub description: String,
131    pub default_severity: Severity,
132    pub attack_category: AttackCategory,
133    pub cwe_id: Option<String>,
134}