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