1use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::fmt;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct VulnerabilityRef {
10 pub id: String,
12 pub source: VulnerabilitySource,
14 pub severity: Option<Severity>,
16 pub cvss: Vec<CvssScore>,
18 pub affected_versions: Vec<String>,
20 pub remediation: Option<Remediation>,
22 pub description: Option<String>,
24 pub cwes: Vec<String>,
26 pub published: Option<DateTime<Utc>>,
28 pub modified: Option<DateTime<Utc>>,
30 pub is_kev: bool,
32 pub kev_info: Option<KevInfo>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct KevInfo {
39 pub date_added: DateTime<Utc>,
41 pub due_date: DateTime<Utc>,
43 pub known_ransomware_use: bool,
45 pub required_action: String,
47 pub vendor_project: Option<String>,
49 pub product: Option<String>,
51}
52
53impl KevInfo {
54 pub fn new(date_added: DateTime<Utc>, due_date: DateTime<Utc>, required_action: String) -> Self {
56 Self {
57 date_added,
58 due_date,
59 known_ransomware_use: false,
60 required_action,
61 vendor_project: None,
62 product: None,
63 }
64 }
65
66 pub fn is_overdue(&self) -> bool {
68 Utc::now() > self.due_date
69 }
70
71 pub fn days_until_due(&self) -> i64 {
73 (self.due_date - Utc::now()).num_days()
74 }
75}
76
77impl VulnerabilityRef {
78 pub fn new(id: String, source: VulnerabilitySource) -> Self {
80 Self {
81 id,
82 source,
83 severity: None,
84 cvss: Vec::new(),
85 affected_versions: Vec::new(),
86 remediation: None,
87 description: None,
88 cwes: Vec::new(),
89 published: None,
90 modified: None,
91 is_kev: false,
92 kev_info: None,
93 }
94 }
95
96 pub fn is_actively_exploited(&self) -> bool {
98 self.is_kev
99 }
100
101 pub fn is_ransomware_related(&self) -> bool {
103 self.kev_info
104 .as_ref()
105 .map(|k| k.known_ransomware_use)
106 .unwrap_or(false)
107 }
108
109 pub fn max_cvss_score(&self) -> Option<f32> {
111 self.cvss
112 .iter()
113 .map(|c| c.base_score)
114 .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
115 }
116}
117
118impl PartialEq for VulnerabilityRef {
119 fn eq(&self, other: &Self) -> bool {
120 self.id == other.id && self.source == other.source
121 }
122}
123
124impl Eq for VulnerabilityRef {}
125
126impl std::hash::Hash for VulnerabilityRef {
127 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
128 self.id.hash(state);
129 self.source.hash(state);
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
135pub enum VulnerabilitySource {
136 Nvd,
137 Ghsa,
138 Osv,
139 Snyk,
140 Sonatype,
141 VulnDb,
142 Cve,
143 Other(String),
144}
145
146impl fmt::Display for VulnerabilitySource {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 match self {
149 VulnerabilitySource::Nvd => write!(f, "NVD"),
150 VulnerabilitySource::Ghsa => write!(f, "GHSA"),
151 VulnerabilitySource::Osv => write!(f, "OSV"),
152 VulnerabilitySource::Snyk => write!(f, "Snyk"),
153 VulnerabilitySource::Sonatype => write!(f, "Sonatype"),
154 VulnerabilitySource::VulnDb => write!(f, "VulnDB"),
155 VulnerabilitySource::Cve => write!(f, "CVE"),
156 VulnerabilitySource::Other(s) => write!(f, "{}", s),
157 }
158 }
159}
160
161#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
163pub enum Severity {
164 Critical,
165 High,
166 Medium,
167 Low,
168 Info,
169 None,
170 #[default]
171 Unknown,
172}
173
174impl Severity {
175 pub fn from_cvss(score: f32) -> Self {
177 match score {
178 s if s >= 9.0 => Severity::Critical,
179 s if s >= 7.0 => Severity::High,
180 s if s >= 4.0 => Severity::Medium,
181 s if s >= 0.1 => Severity::Low,
182 0.0 => Severity::None,
183 _ => Severity::Unknown,
184 }
185 }
186
187 pub fn priority(&self) -> u8 {
189 match self {
190 Severity::Critical => 0,
191 Severity::High => 1,
192 Severity::Medium => 2,
193 Severity::Low => 3,
194 Severity::Info => 4,
195 Severity::None => 5,
196 Severity::Unknown => 6,
197 }
198 }
199}
200
201impl fmt::Display for Severity {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 match self {
204 Severity::Critical => write!(f, "Critical"),
205 Severity::High => write!(f, "High"),
206 Severity::Medium => write!(f, "Medium"),
207 Severity::Low => write!(f, "Low"),
208 Severity::Info => write!(f, "Info"),
209 Severity::None => write!(f, "None"),
210 Severity::Unknown => write!(f, "Unknown"),
211 }
212 }
213}
214
215
216#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct CvssScore {
219 pub version: CvssVersion,
221 pub base_score: f32,
223 pub vector: Option<String>,
225 pub exploitability_score: Option<f32>,
227 pub impact_score: Option<f32>,
229}
230
231impl CvssScore {
232 pub fn new(version: CvssVersion, base_score: f32) -> Self {
234 Self {
235 version,
236 base_score,
237 vector: None,
238 exploitability_score: None,
239 impact_score: None,
240 }
241 }
242}
243
244#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
246pub enum CvssVersion {
247 V2,
248 V3,
249 V31,
250 V4,
251}
252
253impl fmt::Display for CvssVersion {
254 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255 match self {
256 CvssVersion::V2 => write!(f, "2.0"),
257 CvssVersion::V3 => write!(f, "3.0"),
258 CvssVersion::V31 => write!(f, "3.1"),
259 CvssVersion::V4 => write!(f, "4.0"),
260 }
261 }
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct Remediation {
267 pub remediation_type: RemediationType,
269 pub description: Option<String>,
271 pub fixed_version: Option<String>,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
277pub enum RemediationType {
278 Patch,
279 Upgrade,
280 Workaround,
281 Mitigation,
282 None,
283}
284
285impl fmt::Display for RemediationType {
286 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287 match self {
288 RemediationType::Patch => write!(f, "Patch"),
289 RemediationType::Upgrade => write!(f, "Upgrade"),
290 RemediationType::Workaround => write!(f, "Workaround"),
291 RemediationType::Mitigation => write!(f, "Mitigation"),
292 RemediationType::None => write!(f, "None"),
293 }
294 }
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct VexStatus {
300 pub status: VexState,
302 pub justification: Option<VexJustification>,
304 pub action_statement: Option<String>,
306 pub impact_statement: Option<String>,
308 pub response: Option<VexResponse>,
310 pub detail: Option<String>,
312}
313
314impl VexStatus {
315 pub fn new(status: VexState) -> Self {
317 Self {
318 status,
319 justification: None,
320 action_statement: None,
321 impact_statement: None,
322 response: None,
323 detail: None,
324 }
325 }
326}
327
328#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
330pub enum VexState {
331 Affected,
332 NotAffected,
333 Fixed,
334 UnderInvestigation,
335}
336
337impl fmt::Display for VexState {
338 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339 match self {
340 VexState::Affected => write!(f, "Affected"),
341 VexState::NotAffected => write!(f, "Not Affected"),
342 VexState::Fixed => write!(f, "Fixed"),
343 VexState::UnderInvestigation => write!(f, "Under Investigation"),
344 }
345 }
346}
347
348#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
350pub enum VexJustification {
351 ComponentNotPresent,
352 VulnerableCodeNotPresent,
353 VulnerableCodeNotInExecutePath,
354 VulnerableCodeCannotBeControlledByAdversary,
355 InlineMitigationsAlreadyExist,
356}
357
358impl fmt::Display for VexJustification {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 match self {
361 VexJustification::ComponentNotPresent => write!(f, "Component not present"),
362 VexJustification::VulnerableCodeNotPresent => write!(f, "Vulnerable code not present"),
363 VexJustification::VulnerableCodeNotInExecutePath => {
364 write!(f, "Vulnerable code not in execute path")
365 }
366 VexJustification::VulnerableCodeCannotBeControlledByAdversary => {
367 write!(f, "Vulnerable code cannot be controlled by adversary")
368 }
369 VexJustification::InlineMitigationsAlreadyExist => {
370 write!(f, "Inline mitigations already exist")
371 }
372 }
373 }
374}
375
376#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
378pub enum VexResponse {
379 CanNotFix,
380 WillNotFix,
381 Update,
382 Rollback,
383 Workaround,
384}
385
386impl fmt::Display for VexResponse {
387 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388 match self {
389 VexResponse::CanNotFix => write!(f, "Can Not Fix"),
390 VexResponse::WillNotFix => write!(f, "Will Not Fix"),
391 VexResponse::Update => write!(f, "Update"),
392 VexResponse::Rollback => write!(f, "Rollback"),
393 VexResponse::Workaround => write!(f, "Workaround"),
394 }
395 }
396}