software_engineering/vulnerability_management.rs
1//! # Security and Vulnerability Management Metrics
2//!
3//! Security debt is measured the same way technical debt is measured (see
4//! [`crate::technical_debt`]): by severity and by how long it is carried.
5//! For every discovered vulnerability, the primary metric is time from
6//! discovery to genuine remediation — a deployed fix, not a closed ticket or
7//! an unmerged patch — tracked against an explicit target that varies by
8//! severity, commonly days for critical issues and weeks for lower-severity
9//! ones.
10//!
11//! ## Formula
12//!
13//! ```text
14//! Time to remediate = remediated at − discovered at
15//! Within target when time to remediate ≤ remediation target for severity
16//! ```
17//!
18//! ## Why it matters
19//!
20//! Using a standardized external scoring system, such as CVSS, as the
21//! primary basis for severity classification resists the same lenient-drift
22//! risk that inconsistent, purely internal judgement invites elsewhere in
23//! this book: an externally anchored score is harder to quietly redefine
24//! downward than a purely internal one. Building a genuinely non-punitive
25//! disclosure culture matters just as much as the metric itself — an
26//! engineer or researcher who reports a vulnerability is providing a
27//! valuable service, and punishing disclosure reliably drives real risk
28//! underground rather than into a managed remediation process.
29//!
30//! ## Example
31//!
32//! ```rust
33//! use software_engineering::vulnerability_management::{
34//! VulnerabilitySeverity, remediation_target_days, time_to_remediate_days,
35//! is_remediated_within_target,
36//! };
37//!
38//! // A critical vulnerability discovered on day 100, remediated on day 105.
39//! let elapsed = time_to_remediate_days(100.0, 105.0);
40//! assert_eq!(elapsed, 5.0);
41//! assert_eq!(remediation_target_days(VulnerabilitySeverity::Critical), 7.0);
42//! assert!(is_remediated_within_target(VulnerabilitySeverity::Critical, elapsed));
43//!
44//! // The same 5 days would miss no severity's target, but 10 days misses
45//! // Critical's 7-day target.
46//! assert!(!is_remediated_within_target(VulnerabilitySeverity::Critical, 10.0));
47//! ```
48//!
49//! ## Pitfalls
50//!
51//! - **Measuring remediation to ticket-closed rather than genuinely
52//! deployed** — overstates how quickly real risk was actually reduced.
53//! - **Tracking a raw, unweighted vulnerability count** instead of
54//! time-to-remediate by severity — hides whether the highest-risk items
55//! are being fixed fastest.
56//! - **Relying entirely on internal, inconsistent severity judgement**
57//! instead of a standardized external scale like CVSS where one is
58//! available.
59//! - **Punishing vulnerability disclosure**, internally or externally —
60//! discourages exactly the reporting the entire management system depends
61//! on.
62//! - **Letting accepted-risk vulnerabilities disappear** into an invisible
63//! status instead of the same visible, quantified technical debt backlog
64//! used elsewhere.
65//!
66//! ## Sources
67//!
68//! - Chapter 6.4, Security and vulnerability management metrics.
69//! - FIRST.org, *Common Vulnerability Scoring System (CVSS)*.
70//!
71//! Topic doc: software-engineering-metrics/locales/en-001/chapters/06-04-security-and-vulnerability-management-metrics.md
72
73/// A vulnerability severity level, following a CVSS-like scale.
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
75pub enum VulnerabilitySeverity {
76 /// Data loss or corruption, security exposure, or complete feature
77 /// unavailability.
78 Critical,
79 /// Significant impact short of critical.
80 High,
81 /// Moderate impact.
82 Medium,
83 /// Cosmetic or negligible functional impact.
84 Low,
85}
86
87/// A common, documented remediation-time target for a severity level, in
88/// days.
89///
90/// `Critical = 7.0`, `High = 30.0`, `Medium = 90.0`, `Low = 180.0` — days for
91/// critical issues, weeks-equivalent for lower severities, per the chapter's
92/// guidance. These are a common industry convention, not a universal
93/// standard; adapt them to your own risk tolerance and, where applicable,
94/// contractual or regulatory requirements.
95///
96/// # Arguments
97///
98/// * `severity` — the vulnerability's severity level.
99///
100/// # Returns
101///
102/// The target number of days to remediate.
103///
104/// # Examples
105///
106/// ```rust
107/// use software_engineering::vulnerability_management::{
108/// VulnerabilitySeverity, remediation_target_days,
109/// };
110///
111/// assert_eq!(remediation_target_days(VulnerabilitySeverity::Critical), 7.0);
112/// assert_eq!(remediation_target_days(VulnerabilitySeverity::Low), 180.0);
113/// ```
114#[must_use]
115pub fn remediation_target_days(severity: VulnerabilitySeverity) -> f64 {
116 match severity {
117 VulnerabilitySeverity::Critical => 7.0,
118 VulnerabilitySeverity::High => 30.0,
119 VulnerabilitySeverity::Medium => 90.0,
120 VulnerabilitySeverity::Low => 180.0,
121 }
122}
123
124/// Time from discovery to genuine remediation — a deployed fix, not a
125/// closed ticket or an unmerged patch.
126///
127/// `remediated_at_days − discovered_at_days`.
128///
129/// # Arguments
130///
131/// * `discovered_at_days` — the day the vulnerability was discovered, on any
132/// consistent scale.
133/// * `remediated_at_days` — the day the fix was genuinely deployed, on the
134/// same scale.
135///
136/// # Returns
137///
138/// The elapsed remediation time, in days.
139///
140/// # Examples
141///
142/// ```rust
143/// use software_engineering::vulnerability_management::time_to_remediate_days;
144///
145/// assert_eq!(time_to_remediate_days(100.0, 105.0), 5.0);
146/// ```
147#[must_use]
148pub fn time_to_remediate_days(discovered_at_days: f64, remediated_at_days: f64) -> f64 {
149 remediated_at_days - discovered_at_days
150}
151
152/// Whether an actual remediation time met the target for its severity.
153///
154/// `actual_days_to_remediate ≤ `[`remediation_target_days`]`(severity)`.
155///
156/// # Arguments
157///
158/// * `severity` — the vulnerability's severity level.
159/// * `actual_days_to_remediate` — the actual elapsed remediation time, in
160/// days (typically from [`time_to_remediate_days`]).
161///
162/// # Returns
163///
164/// `true` if the actual time is at or under the target, `false` otherwise.
165///
166/// # Examples
167///
168/// ```rust
169/// use software_engineering::vulnerability_management::{
170/// VulnerabilitySeverity, is_remediated_within_target,
171/// };
172///
173/// assert!(is_remediated_within_target(VulnerabilitySeverity::Critical, 5.0));
174/// assert!(!is_remediated_within_target(VulnerabilitySeverity::Critical, 10.0));
175/// ```
176#[must_use]
177pub fn is_remediated_within_target(severity: VulnerabilitySeverity, actual_days_to_remediate: f64) -> bool {
178 actual_days_to_remediate <= remediation_target_days(severity)
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 // "Set explicit remediation-time targets by severity, commonly measured
186 // in days for critical issues and weeks for lower-severity ones."
187 #[test]
188 fn remediation_target_days_matches_documented_values_for_all_severities() {
189 assert!((remediation_target_days(VulnerabilitySeverity::Critical) - 7.0).abs() < 1e-9);
190 assert!((remediation_target_days(VulnerabilitySeverity::High) - 30.0).abs() < 1e-9);
191 assert!((remediation_target_days(VulnerabilitySeverity::Medium) - 90.0).abs() < 1e-9);
192 assert!((remediation_target_days(VulnerabilitySeverity::Low) - 180.0).abs() < 1e-9);
193 }
194
195 // "track the time from discovery to genuine remediation, not to a
196 // ticket being closed or a fix being merged but not yet deployed."
197 #[test]
198 fn time_to_remediate_is_remediated_minus_discovered() {
199 let elapsed = time_to_remediate_days(100.0, 105.0);
200 assert!((elapsed - 5.0).abs() < 1e-9);
201 }
202
203 #[test]
204 fn remediation_within_target_for_critical_and_high_severities() {
205 assert!(is_remediated_within_target(VulnerabilitySeverity::Critical, 5.0));
206 assert!(!is_remediated_within_target(VulnerabilitySeverity::Critical, 10.0));
207 assert!(is_remediated_within_target(VulnerabilitySeverity::High, 20.0));
208 assert!(!is_remediated_within_target(VulnerabilitySeverity::High, 45.0));
209 }
210}