syncable_cli/analyzer/security/
config.rs

1//! # Security Analysis Configuration
2//! 
3//! Configuration options for customizing security analysis behavior.
4
5use serde::{Deserialize, Serialize};
6
7/// Configuration for security analysis
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct SecurityAnalysisConfig {
10    // General settings
11    pub include_low_severity: bool,
12    pub include_info_level: bool,
13    
14    // Analysis scope
15    pub check_secrets: bool,
16    pub check_code_patterns: bool,
17    pub check_infrastructure: bool,
18    pub check_compliance: bool,
19    
20    // Language-specific settings
21    pub javascript_enabled: bool,
22    pub python_enabled: bool,
23    pub rust_enabled: bool,
24    
25    // Framework-specific settings
26    pub frameworks_to_check: Vec<String>,
27    
28    // File filtering
29    pub ignore_patterns: Vec<String>,
30    pub include_patterns: Vec<String>,
31    
32    // Git integration
33    pub skip_gitignored_files: bool,
34    pub downgrade_gitignored_severity: bool,
35    pub check_git_history: bool,
36    
37    // Environment variable handling
38    pub check_env_files: bool,
39    pub warn_on_public_env_vars: bool,
40    pub sensitive_env_keywords: Vec<String>,
41    
42    // JavaScript/TypeScript specific
43    pub check_package_json: bool,
44    pub check_node_modules: bool,
45    pub framework_env_prefixes: Vec<String>,
46    
47    // Output customization
48    pub max_findings_per_file: Option<usize>,
49    pub deduplicate_findings: bool,
50    pub group_by_severity: bool,
51    
52    // Performance settings
53    pub max_file_size_mb: Option<usize>,
54    pub parallel_analysis: bool,
55    pub analysis_timeout_seconds: Option<u64>,
56}
57
58impl Default for SecurityAnalysisConfig {
59    fn default() -> Self {
60        Self {
61            // General settings
62            include_low_severity: false,
63            include_info_level: false,
64            
65            // Analysis scope
66            check_secrets: true,
67            check_code_patterns: true,
68            check_infrastructure: true,
69            check_compliance: false, // Disabled by default as it requires more setup
70            
71            // Language-specific settings
72            javascript_enabled: true,
73            python_enabled: true,
74            rust_enabled: true,
75            
76            // Framework-specific settings
77            frameworks_to_check: vec![
78                "React".to_string(),
79                "Vue".to_string(),
80                "Angular".to_string(),
81                "Next.js".to_string(),
82                "Vite".to_string(),
83                "Express".to_string(),
84                "Django".to_string(),
85                "Spring Boot".to_string(),
86            ],
87            
88            // File filtering
89            ignore_patterns: vec![
90                "node_modules".to_string(),
91                ".git".to_string(),
92                "target".to_string(),
93                "build".to_string(),
94                ".next".to_string(),
95                "coverage".to_string(),
96                "dist".to_string(),
97                "*.min.js".to_string(),
98                "*.bundle.js".to_string(),
99                "*.map".to_string(),
100                "*.lock".to_string(),
101                "*_sample.*".to_string(),
102                "*example*".to_string(),
103                "*test*".to_string(),
104                "*spec*".to_string(),
105                "*mock*".to_string(),
106                "*.d.ts".to_string(), // TypeScript declaration files
107            ],
108            include_patterns: vec![], // Empty means include all (subject to ignore patterns)
109            
110            // Git integration
111            skip_gitignored_files: true,
112            downgrade_gitignored_severity: false,
113            check_git_history: false, // Disabled by default for performance
114            
115            // Environment variable handling
116            check_env_files: true,
117            warn_on_public_env_vars: true,
118            sensitive_env_keywords: vec![
119                "SECRET".to_string(),
120                "KEY".to_string(),
121                "TOKEN".to_string(),
122                "PASSWORD".to_string(),
123                "PASS".to_string(),
124                "AUTH".to_string(),
125                "API".to_string(),
126                "PRIVATE".to_string(),
127                "CREDENTIAL".to_string(),
128                "CERT".to_string(),
129                "SSL".to_string(),
130                "TLS".to_string(),
131                "OAUTH".to_string(),
132                "CLIENT_SECRET".to_string(),
133                "ACCESS_TOKEN".to_string(),
134                "REFRESH_TOKEN".to_string(),
135                "DATABASE_URL".to_string(),
136                "DB_PASS".to_string(),
137                "STRIPE_SECRET".to_string(),
138                "AWS_SECRET".to_string(),
139                "FIREBASE_PRIVATE".to_string(),
140            ],
141            
142            // JavaScript/TypeScript specific
143            check_package_json: true,
144            check_node_modules: false, // Usually don't want to scan dependencies
145            framework_env_prefixes: vec![
146                "REACT_APP_".to_string(),
147                "NEXT_PUBLIC_".to_string(),
148                "VITE_".to_string(),
149                "VUE_APP_".to_string(),
150                "EXPO_PUBLIC_".to_string(),
151                "NUXT_PUBLIC_".to_string(),
152                "GATSBY_".to_string(),
153                "STORYBOOK_".to_string(),
154            ],
155            
156            // Output customization
157            max_findings_per_file: Some(50), // Prevent overwhelming output
158            deduplicate_findings: true,
159            group_by_severity: true,
160            
161            // Performance settings
162            max_file_size_mb: Some(10), // Skip very large files
163            parallel_analysis: true,
164            analysis_timeout_seconds: Some(300), // 5 minutes max
165        }
166    }
167}
168
169impl SecurityAnalysisConfig {
170    /// Create a configuration optimized for JavaScript/TypeScript projects
171    pub fn for_javascript() -> Self {
172        let mut config = Self::default();
173        config.javascript_enabled = true;
174        config.python_enabled = false;
175        config.rust_enabled = false;
176        config.check_package_json = true;
177        config.frameworks_to_check = vec![
178            "React".to_string(),
179            "Vue".to_string(),
180            "Angular".to_string(),
181            "Next.js".to_string(),
182            "Vite".to_string(),
183            "Express".to_string(),
184            "Svelte".to_string(),
185            "Nuxt".to_string(),
186        ];
187        config
188    }
189    
190    /// Create a configuration optimized for Python projects
191    pub fn for_python() -> Self {
192        let mut config = Self::default();
193        config.javascript_enabled = false;
194        config.python_enabled = true;
195        config.rust_enabled = false;
196        config.check_package_json = false;
197        config.frameworks_to_check = vec![
198            "Django".to_string(),
199            "Flask".to_string(),
200            "FastAPI".to_string(),
201            "Tornado".to_string(),
202        ];
203        config
204    }
205    
206    /// Create a high-security configuration with strict settings
207    pub fn high_security() -> Self {
208        let mut config = Self::default();
209        config.include_low_severity = true;
210        config.include_info_level = true;
211        config.skip_gitignored_files = false; // Check everything
212        config.check_git_history = true;
213        config.warn_on_public_env_vars = true;
214        config.max_findings_per_file = None; // No limit
215        config
216    }
217    
218    /// Create a fast configuration for CI/CD pipelines
219    pub fn fast_ci() -> Self {
220        let mut config = Self::default();
221        config.include_low_severity = false;
222        config.include_info_level = false;
223        config.check_compliance = false;
224        config.check_git_history = false;
225        config.parallel_analysis = true;
226        config.max_findings_per_file = Some(20); // Limit output
227        config.analysis_timeout_seconds = Some(120); // 2 minutes max
228        config
229    }
230    
231    /// Check if a file should be analyzed based on patterns
232    pub fn should_analyze_file(&self, file_path: &std::path::Path) -> bool {
233        let file_path_str = file_path.to_string_lossy();
234        let file_name = file_path.file_name()
235            .and_then(|n| n.to_str())
236            .unwrap_or("");
237        
238        // Check ignore patterns first
239        for pattern in &self.ignore_patterns {
240            if self.matches_pattern(pattern, &file_path_str, file_name) {
241                return false;
242            }
243        }
244        
245        // If include patterns are specified, file must match at least one
246        if !self.include_patterns.is_empty() {
247            return self.include_patterns.iter().any(|pattern| {
248                self.matches_pattern(pattern, &file_path_str, file_name)
249            });
250        }
251        
252        true
253    }
254    
255    /// Check if a pattern matches a file
256    fn matches_pattern(&self, pattern: &str, file_path: &str, file_name: &str) -> bool {
257        if pattern.contains('*') {
258            // Use glob matching for wildcard patterns
259            glob::Pattern::new(pattern)
260                .map(|p| p.matches(file_path) || p.matches(file_name))
261                .unwrap_or(false)
262        } else {
263            // Simple string matching
264            file_path.contains(pattern) || file_name.contains(pattern)
265        }
266    }
267    
268    /// Check if an environment variable name appears sensitive
269    pub fn is_sensitive_env_var(&self, var_name: &str) -> bool {
270        let var_upper = var_name.to_uppercase();
271        self.sensitive_env_keywords.iter()
272            .any(|keyword| var_upper.contains(keyword))
273    }
274    
275    /// Check if an environment variable should be public (safe for client-side)
276    pub fn is_public_env_var(&self, var_name: &str) -> bool {
277        self.framework_env_prefixes.iter()
278            .any(|prefix| var_name.starts_with(prefix))
279    }
280    
281    /// Get the maximum file size to analyze in bytes
282    pub fn max_file_size_bytes(&self) -> Option<usize> {
283        self.max_file_size_mb.map(|mb| mb * 1024 * 1024)
284    }
285}
286
287/// Preset configurations for common use cases
288#[derive(Debug, Clone, Copy)]
289pub enum SecurityConfigPreset {
290    /// Default balanced configuration
291    Default,
292    /// Optimized for JavaScript/TypeScript projects
293    JavaScript,
294    /// Optimized for Python projects
295    Python,
296    /// High-security configuration with strict settings
297    HighSecurity,
298    /// Fast configuration for CI/CD pipelines
299    FastCI,
300}
301
302impl SecurityConfigPreset {
303    pub fn to_config(self) -> SecurityAnalysisConfig {
304        match self {
305            Self::Default => SecurityAnalysisConfig::default(),
306            Self::JavaScript => SecurityAnalysisConfig::for_javascript(),
307            Self::Python => SecurityAnalysisConfig::for_python(),
308            Self::HighSecurity => SecurityAnalysisConfig::high_security(),
309            Self::FastCI => SecurityAnalysisConfig::fast_ci(),
310        }
311    }
312}
313
314impl From<SecurityConfigPreset> for SecurityAnalysisConfig {
315    fn from(preset: SecurityConfigPreset) -> Self {
316        preset.to_config()
317    }
318}