#[derive(Debug, Clone)]
pub struct FactorWeights {
pub base_weight: f32,
pub name_weight: f32,
pub value_weight: f32,
pub context_weight: f32,
pub consequence_weight: f32,
pub rhs_weight: f32,
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 {
pub fn name_focused() -> Self {
Self {
name_weight: 1.5,
..Default::default()
}
}
pub fn value_focused() -> Self {
Self {
value_weight: 1.5,
pattern_weight: 1.5,
..Default::default()
}
}
pub fn context_focused() -> Self {
Self {
context_weight: 1.5,
consequence_weight: 1.5,
rhs_weight: 1.5,
..Default::default()
}
}
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,
crate::models::FactorCategory::Suppression
| crate::models::FactorCategory::Custom => self.base_weight,
};
(score as f32 * weight).round() as i32
}
}
pub struct PredefinedFactors;
impl PredefinedFactors {
pub const HIGH_ENTROPY: (&'static str, i32, &'static str) =
("high_entropy", 20, "Value has high entropy (looks random)");
pub const LOW_ENTROPY: (&'static str, i32, &'static str) =
("low_entropy", -15, "Value has low entropy (looks like text)");
pub const SHORT_VALUE: (&'static str, i32, &'static str) =
("short_value", -20, "Value is very short (likely not a secret)");
pub const LONG_VALUE: (&'static str, i32, &'static str) =
("long_value", 15, "Value is long (may be a key/token)");
pub const BASE64_PATTERN: (&'static str, i32, &'static str) =
("base64_pattern", 15, "Value looks like base64 (may be encoded secret)");
pub const HEX_PATTERN: (&'static str, i32, &'static str) =
("hex_pattern", 15, "Value looks like hex-encoded data (may be a key)");
pub const UUID_PATTERN: (&'static str, i32, &'static str) =
("uuid_pattern", -10, "Value looks like a UUID (often not sensitive)");
pub const VERSION_PATTERN: (&'static str, i32, &'static str) =
("version_pattern", -30, "Value looks like a version string");
pub const PLACEHOLDER: (&'static str, i32, &'static str) =
("placeholder", -50, "Value appears to be a placeholder");
pub const TEST_CONTEXT: (&'static str, i32, &'static str) =
("test_context", -100, "Code is in a test context (mock data expected)");
pub const AUTH_STATE_CHANGE: (&'static str, i32, &'static str) =
("auth_state_change", 40, "Comparison triggers auth state modification");
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);
}
}