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 - Enhanced patterns to reduce false positives
89            ignore_patterns: vec![
90                // Dependencies and build artifacts
91                "node_modules".to_string(),
92                ".git".to_string(),
93                "target".to_string(),
94                "build".to_string(),
95                ".next".to_string(),
96                "coverage".to_string(),
97                "dist".to_string(),
98                ".nuxt".to_string(),
99                ".output".to_string(),
100                ".vercel".to_string(),
101                ".netlify".to_string(),
102                
103                // Python virtual environments
104                "venv/".to_string(),
105                ".venv/".to_string(),
106                
107                // Minified and bundled files
108                "*.min.js".to_string(),
109                "*.min.css".to_string(),
110                "*.bundle.js".to_string(),
111                "*.bundle.css".to_string(),
112                "*.chunk.js".to_string(),
113                "*.vendor.js".to_string(),
114                "*.map".to_string(),
115                
116                // Lock files and package managers
117                "*.lock".to_string(),
118                "*.lockb".to_string(),
119                "yarn.lock".to_string(),
120                "package-lock.json".to_string(),
121                "pnpm-lock.yaml".to_string(),
122                "bun.lockb".to_string(),
123                "cargo.lock".to_string(),
124                "go.sum".to_string(),
125                "poetry.lock".to_string(),
126                "composer.lock".to_string(),
127                "gemfile.lock".to_string(),
128                
129                // Asset files
130                "*.jpg".to_string(),
131                "*.jpeg".to_string(),
132                "*.png".to_string(),
133                "*.gif".to_string(),
134                "*.bmp".to_string(),
135                "*.svg".to_string(),
136                "*.ico".to_string(),
137                "*.webp".to_string(),
138                "*.tiff".to_string(),
139                "*.mp3".to_string(),
140                "*.mp4".to_string(),
141                "*.avi".to_string(),
142                "*.mov".to_string(),
143                "*.pdf".to_string(),
144                "*.ttf".to_string(),
145                "*.otf".to_string(),
146                "*.woff".to_string(),
147                "*.woff2".to_string(),
148                "*.eot".to_string(),
149                
150                // Database & Certificate files
151                "*.wt".to_string(),
152                "*.cer".to_string(),
153                "*.jks".to_string(),
154                
155                // Test and example files
156                "*_sample.*".to_string(),
157                "*example*".to_string(),
158                "*test*".to_string(),
159                "*spec*".to_string(),
160                "*mock*".to_string(),
161                "*fixture*".to_string(),
162                "test/*".to_string(),
163                "tests/*".to_string(),
164                "__test__/*".to_string(),
165                "__tests__/*".to_string(),
166                "spec/*".to_string(),
167                "specs/*".to_string(),
168                
169                // Documentation
170                "*.md".to_string(),
171                "*.txt".to_string(),
172                "*.rst".to_string(),
173                "docs/*".to_string(),
174                "documentation/*".to_string(),
175                
176                // IDE and editor files
177                ".vscode/*".to_string(),
178                ".idea/*".to_string(),
179                ".vs/*".to_string(),
180                "*.swp".to_string(),
181                "*.swo".to_string(),
182                ".DS_Store".to_string(),
183                "Thumbs.db".to_string(),
184                
185                // TypeScript and generated files
186                "*.d.ts".to_string(),
187                "*.generated.*".to_string(),
188                "*.auto.*".to_string(),
189                
190                // Framework-specific
191                ".angular/*".to_string(),
192                ".svelte-kit/*".to_string(),
193                "storybook-static/*".to_string(),
194            ],
195            include_patterns: vec![], // Empty means include all (subject to ignore patterns)
196            
197            // Git integration
198            skip_gitignored_files: true,
199            downgrade_gitignored_severity: false,
200            check_git_history: false, // Disabled by default for performance
201            
202            // Environment variable handling
203            check_env_files: true,
204            warn_on_public_env_vars: true,
205            sensitive_env_keywords: vec![
206                "SECRET".to_string(),
207                "KEY".to_string(),
208                "TOKEN".to_string(),
209                "PASSWORD".to_string(),
210                "PASS".to_string(),
211                "AUTH".to_string(),
212                "API".to_string(),
213                "PRIVATE".to_string(),
214                "CREDENTIAL".to_string(),
215                "CERT".to_string(),
216                "SSL".to_string(),
217                "TLS".to_string(),
218                "OAUTH".to_string(),
219                "CLIENT_SECRET".to_string(),
220                "ACCESS_TOKEN".to_string(),
221                "REFRESH_TOKEN".to_string(),
222                "DATABASE_URL".to_string(),
223                "DB_PASS".to_string(),
224                "STRIPE_SECRET".to_string(),
225                "AWS_SECRET".to_string(),
226                "FIREBASE_PRIVATE".to_string(),
227            ],
228            
229            // JavaScript/TypeScript specific
230            check_package_json: true,
231            check_node_modules: false, // Usually don't want to scan dependencies
232            framework_env_prefixes: vec![
233                "REACT_APP_".to_string(),
234                "NEXT_PUBLIC_".to_string(),
235                "VITE_".to_string(),
236                "VUE_APP_".to_string(),
237                "EXPO_PUBLIC_".to_string(),
238                "NUXT_PUBLIC_".to_string(),
239                "GATSBY_".to_string(),
240                "STORYBOOK_".to_string(),
241            ],
242            
243            // Output customization
244            max_findings_per_file: Some(50), // Prevent overwhelming output
245            deduplicate_findings: true,
246            group_by_severity: true,
247            
248            // Performance settings
249            max_file_size_mb: Some(10), // Skip very large files
250            parallel_analysis: true,
251            analysis_timeout_seconds: Some(300), // 5 minutes max
252        }
253    }
254}
255
256impl SecurityAnalysisConfig {
257    /// Create a configuration optimized for JavaScript/TypeScript projects
258    pub fn for_javascript() -> Self {
259        let mut config = Self::default();
260        config.javascript_enabled = true;
261        config.python_enabled = false;
262        config.rust_enabled = false;
263        config.check_package_json = true;
264        config.frameworks_to_check = vec![
265            "React".to_string(),
266            "Vue".to_string(),
267            "Angular".to_string(),
268            "Next.js".to_string(),
269            "Vite".to_string(),
270            "Express".to_string(),
271            "Svelte".to_string(),
272            "Nuxt".to_string(),
273        ];
274        config
275    }
276    
277    /// Create a configuration optimized for Python projects
278    pub fn for_python() -> Self {
279        let mut config = Self::default();
280        config.javascript_enabled = false;
281        config.python_enabled = true;
282        config.rust_enabled = false;
283        config.check_package_json = false;
284        config.frameworks_to_check = vec![
285            "Django".to_string(),
286            "Flask".to_string(),
287            "FastAPI".to_string(),
288            "Tornado".to_string(),
289        ];
290        config
291    }
292    
293    /// Create a high-security configuration with strict settings
294    pub fn high_security() -> Self {
295        let mut config = Self::default();
296        config.include_low_severity = true;
297        config.include_info_level = true;
298        config.skip_gitignored_files = false; // Check everything
299        config.check_git_history = true;
300        config.warn_on_public_env_vars = true;
301        config.max_findings_per_file = None; // No limit
302        config
303    }
304    
305    /// Create a fast configuration for CI/CD pipelines
306    pub fn fast_ci() -> Self {
307        let mut config = Self::default();
308        config.include_low_severity = false;
309        config.include_info_level = false;
310        config.check_compliance = false;
311        config.check_git_history = false;
312        config.parallel_analysis = true;
313        config.max_findings_per_file = Some(20); // Limit output
314        config.analysis_timeout_seconds = Some(120); // 2 minutes max
315        config
316    }
317    
318    /// Check if a file should be analyzed based on patterns
319    pub fn should_analyze_file(&self, file_path: &std::path::Path) -> bool {
320        let file_path_str = file_path.to_string_lossy();
321        let file_name = file_path.file_name()
322            .and_then(|n| n.to_str())
323            .unwrap_or("");
324        
325        // Check ignore patterns first
326        for pattern in &self.ignore_patterns {
327            if self.matches_pattern(pattern, &file_path_str, file_name) {
328                return false;
329            }
330        }
331        
332        // If include patterns are specified, file must match at least one
333        if !self.include_patterns.is_empty() {
334            return self.include_patterns.iter().any(|pattern| {
335                self.matches_pattern(pattern, &file_path_str, file_name)
336            });
337        }
338        
339        true
340    }
341    
342    /// Check if a pattern matches a file
343    fn matches_pattern(&self, pattern: &str, file_path: &str, file_name: &str) -> bool {
344        if pattern.contains('*') {
345            // Use glob matching for wildcard patterns
346            glob::Pattern::new(pattern)
347                .map(|p| p.matches(file_path) || p.matches(file_name))
348                .unwrap_or(false)
349        } else {
350            // Simple string matching
351            file_path.contains(pattern) || file_name.contains(pattern)
352        }
353    }
354    
355    /// Check if an environment variable name appears sensitive
356    pub fn is_sensitive_env_var(&self, var_name: &str) -> bool {
357        let var_upper = var_name.to_uppercase();
358        self.sensitive_env_keywords.iter()
359            .any(|keyword| var_upper.contains(keyword))
360    }
361    
362    /// Check if an environment variable should be public (safe for client-side)
363    pub fn is_public_env_var(&self, var_name: &str) -> bool {
364        self.framework_env_prefixes.iter()
365            .any(|prefix| var_name.starts_with(prefix))
366    }
367    
368    /// Get the maximum file size to analyze in bytes
369    pub fn max_file_size_bytes(&self) -> Option<usize> {
370        self.max_file_size_mb.map(|mb| mb * 1024 * 1024)
371    }
372}
373
374/// Preset configurations for common use cases
375#[derive(Debug, Clone, Copy)]
376pub enum SecurityConfigPreset {
377    /// Default balanced configuration
378    Default,
379    /// Optimized for JavaScript/TypeScript projects
380    JavaScript,
381    /// Optimized for Python projects
382    Python,
383    /// High-security configuration with strict settings
384    HighSecurity,
385    /// Fast configuration for CI/CD pipelines
386    FastCI,
387}
388
389impl SecurityConfigPreset {
390    pub fn to_config(self) -> SecurityAnalysisConfig {
391        match self {
392            Self::Default => SecurityAnalysisConfig::default(),
393            Self::JavaScript => SecurityAnalysisConfig::for_javascript(),
394            Self::Python => SecurityAnalysisConfig::for_python(),
395            Self::HighSecurity => SecurityAnalysisConfig::high_security(),
396            Self::FastCI => SecurityAnalysisConfig::fast_ci(),
397        }
398    }
399}
400
401impl From<SecurityConfigPreset> for SecurityAnalysisConfig {
402    fn from(preset: SecurityConfigPreset) -> Self {
403        preset.to_config()
404    }
405}