use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::DedupScope;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ScanConfig {
pub min_confidence: f64,
pub max_decode_depth: usize,
pub entropy_enabled: bool,
pub entropy_in_source_files: bool,
pub entropy_threshold: f64,
pub min_secret_len: usize,
pub max_file_size: u64,
pub dedup: DedupScope,
pub ml_enabled: bool,
pub ml_weight: f64,
pub unicode_normalization: bool,
pub decode_size_limit: usize,
pub max_matches_per_chunk: usize,
pub known_prefixes: Vec<String>,
pub secret_keywords: Vec<String>,
pub test_keywords: Vec<String>,
pub placeholder_keywords: Vec<String>,
}
pub const MAX_DECODE_DEPTH_LIMIT: usize = 16;
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("min_confidence must be between 0.0 and 1.0, found {0}")]
InvalidConfidence(f64),
#[error("max_decode_depth exceeds limit of {MAX_DECODE_DEPTH_LIMIT}, found {0}")]
DepthTooHigh(usize),
}
impl Default for ScanConfig {
fn default() -> Self {
Self {
min_confidence: 0.3,
max_decode_depth: 3,
entropy_enabled: true,
entropy_in_source_files: false,
entropy_threshold: 4.5,
min_secret_len: 20,
max_file_size: 10 * 1024 * 1024, dedup: DedupScope::Credential,
ml_enabled: true,
ml_weight: 0.5,
unicode_normalization: true,
decode_size_limit: 64 * 1024 * 1024,
max_matches_per_chunk: 1000,
known_prefixes: vec!["AKIA".into(), "ASIA".into(), "ghp_".into(), "sk_".into()],
secret_keywords: vec!["password".into(), "secret".into(), "key".into()],
test_keywords: vec!["test".into(), "dummy".into(), "mock".into()],
placeholder_keywords: vec!["example".into(), "your_".into(), "placeholder".into()],
}
}
}
impl ScanConfig {
pub fn fast() -> Self {
Self {
max_decode_depth: 2,
entropy_enabled: false,
ml_enabled: false,
..Default::default()
}
}
pub fn thorough() -> Self {
Self {
max_decode_depth: 8,
entropy_in_source_files: true,
ml_enabled: true,
..Default::default()
}
}
pub fn paranoid() -> Self {
Self {
max_decode_depth: MAX_DECODE_DEPTH_LIMIT,
entropy_enabled: true,
entropy_in_source_files: true,
min_secret_len: 16,
ml_enabled: true,
..Default::default()
}
}
pub fn validate(&self) -> Result<(), ConfigError> {
if !(0.0..=1.0).contains(&self.min_confidence) {
return Err(ConfigError::InvalidConfidence(self.min_confidence));
}
if self.max_decode_depth > MAX_DECODE_DEPTH_LIMIT {
return Err(ConfigError::DepthTooHigh(self.max_decode_depth));
}
Ok(())
}
}
pub fn secret_filenames() -> Vec<String> {
vec![
".env",
".env.local",
".env.production",
".env.development",
".env.test",
"config.json",
"config.yaml",
"config.yml",
"credentials.json",
"secrets.json",
"settings.json",
"production.json",
"development.json",
"local.json",
"appsettings.json",
"web.config",
"web.Debug.config",
"web.Release.config",
"Application.xml",
"Settings.xml",
"App.config",
"pom.xml",
"build.gradle",
"build.gradle.kts",
"package.json",
"package-lock.json",
"yarn.lock",
"composer.json",
"composer.lock",
"pipfile",
"pipfile.lock",
"requirements.txt",
"gemfile",
"gemfile.lock",
"cargo.toml",
"cargo.lock",
"go.mod",
"go.sum",
"docker-compose.yml",
"docker-compose.yaml",
"dockerfile",
"kubernetes.yml",
"kubernetes.yaml",
"k8s.yml",
"k8s.yaml",
"deploy.yml",
"deploy.yaml",
"service.yml",
"service.yaml",
"configmap.yml",
"configmap.yaml",
"secret.yml",
"secret.yaml",
]
.iter()
.map(|s| s.to_string())
.collect()
}