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