use std::fmt;
use semver::{Version, VersionReq};
use serde::{Deserialize, Serialize};
use crate::Severity;
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
)]
#[serde(rename_all = "lowercase")]
pub enum Ecosystem {
#[default]
Cargo,
Go,
Npm,
Pypi,
RubyGems,
Packagist,
NuGet,
Julia,
Swift,
Hex,
Maven,
#[serde(rename = "github-actions")]
GitHubActions,
}
impl Ecosystem {
pub fn is_cargo(&self) -> bool {
matches!(self, Ecosystem::Cargo)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
pub struct RepoId(pub String);
impl fmt::Display for RepoId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DependencyKind {
Direct,
Transitive,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DepSource {
#[default]
CratesIo,
Git {
url: String,
rev: Option<String>,
},
Path,
OtherRegistry {
url: String,
},
}
impl DepSource {
pub fn is_crates_io(&self) -> bool {
matches!(self, DepSource::CratesIo)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Occurrence {
InRepo {
repo: RepoId,
package: String,
installed: Version,
patched: Vec<VersionReq>,
dependency_kind: DependencyKind,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
dependency_path: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
active: Option<bool>,
#[serde(default, skip_serializing_if = "DepSource::is_crates_io")]
source: DepSource,
},
Toolchain {
channel: String,
installed: Option<Version>,
patched: Vec<VersionReq>,
},
}
impl Occurrence {
pub fn is_vulnerable(&self) -> bool {
match self {
Occurrence::InRepo {
installed, patched, ..
} => !patched.iter().any(|req| req.matches(installed)),
Occurrence::Toolchain {
installed, patched, ..
} => match installed {
Some(version) => !patched.iter().any(|req| req.matches(version)),
None => true,
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
pub struct Exploitability {
#[serde(default, skip_serializing_if = "is_false")]
pub kev: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub epss: Option<f32>,
}
fn is_false(value: &bool) -> bool {
!*value
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct VulnFinding {
pub advisory_id: String,
pub aliases: Vec<String>,
#[serde(default, skip_serializing_if = "Ecosystem::is_cargo")]
pub ecosystem: Ecosystem,
pub title: String,
pub severity: Severity,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cvss_score: Option<f32>,
pub url: Option<String>,
pub occurrences: Vec<Occurrence>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub affected_functions: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reachable: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reachability: Option<Reachability>,
#[serde(flatten)]
pub exploit: Exploitability,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Reachability {
pub verdict: ReachVerdict,
pub config: String,
pub engine: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub targets: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub witness: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ReachVerdict {
Reachable { witness: Vec<String> },
NotReachable,
Unknown { reason: String },
}
impl Reachability {
pub fn tier_c_unknown(reason: impl Into<String>) -> Self {
Reachability {
verdict: ReachVerdict::Unknown {
reason: reason.into(),
},
config: "package-level".to_string(),
engine: "fleetreach-tier-c".to_string(),
targets: Vec::new(),
witness: None,
}
}
pub fn as_legacy_bool(&self) -> Option<bool> {
match self.verdict {
ReachVerdict::Reachable { .. } => Some(true),
ReachVerdict::NotReachable => Some(false),
ReachVerdict::Unknown { .. } => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum WarnKind {
Unmaintained,
Yanked,
Unsound,
Notice,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WarnFinding {
pub kind: WarnKind,
pub advisory_id: Option<String>,
pub title: String,
pub occurrences: Vec<Occurrence>,
}