use std::collections::HashMap;
pub use petgraph::graph::{Graph as VulnIRGraph, NodeIndex};
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ConfidenceValue(f64);
impl From<f64> for ConfidenceValue {
fn from(value: f64) -> Self {
Self(value.clamp(0.0, 1.0))
}
}
impl ConfidenceValue {
pub fn value(&self) -> f64 {
self.0
}
}
impl Default for ConfidenceValue {
fn default() -> Self {
Self(1.0)
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct Producer {
pub name: String,
pub kind: ProducerKind,
pub version: Option<String>,
}
impl Producer {
pub fn new(name: impl Into<String>, kind: ProducerKind) -> Self {
Self {
name: name.into(),
kind,
version: None,
}
}
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub enum ProducerKind {
#[default]
Static,
Dynamic,
Hybrid,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct Provenance {
pub producer: Producer,
pub metadata: HashMap<String, serde_json::Value>,
pub location: Option<SourceLocation>,
}
impl Provenance {
pub fn new(producer: Producer) -> Self {
Self {
producer,
metadata: HashMap::new(),
location: None,
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum VulnEdge {
TaintReach {
path: String,
confidence: ConfidenceValue,
observed_dynamically: bool,
transform_chain: Vec<String>,
evidence: Vec<Evidence>,
},
DataFlow {
confidence: ConfidenceValue,
},
ControlFlow,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum VulnNode {
TrustBoundary {
id: String,
description: String,
source_type: String,
confidence: ConfidenceValue,
taint_id: Option<String>,
created_ns: Option<u64>,
provenance: Vec<Provenance>,
metadata: Metadata,
},
Capability {
id: String,
resource: String,
permission: String,
target: Option<String>,
arguments: Vec<String>,
duration_ns: Option<u64>,
provenance: Vec<Provenance>,
metadata: Metadata,
},
DataSink {
id: String,
sink_type: String,
description: String,
provenance: Vec<Provenance>,
},
Source {
id: String,
source_type: String,
description: String,
provenance: Vec<Provenance>,
},
}
pub type Metadata = HashMap<String, serde_json::Value>;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Evidence {
pub kind: String,
pub data: serde_json::Value,
pub confidence: ConfidenceValue,
pub observed_dynamically: bool,
}
impl Evidence {
pub fn new(
kind: impl Into<String>,
data: impl Into<String>,
confidence: impl Into<ConfidenceValue>,
observed_dynamically: bool,
) -> Self {
Self {
kind: kind.into(),
data: serde_json::json!(data.into()),
confidence: confidence.into(),
observed_dynamically,
}
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct SourceLocation {
pub file: Option<String>,
pub line: Option<u32>,
pub column: Option<u32>,
pub artifact: Option<String>,
}
pub trait VulnProducer {
fn producer(&self) -> Producer;
fn create_provenance(&self, location: Option<SourceLocation>) -> Provenance {
let mut prov = Provenance::new(self.producer());
prov.location = location;
prov
}
}
pub use petgraph::Direction;
pub trait VulnIRGraphExt {
fn nodes(&self) -> impl Iterator<Item = (NodeIndex, &VulnNode)>;
fn edges(&self) -> impl Iterator<Item = (NodeIndex, NodeIndex, &VulnEdge)>;
fn node_count(&self) -> usize;
fn edge_count(&self) -> usize;
fn attack_surface(&self) -> Vec<(NodeIndex, &VulnNode)>;
fn reachable_capabilities(&self, _start: NodeIndex) -> Vec<(NodeIndex, &VulnNode)>;
}
impl VulnIRGraphExt for VulnIRGraph<VulnNode, VulnEdge> {
fn nodes(&self) -> impl Iterator<Item = (NodeIndex, &VulnNode)> {
self.node_indices().map(|idx| (idx, &self[idx]))
}
fn edges(&self) -> impl Iterator<Item = (NodeIndex, NodeIndex, &VulnEdge)> {
self.edge_indices()
.filter_map(|e| self.edge_endpoints(e).map(|(a, b)| (a, b, &self[e])))
}
fn node_count(&self) -> usize {
self.node_count()
}
fn edge_count(&self) -> usize {
self.edge_count()
}
fn attack_surface(&self) -> Vec<(NodeIndex, &VulnNode)> {
self.node_indices()
.filter_map(|idx| {
let node = &self[idx];
match node {
VulnNode::TrustBoundary { .. } => Some((idx, node)),
_ => None,
}
})
.collect()
}
fn reachable_capabilities(&self, _start: NodeIndex) -> Vec<(NodeIndex, &VulnNode)> {
self.node_indices()
.filter_map(|idx| {
let node = &self[idx];
match node {
VulnNode::Capability { .. } => Some((idx, node)),
_ => None,
}
})
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Severity {
Critical,
High,
Medium,
Low,
Info,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CweId(pub String);
impl CweId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
}