use crate::core::Severity;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct Config {
pub general: GeneralConfig,
pub acquisition: AcquisitionConfig,
pub sanitization: SanitizeConfig,
pub archive: ArchiveConfig,
pub scan: ScanConfig,
pub plugins: PluginsConfig,
pub network: NetworkConfig,
pub output: OutputConfig,
pub hooks: HooksConfig,
pub auth: AuthConfig,
pub submodules: SubmodulesConfig,
pub lfs: LfsConfig,
pub proxy: ProxyConfig,
pub llm_security: LlmSecurityConfig,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GeneralConfig {
pub quarantine_dir: PathBuf,
pub parallel: bool,
pub max_threads: usize,
pub fail_on_severity: Severity,
pub file_timeout_seconds: u64,
pub total_timeout_seconds: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AcquisitionConfig {
pub default_strategy: String,
pub max_download_size_mb: u64,
pub verify_integrity: bool,
pub use_gitoxide: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SanitizeConfig {
pub remove_hooks: bool,
pub sanitize_config: bool,
pub sanitize_attributes: bool,
pub disable_lfs: bool,
pub remove_submodules: bool,
pub allowed_config_keys: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ArchiveConfig {
pub max_compression_ratio: u64,
pub max_extracted_size_mb: u64,
pub max_file_count: usize,
pub max_nesting_depth: usize,
pub max_single_file_mb: u64,
pub max_path_length: usize,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ScanConfig {
pub skip_paths: Vec<String>,
pub force_scan: Vec<String>,
pub max_scan_file_mb: u64,
pub allowlist_files: Vec<String>,
}
impl ScanConfig {
pub fn is_allowlisted(&self, path: &std::path::Path) -> bool {
let path_str = path.to_string_lossy();
self.allowlist_files.iter().any(|pat| {
glob::Pattern::new(pat)
.map(|g| g.matches(&path_str))
.unwrap_or(false)
})
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PluginsConfig {
pub enabled: Vec<String>,
pub plugin_dir: PathBuf,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct NetworkConfig {
pub offline: bool,
pub proxy: String,
pub timeout_seconds: u64,
pub retries: u32,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct OutputConfig {
pub format: String,
pub report_dir: PathBuf,
pub keep_reports_days: u32,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AuthConfig {
pub prefer_ssh: bool,
pub token_sources: Vec<String>,
pub ssh_key_path: Option<PathBuf>,
pub use_credential_helper: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SubmodulesConfig {
pub auto_acquire: bool,
pub max_depth: usize,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LfsConfig {
pub enabled: bool,
pub verify_hashes: bool,
pub max_object_size_mb: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ProxyConfig {
pub scan_on_pull: bool,
pub scan_on_merge: bool,
pub block_push_on_secrets: bool,
pub incremental_scan: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct LlmSecurityConfig {
pub enabled: bool,
pub binary: Option<String>,
pub scan_on_acquire: bool,
pub scan_mcp_configs: bool,
pub firewall_prompts: bool,
pub verify_pins: bool,
pub fail_on_severity: Option<Severity>,
pub mcp_config_patterns: Vec<String>,
pub run_modelscan: bool,
pub run_picklescan: bool,
pub run_fickling: bool,
pub run_mcp_scan: bool,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct HooksConfig {
pub pre_commit_enabled: bool,
pub pre_push_enabled: bool,
pub fail_on_severity: Severity,
pub auto_fix: bool,
}
impl Default for GeneralConfig {
fn default() -> Self {
let quarantine_dir = dirs::cache_dir()
.map(|d| d.join("securegit").join("quarantine"))
.unwrap_or_else(|| {
dirs::home_dir()
.map(|h| h.join(".cache/securegit/quarantine"))
.unwrap_or_else(|| PathBuf::from("/tmp/securegit-quarantine"))
});
Self {
quarantine_dir,
parallel: true,
max_threads: 0,
fail_on_severity: Severity::High,
file_timeout_seconds: 30,
total_timeout_seconds: 600,
}
}
}
impl Default for AcquisitionConfig {
fn default() -> Self {
Self {
default_strategy: "zip-with-history".to_string(),
max_download_size_mb: 500,
verify_integrity: true,
use_gitoxide: true,
}
}
}
impl Default for SanitizeConfig {
fn default() -> Self {
Self {
remove_hooks: true,
sanitize_config: true,
sanitize_attributes: true,
disable_lfs: true,
remove_submodules: false,
allowed_config_keys: vec![
"core.repositoryformatversion".to_string(),
"core.filemode".to_string(),
"core.bare".to_string(),
"core.logallrefupdates".to_string(),
"core.ignorecase".to_string(),
"core.autocrlf".to_string(),
"core.symlinks".to_string(),
"core.precomposeunicode".to_string(),
"user.name".to_string(),
"user.email".to_string(),
"remote.*".to_string(),
"branch.*".to_string(),
],
}
}
}
impl Default for ArchiveConfig {
fn default() -> Self {
Self {
max_compression_ratio: 100,
max_extracted_size_mb: 5000,
max_file_count: 50000,
max_nesting_depth: 25,
max_single_file_mb: 1000,
max_path_length: 4096,
}
}
}
impl Default for ScanConfig {
fn default() -> Self {
Self {
skip_paths: vec![
"node_modules/**".to_string(),
"vendor/**".to_string(),
"__pycache__/**".to_string(),
],
force_scan: vec!["**/package.json".to_string(), "**/Cargo.toml".to_string()],
max_scan_file_mb: 50,
allowlist_files: vec![],
}
}
}
impl Default for PluginsConfig {
fn default() -> Self {
let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
Self {
enabled: vec![
"patterns".to_string(),
"entropy".to_string(),
"secrets".to_string(),
"binary".to_string(),
],
plugin_dir: home.join(".config/securegit/plugins"),
}
}
}
impl Default for NetworkConfig {
fn default() -> Self {
Self {
offline: false,
proxy: String::new(),
timeout_seconds: 30,
retries: 3,
}
}
}
impl Default for OutputConfig {
fn default() -> Self {
let home = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
Self {
format: "pretty".to_string(),
report_dir: home.join(".local/share/securegit/reports"),
keep_reports_days: 30,
}
}
}
impl Default for HooksConfig {
fn default() -> Self {
Self {
pre_commit_enabled: true,
pre_push_enabled: true,
fail_on_severity: Severity::High,
auto_fix: false,
}
}
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
prefer_ssh: false,
token_sources: vec![
"SECUREGIT_TOKEN".to_string(),
"GITHUB_TOKEN".to_string(),
"GH_TOKEN".to_string(),
"GITLAB_TOKEN".to_string(),
],
ssh_key_path: None,
use_credential_helper: true,
}
}
}
impl Default for SubmodulesConfig {
fn default() -> Self {
Self {
auto_acquire: false,
max_depth: 10,
}
}
}
impl Default for LfsConfig {
fn default() -> Self {
Self {
enabled: false,
verify_hashes: true,
max_object_size_mb: 500,
}
}
}
impl Default for ProxyConfig {
fn default() -> Self {
Self {
scan_on_pull: true,
scan_on_merge: true,
block_push_on_secrets: true,
incremental_scan: true,
}
}
}
impl Default for LlmSecurityConfig {
fn default() -> Self {
Self {
enabled: false,
binary: None,
scan_on_acquire: true,
scan_mcp_configs: true,
firewall_prompts: true,
verify_pins: true,
fail_on_severity: None,
mcp_config_patterns: vec![
"claude_desktop_config.json".into(),
"mcp.json".into(),
".claude/settings.json".into(),
".cursor/mcp.json".into(),
"cline_mcp_settings.json".into(),
],
run_modelscan: true,
run_picklescan: true,
run_fickling: true,
run_mcp_scan: true,
}
}
}