vsec 0.0.1

Detect secrets and in Rust codebases
Documentation
// src/models/finding.rs

use std::collections::HashMap;
use std::path::PathBuf;

use crate::models::{AnalysisContext, Comparison, Score};

/// Represents a potential secret finding
#[derive(Debug, Clone)]
pub struct Finding {
    /// Unique identifier for this finding
    pub id: FindingId,

    /// The constant or literal that was flagged
    pub suspect: SuspectValue,

    /// Where this was found
    pub location: SourceLocation,

    /// How this value is used (if detected)
    pub usage: Option<Usage>,

    /// Context at time of detection
    pub context: AnalysisContext,

    /// Calculated risk score with breakdown
    pub score: Score,

    /// Human-readable explanation
    pub explanation: String,

    /// Suggested remediation
    pub remediation: Option<String>,

    /// Additional metadata (e.g., git commit info for history scanning)
    pub metadata: HashMap<String, String>,
}

/// Newtype for finding IDs (enables type-safe handling)
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct FindingId(pub String);

impl FindingId {
    /// Create a new FindingId with a given string (for testing)
    pub fn new(id: impl Into<String>) -> Self {
        Self(id.into())
    }

    pub fn generate(location: &SourceLocation, name: &str) -> Self {
        use std::collections::hash_map::DefaultHasher;
        use std::hash::{Hash, Hasher};

        let mut hasher = DefaultHasher::new();
        location.file.hash(&mut hasher);
        location.line.hash(&mut hasher);
        name.hash(&mut hasher);

        Self(format!("SEC-{:016x}", hasher.finish()))
    }
}

/// The value that triggered the finding
#[derive(Debug, Clone)]
pub enum SuspectValue {
    /// A named constant: `const FOO: &str = "..."`
    Constant {
        name: String,
        value: String,
        type_annotation: Option<String>,
    },

    /// A static: `static SECRET: &str = "..."`
    Static {
        name: String,
        value: String,
        is_mut: bool,
    },

    /// An inline literal in a comparison: `if x == "secret"`
    InlineLiteral { value: String },

    /// Environment variable with default: `env::var("X").unwrap_or("default")`
    EnvDefault {
        var_name: String,
        default_value: String,
    },
}

impl SuspectValue {
    /// Get the name for scoring purposes
    pub fn name(&self) -> Option<&str> {
        match self {
            Self::Constant { name, .. } => Some(name),
            Self::Static { name, .. } => Some(name),
            Self::InlineLiteral { .. } => None,
            Self::EnvDefault { var_name, .. } => Some(var_name),
        }
    }

    /// Get the actual value
    pub fn value(&self) -> &str {
        match self {
            Self::Constant { value, .. } => value,
            Self::Static { value, .. } => value,
            Self::InlineLiteral { value } => value,
            Self::EnvDefault { default_value, .. } => default_value,
        }
    }
}

/// Source code location
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SourceLocation {
    pub file: PathBuf,
    pub line: u32,
    pub column: u32,
    pub span_start: usize,
    pub span_end: usize,
}

impl SourceLocation {
    pub fn new(file: PathBuf, line: u32, column: u32) -> Self {
        Self {
            file,
            line,
            column,
            span_start: 0,
            span_end: 0,
        }
    }

    pub fn from_span(file: PathBuf, span: proc_macro2::Span) -> Self {
        let start = span.start();
        Self {
            file,
            line: start.line as u32,
            column: start.column as u32,
            span_start: 0,
            span_end: 0,
        }
    }
}

/// How the suspect value is used
#[derive(Debug, Clone)]
pub enum Usage {
    /// Used in an equality comparison
    Comparison(Comparison),

    /// Passed to a function as an argument
    FunctionArgument {
        function_name: String,
        argument_position: usize,
    },

    /// Passed to a method as an argument (e.g., `req.insert("auth", token)`)
    MethodArgument {
        receiver: String,
        method_name: String,
        argument_position: usize,
    },

    /// Returned from a function
    ReturnValue { function_name: String },

    /// Assigned to a struct field during initialization (e.g., `Config { api_key: "secret" }`)
    StructFieldInit {
        struct_name: Option<String>,
        field_name: String,
    },

    /// Assigned to a field via assignment (e.g., `self.token = "secret"`)
    FieldAssignment {
        struct_name: Option<String>,
        field_name: String,
    },

    /// Used in a match arm pattern or guard
    MatchArm {
        match_arm_index: usize,
        in_guard: bool,
    },

    /// Used in pattern matching (legacy alias)
    PatternMatch { match_arm_index: usize },

    /// Used inside a closure body
    ClosureCapture {
        /// If the closure is passed to a known function
        passed_to: Option<String>,
    },

    /// Element in an array or vec literal
    ArrayElement {
        index: usize,
        array_len: usize,
    },

    /// Passed to a macro invocation
    MacroArgument {
        macro_name: String,
        argument_position: usize,
    },

    /// Used in a builder pattern method chain
    BuilderMethod {
        method_name: String,
        builder_type: Option<String>,
    },

    /// Unknown usage (just defined)
    Definition,
}