Skip to main content

vulnir/
lib.rs

1//! Minimal stub implementation of vulnir for jsdet-core tests.
2//!
3//! This provides just enough types to satisfy the imports in jsdet-core.
4
5use std::collections::HashMap;
6
7pub use petgraph::graph::{Graph as VulnIRGraph, NodeIndex};
8
9/// Confidence value for assertions.
10#[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/// Producer of vulnerability information.
32#[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/// Type of producer.
55#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize, serde::Deserialize)]
56pub enum ProducerKind {
57    #[default]
58    Static,
59    Dynamic,
60    Hybrid,
61}
62
63/// Provenance information.
64#[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/// A vulnerability graph edge.
82#[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/// A vulnerability graph node.
98#[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
134/// Metadata for nodes.
135pub type Metadata = HashMap<String, serde_json::Value>;
136
137/// Evidence for assertions.
138#[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/// Source location information.
163#[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
171/// Trait for vulnerability producers.
172pub 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
182// Re-export petgraph types for graph manipulation
183pub use petgraph::Direction;
184
185/// Extension trait for VulnIRGraph.
186pub 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    /// Returns nodes that represent attack surfaces (TrustBoundary nodes).
192    fn attack_surface(&self) -> Vec<(NodeIndex, &VulnNode)>;
193    /// Returns capabilities reachable from a given node.
194    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/// Severity levels.
241#[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/// CWE (Common Weakness Enumeration) identifier.
251#[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}