1use std::collections::HashMap;
6
7pub use petgraph::graph::{Graph as VulnIRGraph, NodeIndex};
8
9#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
11pub struct ConfidenceValue(f64);
12
13impl From<f64> for ConfidenceValue {
14 fn from(value: f64) -> Self {
15 Self(value.clamp(0.0, 1.0))
16 }
17}
18
19impl ConfidenceValue {
20 pub fn value(&self) -> f64 {
21 self.0
22 }
23}
24
25impl Default for ConfidenceValue {
26 fn default() -> Self {
27 Self(1.0)
28 }
29}
30
31#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
33pub struct Producer {
34 pub name: String,
35 pub kind: ProducerKind,
36 pub version: Option<String>,
37}
38
39impl Producer {
40 pub fn new(name: impl Into<String>, kind: ProducerKind) -> Self {
41 Self {
42 name: name.into(),
43 kind,
44 version: None,
45 }
46 }
47
48 pub fn with_version(mut self, version: impl Into<String>) -> Self {
49 self.version = Some(version.into());
50 self
51 }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize, serde::Deserialize)]
56pub enum ProducerKind {
57 #[default]
58 Static,
59 Dynamic,
60 Hybrid,
61}
62
63#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
65pub struct Provenance {
66 pub producer: Producer,
67 pub metadata: HashMap<String, serde_json::Value>,
68 pub location: Option<SourceLocation>,
69}
70
71impl Provenance {
72 pub fn new(producer: Producer) -> Self {
73 Self {
74 producer,
75 metadata: HashMap::new(),
76 location: None,
77 }
78 }
79}
80
81#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
83pub enum VulnEdge {
84 TaintReach {
85 path: String,
86 confidence: ConfidenceValue,
87 observed_dynamically: bool,
88 transform_chain: Vec<String>,
89 evidence: Vec<Evidence>,
90 },
91 DataFlow {
92 confidence: ConfidenceValue,
93 },
94 ControlFlow,
95}
96
97#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
99pub enum VulnNode {
100 TrustBoundary {
101 id: String,
102 description: String,
103 source_type: String,
104 confidence: ConfidenceValue,
105 taint_id: Option<String>,
106 created_ns: Option<u64>,
107 provenance: Vec<Provenance>,
108 metadata: Metadata,
109 },
110 Capability {
111 id: String,
112 resource: String,
113 permission: String,
114 target: Option<String>,
115 arguments: Vec<String>,
116 duration_ns: Option<u64>,
117 provenance: Vec<Provenance>,
118 metadata: Metadata,
119 },
120 DataSink {
121 id: String,
122 sink_type: String,
123 description: String,
124 provenance: Vec<Provenance>,
125 },
126 Source {
127 id: String,
128 source_type: String,
129 description: String,
130 provenance: Vec<Provenance>,
131 },
132}
133
134pub type Metadata = HashMap<String, serde_json::Value>;
136
137#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
139pub struct Evidence {
140 pub kind: String,
141 pub data: serde_json::Value,
142 pub confidence: ConfidenceValue,
143 pub observed_dynamically: bool,
144}
145
146impl Evidence {
147 pub fn new(
148 kind: impl Into<String>,
149 data: impl Into<String>,
150 confidence: impl Into<ConfidenceValue>,
151 observed_dynamically: bool,
152 ) -> Self {
153 Self {
154 kind: kind.into(),
155 data: serde_json::json!(data.into()),
156 confidence: confidence.into(),
157 observed_dynamically,
158 }
159 }
160}
161
162#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
164pub struct SourceLocation {
165 pub file: Option<String>,
166 pub line: Option<u32>,
167 pub column: Option<u32>,
168 pub artifact: Option<String>,
169}
170
171pub trait VulnProducer {
173 fn producer(&self) -> Producer;
174
175 fn create_provenance(&self, location: Option<SourceLocation>) -> Provenance {
176 let mut prov = Provenance::new(self.producer());
177 prov.location = location;
178 prov
179 }
180}
181
182pub use petgraph::Direction;
184
185pub trait VulnIRGraphExt {
187 fn nodes(&self) -> impl Iterator<Item = (NodeIndex, &VulnNode)>;
188 fn edges(&self) -> impl Iterator<Item = (NodeIndex, NodeIndex, &VulnEdge)>;
189 fn node_count(&self) -> usize;
190 fn edge_count(&self) -> usize;
191 fn attack_surface(&self) -> Vec<(NodeIndex, &VulnNode)>;
193 fn reachable_capabilities(&self, _start: NodeIndex) -> Vec<(NodeIndex, &VulnNode)>;
195}
196
197impl VulnIRGraphExt for VulnIRGraph<VulnNode, VulnEdge> {
198 fn nodes(&self) -> impl Iterator<Item = (NodeIndex, &VulnNode)> {
199 self.node_indices().map(|idx| (idx, &self[idx]))
200 }
201
202 fn edges(&self) -> impl Iterator<Item = (NodeIndex, NodeIndex, &VulnEdge)> {
203 self.edge_indices()
204 .filter_map(|e| self.edge_endpoints(e).map(|(a, b)| (a, b, &self[e])))
205 }
206
207 fn node_count(&self) -> usize {
208 self.node_count()
209 }
210
211 fn edge_count(&self) -> usize {
212 self.edge_count()
213 }
214
215 fn attack_surface(&self) -> Vec<(NodeIndex, &VulnNode)> {
216 self.node_indices()
217 .filter_map(|idx| {
218 let node = &self[idx];
219 match node {
220 VulnNode::TrustBoundary { .. } => Some((idx, node)),
221 _ => None,
222 }
223 })
224 .collect()
225 }
226
227 fn reachable_capabilities(&self, _start: NodeIndex) -> Vec<(NodeIndex, &VulnNode)> {
228 self.node_indices()
229 .filter_map(|idx| {
230 let node = &self[idx];
231 match node {
232 VulnNode::Capability { .. } => Some((idx, node)),
233 _ => None,
234 }
235 })
236 .collect()
237 }
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
242pub enum Severity {
243 Critical,
244 High,
245 Medium,
246 Low,
247 Info,
248}
249
250#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
252pub struct CweId(pub String);
253
254impl CweId {
255 pub fn new(id: impl Into<String>) -> Self {
256 Self(id.into())
257 }
258}