vsec 0.0.1

Detect secrets and in Rust codebases
Documentation
// src/scoring/factors.rs

/// Weight configuration for different factor categories
#[derive(Debug, Clone)]
pub struct FactorWeights {
    /// Weight multiplier for base scores
    pub base_weight: f32,

    /// Weight multiplier for name-based factors
    pub name_weight: f32,

    /// Weight multiplier for value analysis factors
    pub value_weight: f32,

    /// Weight multiplier for context factors
    pub context_weight: f32,

    /// Weight multiplier for consequence factors
    pub consequence_weight: f32,

    /// Weight multiplier for RHS factors
    pub rhs_weight: f32,

    /// Weight multiplier for pattern match factors
    pub pattern_weight: f32,
}

impl Default for FactorWeights {
    fn default() -> Self {
        Self {
            base_weight: 1.0,
            name_weight: 1.0,
            value_weight: 1.0,
            context_weight: 1.0,
            consequence_weight: 1.0,
            rhs_weight: 1.0,
            pattern_weight: 1.0,
        }
    }
}

impl FactorWeights {
    /// Create weights that emphasize name analysis
    pub fn name_focused() -> Self {
        Self {
            name_weight: 1.5,
            ..Default::default()
        }
    }

    /// Create weights that emphasize value analysis
    pub fn value_focused() -> Self {
        Self {
            value_weight: 1.5,
            pattern_weight: 1.5,
            ..Default::default()
        }
    }

    /// Create weights that emphasize context
    pub fn context_focused() -> Self {
        Self {
            context_weight: 1.5,
            consequence_weight: 1.5,
            rhs_weight: 1.5,
            ..Default::default()
        }
    }

    /// Apply weight to a score based on category
    pub fn apply(&self, category: &crate::models::FactorCategory, score: i32) -> i32 {
        let weight = match category {
            crate::models::FactorCategory::Base => self.base_weight,
            crate::models::FactorCategory::Name => self.name_weight,
            crate::models::FactorCategory::ValueAnalysis => self.value_weight,
            crate::models::FactorCategory::Context => self.context_weight,
            crate::models::FactorCategory::Consequence => self.consequence_weight,
            crate::models::FactorCategory::RightHandSide => self.rhs_weight,
            crate::models::FactorCategory::Pattern => self.pattern_weight,
            // Suppression and Custom use base weight
            crate::models::FactorCategory::Suppression
            | crate::models::FactorCategory::Custom => self.base_weight,
        };

        (score as f32 * weight).round() as i32
    }
}

/// Predefined factor definitions for common patterns
pub struct PredefinedFactors;

impl PredefinedFactors {
    /// Factor for high entropy values
    pub const HIGH_ENTROPY: (&'static str, i32, &'static str) =
        ("high_entropy", 20, "Value has high entropy (looks random)");

    /// Factor for low entropy values
    pub const LOW_ENTROPY: (&'static str, i32, &'static str) =
        ("low_entropy", -15, "Value has low entropy (looks like text)");

    /// Factor for short values
    pub const SHORT_VALUE: (&'static str, i32, &'static str) =
        ("short_value", -20, "Value is very short (likely not a secret)");

    /// Factor for long values
    pub const LONG_VALUE: (&'static str, i32, &'static str) =
        ("long_value", 15, "Value is long (may be a key/token)");

    /// Factor for base64 patterns
    pub const BASE64_PATTERN: (&'static str, i32, &'static str) =
        ("base64_pattern", 15, "Value looks like base64 (may be encoded secret)");

    /// Factor for hex patterns
    pub const HEX_PATTERN: (&'static str, i32, &'static str) =
        ("hex_pattern", 15, "Value looks like hex-encoded data (may be a key)");

    /// Factor for UUID patterns
    pub const UUID_PATTERN: (&'static str, i32, &'static str) =
        ("uuid_pattern", -10, "Value looks like a UUID (often not sensitive)");

    /// Factor for version patterns
    pub const VERSION_PATTERN: (&'static str, i32, &'static str) =
        ("version_pattern", -30, "Value looks like a version string");

    /// Factor for placeholder patterns
    pub const PLACEHOLDER: (&'static str, i32, &'static str) =
        ("placeholder", -50, "Value appears to be a placeholder");

    /// Factor for test context
    pub const TEST_CONTEXT: (&'static str, i32, &'static str) =
        ("test_context", -100, "Code is in a test context (mock data expected)");

    /// Factor for auth state changes
    pub const AUTH_STATE_CHANGE: (&'static str, i32, &'static str) =
        ("auth_state_change", 40, "Comparison triggers auth state modification");

    /// Factor for empty block consequences
    pub const EMPTY_BLOCK: (&'static str, i32, &'static str) =
        ("empty_block", -40, "Comparison consequence is an empty block");
}

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

    #[test]
    fn test_default_weights() {
        let weights = FactorWeights::default();
        assert_eq!(weights.apply(&FactorCategory::Base, 50), 50);
        assert_eq!(weights.apply(&FactorCategory::Name, 20), 20);
    }

    #[test]
    fn test_weighted_scoring() {
        let weights = FactorWeights {
            name_weight: 2.0,
            ..Default::default()
        };
        assert_eq!(weights.apply(&FactorCategory::Name, 20), 40);
        assert_eq!(weights.apply(&FactorCategory::Base, 50), 50);
    }
}