software-engineering 1.0.0

Software engineering metrics models, structs, calculations, and examples.
Documentation
//! # Security and Vulnerability Management Metrics
//!
//! Security debt is measured the same way technical debt is measured (see
//! [`crate::technical_debt`]): by severity and by how long it is carried.
//! For every discovered vulnerability, the primary metric is time from
//! discovery to genuine remediation — a deployed fix, not a closed ticket or
//! an unmerged patch — tracked against an explicit target that varies by
//! severity, commonly days for critical issues and weeks for lower-severity
//! ones.
//!
//! ## Formula
//!
//! ```text
//! Time to remediate  = remediated at − discovered at
//! Within target        when time to remediate ≤ remediation target for severity
//! ```
//!
//! ## Why it matters
//!
//! Using a standardized external scoring system, such as CVSS, as the
//! primary basis for severity classification resists the same lenient-drift
//! risk that inconsistent, purely internal judgement invites elsewhere in
//! this book: an externally anchored score is harder to quietly redefine
//! downward than a purely internal one. Building a genuinely non-punitive
//! disclosure culture matters just as much as the metric itself — an
//! engineer or researcher who reports a vulnerability is providing a
//! valuable service, and punishing disclosure reliably drives real risk
//! underground rather than into a managed remediation process.
//!
//! ## Example
//!
//! ```rust
//! use software_engineering::vulnerability_management::{
//!     VulnerabilitySeverity, remediation_target_days, time_to_remediate_days,
//!     is_remediated_within_target,
//! };
//!
//! // A critical vulnerability discovered on day 100, remediated on day 105.
//! let elapsed = time_to_remediate_days(100.0, 105.0);
//! assert_eq!(elapsed, 5.0);
//! assert_eq!(remediation_target_days(VulnerabilitySeverity::Critical), 7.0);
//! assert!(is_remediated_within_target(VulnerabilitySeverity::Critical, elapsed));
//!
//! // The same 5 days would miss no severity's target, but 10 days misses
//! // Critical's 7-day target.
//! assert!(!is_remediated_within_target(VulnerabilitySeverity::Critical, 10.0));
//! ```
//!
//! ## Pitfalls
//!
//! - **Measuring remediation to ticket-closed rather than genuinely
//!   deployed** — overstates how quickly real risk was actually reduced.
//! - **Tracking a raw, unweighted vulnerability count** instead of
//!   time-to-remediate by severity — hides whether the highest-risk items
//!   are being fixed fastest.
//! - **Relying entirely on internal, inconsistent severity judgement**
//!   instead of a standardized external scale like CVSS where one is
//!   available.
//! - **Punishing vulnerability disclosure**, internally or externally —
//!   discourages exactly the reporting the entire management system depends
//!   on.
//! - **Letting accepted-risk vulnerabilities disappear** into an invisible
//!   status instead of the same visible, quantified technical debt backlog
//!   used elsewhere.
//!
//! ## Sources
//!
//! - Chapter 6.4, Security and vulnerability management metrics.
//! - FIRST.org, *Common Vulnerability Scoring System (CVSS)*.
//!
//! Topic doc: software-engineering-metrics/locales/en-001/chapters/06-04-security-and-vulnerability-management-metrics.md

/// A vulnerability severity level, following a CVSS-like scale.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VulnerabilitySeverity {
    /// Data loss or corruption, security exposure, or complete feature
    /// unavailability.
    Critical,
    /// Significant impact short of critical.
    High,
    /// Moderate impact.
    Medium,
    /// Cosmetic or negligible functional impact.
    Low,
}

/// A common, documented remediation-time target for a severity level, in
/// days.
///
/// `Critical = 7.0`, `High = 30.0`, `Medium = 90.0`, `Low = 180.0` — days for
/// critical issues, weeks-equivalent for lower severities, per the chapter's
/// guidance. These are a common industry convention, not a universal
/// standard; adapt them to your own risk tolerance and, where applicable,
/// contractual or regulatory requirements.
///
/// # Arguments
///
/// * `severity` — the vulnerability's severity level.
///
/// # Returns
///
/// The target number of days to remediate.
///
/// # Examples
///
/// ```rust
/// use software_engineering::vulnerability_management::{
///     VulnerabilitySeverity, remediation_target_days,
/// };
///
/// assert_eq!(remediation_target_days(VulnerabilitySeverity::Critical), 7.0);
/// assert_eq!(remediation_target_days(VulnerabilitySeverity::Low), 180.0);
/// ```
#[must_use]
pub fn remediation_target_days(severity: VulnerabilitySeverity) -> f64 {
    match severity {
        VulnerabilitySeverity::Critical => 7.0,
        VulnerabilitySeverity::High => 30.0,
        VulnerabilitySeverity::Medium => 90.0,
        VulnerabilitySeverity::Low => 180.0,
    }
}

/// Time from discovery to genuine remediation — a deployed fix, not a
/// closed ticket or an unmerged patch.
///
/// `remediated_at_days − discovered_at_days`.
///
/// # Arguments
///
/// * `discovered_at_days` — the day the vulnerability was discovered, on any
///   consistent scale.
/// * `remediated_at_days` — the day the fix was genuinely deployed, on the
///   same scale.
///
/// # Returns
///
/// The elapsed remediation time, in days.
///
/// # Examples
///
/// ```rust
/// use software_engineering::vulnerability_management::time_to_remediate_days;
///
/// assert_eq!(time_to_remediate_days(100.0, 105.0), 5.0);
/// ```
#[must_use]
pub fn time_to_remediate_days(discovered_at_days: f64, remediated_at_days: f64) -> f64 {
    remediated_at_days - discovered_at_days
}

/// Whether an actual remediation time met the target for its severity.
///
/// `actual_days_to_remediate ≤ `[`remediation_target_days`]`(severity)`.
///
/// # Arguments
///
/// * `severity` — the vulnerability's severity level.
/// * `actual_days_to_remediate` — the actual elapsed remediation time, in
///   days (typically from [`time_to_remediate_days`]).
///
/// # Returns
///
/// `true` if the actual time is at or under the target, `false` otherwise.
///
/// # Examples
///
/// ```rust
/// use software_engineering::vulnerability_management::{
///     VulnerabilitySeverity, is_remediated_within_target,
/// };
///
/// assert!(is_remediated_within_target(VulnerabilitySeverity::Critical, 5.0));
/// assert!(!is_remediated_within_target(VulnerabilitySeverity::Critical, 10.0));
/// ```
#[must_use]
pub fn is_remediated_within_target(severity: VulnerabilitySeverity, actual_days_to_remediate: f64) -> bool {
    actual_days_to_remediate <= remediation_target_days(severity)
}

#[cfg(test)]
mod tests {
    use super::*;

    // "Set explicit remediation-time targets by severity, commonly measured
    // in days for critical issues and weeks for lower-severity ones."
    #[test]
    fn remediation_target_days_matches_documented_values_for_all_severities() {
        assert!((remediation_target_days(VulnerabilitySeverity::Critical) - 7.0).abs() < 1e-9);
        assert!((remediation_target_days(VulnerabilitySeverity::High) - 30.0).abs() < 1e-9);
        assert!((remediation_target_days(VulnerabilitySeverity::Medium) - 90.0).abs() < 1e-9);
        assert!((remediation_target_days(VulnerabilitySeverity::Low) - 180.0).abs() < 1e-9);
    }

    // "track the time from discovery to genuine remediation, not to a
    // ticket being closed or a fix being merged but not yet deployed."
    #[test]
    fn time_to_remediate_is_remediated_minus_discovered() {
        let elapsed = time_to_remediate_days(100.0, 105.0);
        assert!((elapsed - 5.0).abs() < 1e-9);
    }

    #[test]
    fn remediation_within_target_for_critical_and_high_severities() {
        assert!(is_remediated_within_target(VulnerabilitySeverity::Critical, 5.0));
        assert!(!is_remediated_within_target(VulnerabilitySeverity::Critical, 10.0));
        assert!(is_remediated_within_target(VulnerabilitySeverity::High, 20.0));
        assert!(!is_remediated_within_target(VulnerabilitySeverity::High, 45.0));
    }
}