syncable_cli/analyzer/
security_analyzer.rs

1//! # Security Analyzer
2//! 
3//! Comprehensive security analysis module that performs multi-layered security assessment:
4//! - Configuration security analysis (secrets, insecure settings)
5//! - Code security patterns (language/framework-specific issues)
6//! - Infrastructure security (Docker, compose configurations)
7//! - Security policy recommendations and compliance guidance
8//! - Security scoring with actionable remediation steps
9
10use std::collections::HashMap;
11use std::path::{Path, PathBuf};
12use std::fs;
13use std::time::Instant;
14use regex::Regex;
15use serde::{Deserialize, Serialize};
16use thiserror::Error;
17use log::{info, debug};
18use rayon::prelude::*;
19use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
20
21use crate::analyzer::{ProjectAnalysis, DetectedLanguage, DetectedTechnology, EnvVar};
22use crate::analyzer::dependency_parser::Language;
23
24#[derive(Debug, Error)]
25pub enum SecurityError {
26    #[error("Security analysis failed: {0}")]
27    AnalysisFailed(String),
28    
29    #[error("Configuration analysis error: {0}")]
30    ConfigAnalysisError(String),
31    
32    #[error("Code pattern analysis error: {0}")]
33    CodePatternError(String),
34    
35    #[error("Infrastructure analysis error: {0}")]
36    InfrastructureError(String),
37    
38    #[error("IO error: {0}")]
39    Io(#[from] std::io::Error),
40    
41    #[error("Regex error: {0}")]
42    Regex(#[from] regex::Error),
43}
44
45/// Security finding severity levels
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
47pub enum SecuritySeverity {
48    Critical,
49    High,
50    Medium,
51    Low,
52    Info,
53}
54
55/// Categories of security findings
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
57pub enum SecurityCategory {
58    /// Exposed secrets, API keys, passwords
59    SecretsExposure,
60    /// Insecure configuration settings
61    InsecureConfiguration,
62    /// Language/framework-specific security patterns
63    CodeSecurityPattern,
64    /// Infrastructure and deployment security
65    InfrastructureSecurity,
66    /// Authentication and authorization issues
67    AuthenticationSecurity,
68    /// Data protection and privacy concerns
69    DataProtection,
70    /// Network and communication security
71    NetworkSecurity,
72    /// Compliance and regulatory requirements
73    Compliance,
74}
75
76/// A security finding with details and remediation
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct SecurityFinding {
79    pub id: String,
80    pub title: String,
81    pub description: String,
82    pub severity: SecuritySeverity,
83    pub category: SecurityCategory,
84    pub file_path: Option<PathBuf>,
85    pub line_number: Option<usize>,
86    pub evidence: Option<String>,
87    pub remediation: Vec<String>,
88    pub references: Vec<String>,
89    pub cwe_id: Option<String>,
90    pub compliance_frameworks: Vec<String>,
91}
92
93/// Comprehensive security analysis report
94#[derive(Debug, Serialize, Deserialize)]
95pub struct SecurityReport {
96    pub analyzed_at: chrono::DateTime<chrono::Utc>,
97    pub overall_score: f32, // 0-100, higher is better
98    pub risk_level: SecuritySeverity,
99    pub total_findings: usize,
100    pub findings_by_severity: HashMap<SecuritySeverity, usize>,
101    pub findings_by_category: HashMap<SecurityCategory, usize>,
102    pub findings: Vec<SecurityFinding>,
103    pub recommendations: Vec<String>,
104    pub compliance_status: HashMap<String, ComplianceStatus>,
105}
106
107/// Compliance framework status
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct ComplianceStatus {
110    pub framework: String,
111    pub coverage: f32, // 0-100%
112    pub missing_controls: Vec<String>,
113    pub recommendations: Vec<String>,
114}
115
116/// Configuration for security analysis
117#[derive(Debug, Clone)]
118pub struct SecurityAnalysisConfig {
119    pub include_low_severity: bool,
120    pub check_secrets: bool,
121    pub check_code_patterns: bool,
122    pub check_infrastructure: bool,
123    pub check_compliance: bool,
124    pub frameworks_to_check: Vec<String>,
125    pub ignore_patterns: Vec<String>,
126}
127
128impl Default for SecurityAnalysisConfig {
129    fn default() -> Self {
130        Self {
131            include_low_severity: false,
132            check_secrets: true,
133            check_code_patterns: true,
134            check_infrastructure: true,
135            check_compliance: true,
136            frameworks_to_check: vec![
137                "SOC2".to_string(),
138                "GDPR".to_string(),
139                "OWASP".to_string(),
140            ],
141            ignore_patterns: vec![
142                "node_modules".to_string(),
143                ".git".to_string(),
144                "target".to_string(),
145                "build".to_string(),
146                ".next".to_string(),
147                "dist".to_string(),
148                "test".to_string(),
149                "tests".to_string(),
150                "*.json".to_string(), // Exclude JSON files that often contain hashes
151                "*.lock".to_string(), // Exclude lock files with checksums
152                "*_sample.*".to_string(), // Exclude sample files
153                "*audit*".to_string(), // Exclude audit reports
154            ],
155        }
156    }
157}
158
159pub struct SecurityAnalyzer {
160    config: SecurityAnalysisConfig,
161    secret_patterns: Vec<SecretPattern>,
162    security_rules: HashMap<Language, Vec<SecurityRule>>,
163}
164
165/// Pattern for detecting secrets and sensitive data
166struct SecretPattern {
167    name: String,
168    pattern: Regex,
169    severity: SecuritySeverity,
170    description: String,
171}
172
173/// Security rule for code pattern analysis
174struct SecurityRule {
175    id: String,
176    name: String,
177    pattern: Regex,
178    severity: SecuritySeverity,
179    category: SecurityCategory,
180    description: String,
181    remediation: Vec<String>,
182    cwe_id: Option<String>,
183}
184
185impl SecurityAnalyzer {
186    pub fn new() -> Result<Self, SecurityError> {
187        Self::with_config(SecurityAnalysisConfig::default())
188    }
189    
190    pub fn with_config(config: SecurityAnalysisConfig) -> Result<Self, SecurityError> {
191        let secret_patterns = Self::initialize_secret_patterns()?;
192        let security_rules = Self::initialize_security_rules()?;
193        
194        Ok(Self {
195            config,
196            secret_patterns,
197            security_rules,
198        })
199    }
200    
201    /// Perform comprehensive security analysis with appropriate progress for verbosity level
202    pub fn analyze_security(&self, analysis: &ProjectAnalysis) -> Result<SecurityReport, SecurityError> {
203        let start_time = Instant::now();
204        info!("Starting comprehensive security analysis");
205        
206        // Check if we're in verbose mode by checking log level
207        let is_verbose = log::max_level() >= log::LevelFilter::Info;
208        
209        // Set up progress tracking appropriate for verbosity
210        let multi_progress = MultiProgress::new();
211        
212        // In verbose mode, we'll completely skip adding progress bars to avoid visual conflicts
213        
214        // Count enabled analysis phases
215        let mut total_phases = 0;
216        if self.config.check_secrets { total_phases += 1; }
217        if self.config.check_code_patterns { total_phases += 1; }
218        if self.config.check_infrastructure { total_phases += 1; }
219        total_phases += 2; // env vars and framework analysis always run
220        
221        // Create appropriate progress indicator based on verbosity
222        let main_pb = if is_verbose {
223            None // No main progress bar in verbose mode to avoid conflicts with logs
224        } else {
225            // Normal mode: Rich progress bar
226            let pb = multi_progress.add(ProgressBar::new(100));
227            pb.set_style(
228                ProgressStyle::default_bar()
229                    .template("🛡️  {msg} {bar:50.cyan/blue} {percent}% [{elapsed_precise}]")
230                    .unwrap()
231                    .progress_chars("██▉▊▋▌▍▎▏  "),
232            );
233            Some(pb)
234        };
235        
236        let mut findings = Vec::new();
237        let phase_weight = if is_verbose { 1u64 } else { 100 / total_phases as u64 };
238        let mut current_progress = 0u64;
239        
240        // 1. Configuration Security Analysis
241        if self.config.check_secrets {
242            if let Some(ref pb) = main_pb {
243                pb.set_message("Analyzing configuration & secrets...");
244                pb.set_position(current_progress);
245            }
246            
247            if is_verbose {
248                findings.extend(self.analyze_configuration_security(&analysis.project_root)?);
249            } else {
250                findings.extend(self.analyze_configuration_security_with_progress(&analysis.project_root, &multi_progress)?);
251            }
252            
253            if let Some(ref pb) = main_pb {
254                current_progress += phase_weight;
255                pb.set_position(current_progress);
256            }
257        }
258        
259        // 2. Code Security Patterns
260        if self.config.check_code_patterns {
261            if let Some(ref pb) = main_pb {
262                pb.set_message("Analyzing code security patterns...");
263            }
264            
265            if is_verbose {
266                findings.extend(self.analyze_code_security_patterns(&analysis.project_root, &analysis.languages)?);
267            } else {
268                findings.extend(self.analyze_code_security_patterns_with_progress(&analysis.project_root, &analysis.languages, &multi_progress)?);
269            }
270            
271            if let Some(ref pb) = main_pb {
272                current_progress += phase_weight;
273                pb.set_position(current_progress);
274            }
275        }
276        
277        // 3. Infrastructure Security (skipped - not implemented yet)
278        // TODO: Implement infrastructure security analysis
279        // Currently all infrastructure analysis methods return empty results
280        
281        // 4. Environment Variables Security
282        if let Some(ref pb) = main_pb {
283            pb.set_message("Analyzing environment variables...");
284        }
285        
286        findings.extend(self.analyze_environment_security(&analysis.environment_variables));
287        if let Some(ref pb) = main_pb {
288            current_progress += phase_weight;
289            pb.set_position(current_progress);
290        }
291        
292        // 5. Framework-specific Security (skipped - not implemented yet)
293        // TODO: Implement framework-specific security analysis
294        // Currently all framework analysis methods return empty results
295        
296        if let Some(ref pb) = main_pb {
297            current_progress = 100;
298            pb.set_position(current_progress);
299        }
300        
301        // Processing phase
302        if let Some(ref pb) = main_pb {
303            pb.set_message("Processing findings & generating report...");
304        }
305        
306        // DEDUPLICATION: Remove duplicate findings for the same secret/issue
307        let pre_dedup_count = findings.len();
308        findings = self.deduplicate_findings(findings);
309        let post_dedup_count = findings.len();
310        
311        if pre_dedup_count != post_dedup_count {
312            info!("Deduplicated {} redundant findings, {} unique findings remain", 
313                  pre_dedup_count - post_dedup_count, post_dedup_count);
314        }
315        
316        // Filter findings based on configuration
317        let pre_filter_count = findings.len();
318        if !self.config.include_low_severity {
319            findings.retain(|f| f.severity != SecuritySeverity::Low && f.severity != SecuritySeverity::Info);
320        }
321        
322        // Sort by severity (most critical first)
323        findings.sort_by(|a, b| a.severity.cmp(&b.severity));
324        
325        // Calculate metrics
326        let total_findings = findings.len();
327        let findings_by_severity = self.count_by_severity(&findings);
328        let findings_by_category = self.count_by_category(&findings);
329        let overall_score = self.calculate_security_score(&findings);
330        let risk_level = self.determine_risk_level(&findings);
331        
332        // Generate compliance status (disabled - not implemented yet)
333        // TODO: Implement compliance assessment
334        let compliance_status = HashMap::new();
335        
336        // Generate recommendations
337        let recommendations = self.generate_recommendations(&findings, &analysis.technologies);
338        
339        // Complete with summary
340        let duration = start_time.elapsed().as_secs_f32();
341        if let Some(pb) = main_pb {
342            pb.finish_with_message(format!("✅ Security analysis completed in {:.1}s - Found {} issues", duration, total_findings));
343        }
344        
345        // Print summary
346        if pre_filter_count != total_findings {
347            info!("Found {} total findings, showing {} after filtering", pre_filter_count, total_findings);
348        } else {
349            info!("Found {} security findings", total_findings);
350        }
351        
352        Ok(SecurityReport {
353            analyzed_at: chrono::Utc::now(),
354            overall_score,
355            risk_level,
356            total_findings,
357            findings_by_severity,
358            findings_by_category,
359            findings,
360            recommendations,
361            compliance_status,
362        })
363    }
364    
365    /// Initialize secret detection patterns
366    fn initialize_secret_patterns() -> Result<Vec<SecretPattern>, SecurityError> {
367        let patterns = vec![
368            // API Keys and Tokens - Specific patterns first
369            ("AWS Access Key", r"AKIA[0-9A-Z]{16}", SecuritySeverity::Critical),
370            ("AWS Secret Key", r#"(?i)(aws[_-]?secret|secret[_-]?access[_-]?key)["']?\s*[:=]\s*["']?[A-Za-z0-9/+=]{40}["']?"#, SecuritySeverity::Critical),
371            ("S3 Secret Key", r#"(?i)(s3[_-]?secret[_-]?key|linode[_-]?s3[_-]?secret)["']?\s*[:=]\s*["']?[A-Za-z0-9/+=]{20,}["']?"#, SecuritySeverity::High),
372            ("GitHub Token", r"gh[pousr]_[A-Za-z0-9_]{36,255}", SecuritySeverity::High),
373            ("OpenAI API Key", r"sk-[A-Za-z0-9]{48}", SecuritySeverity::High),
374            ("Stripe API Key", r"sk_live_[0-9a-zA-Z]{24}", SecuritySeverity::Critical),
375            ("Stripe Publishable Key", r"pk_live_[0-9a-zA-Z]{24}", SecuritySeverity::Medium),
376            
377            // Database URLs and Passwords
378            ("Database URL", r#"(?i)(database_url|db_url)["']?\s*[:=]\s*["']?[^"'\s]+"#, SecuritySeverity::High),
379            ("Password", r#"(?i)(password|passwd|pwd)["']?\s*[:=]\s*["']?[^"']{6,}"#, SecuritySeverity::Medium),
380            ("JWT Secret", r#"(?i)(jwt[_-]?secret)["']?\s*[:=]\s*["']?[A-Za-z0-9_\-+/=]{20,}"#, SecuritySeverity::High),
381            
382            // Private Keys
383            ("RSA Private Key", r"-----BEGIN RSA PRIVATE KEY-----", SecuritySeverity::Critical),
384            ("SSH Private Key", r"-----BEGIN OPENSSH PRIVATE KEY-----", SecuritySeverity::Critical),
385            ("PGP Private Key", r"-----BEGIN PGP PRIVATE KEY BLOCK-----", SecuritySeverity::Critical),
386            
387            // Cloud Provider Keys
388            ("Google Cloud Service Account", r#""type":\s*"service_account""#, SecuritySeverity::High),
389            ("Azure Storage Key", r"DefaultEndpointsProtocol=https;AccountName=", SecuritySeverity::High),
390            
391            // Generic patterns last (lowest priority)
392            ("Generic API Key", r#"(?i)(api[_-]?key|apikey)["']?\s*[:=]\s*["']?[A-Za-z0-9_\-]{20,}"#, SecuritySeverity::High),
393            ("Generic Secret", r#"(?i)(secret|token|key)["']?\s*[:=]\s*["']?[A-Za-z0-9_\-+/=]{24,}"#, SecuritySeverity::Medium),
394        ];
395        
396        patterns.into_iter()
397            .map(|(name, pattern, severity)| {
398                Ok(SecretPattern {
399                    name: name.to_string(),
400                    pattern: Regex::new(pattern)?,
401                    severity,
402                    description: format!("Potential {} found in code", name),
403                })
404            })
405            .collect()
406    }
407    
408    /// Initialize language-specific security rules
409    fn initialize_security_rules() -> Result<HashMap<Language, Vec<SecurityRule>>, SecurityError> {
410        let mut rules = HashMap::new();
411        
412        // JavaScript/TypeScript Rules
413        rules.insert(Language::JavaScript, vec![
414            SecurityRule {
415                id: "js-001".to_string(),
416                name: "Eval Usage".to_string(),
417                pattern: Regex::new(r"\beval\s*\(")?,
418                severity: SecuritySeverity::High,
419                category: SecurityCategory::CodeSecurityPattern,
420                description: "Use of eval() can lead to code injection vulnerabilities".to_string(),
421                remediation: vec![
422                    "Avoid using eval() with user input".to_string(),
423                    "Use JSON.parse() for parsing JSON data".to_string(),
424                    "Consider using safer alternatives like Function constructor with validation".to_string(),
425                ],
426                cwe_id: Some("CWE-95".to_string()),
427            },
428            SecurityRule {
429                id: "js-002".to_string(),
430                name: "innerHTML Usage".to_string(),
431                pattern: Regex::new(r"\.innerHTML\s*=")?,
432                severity: SecuritySeverity::Medium,
433                category: SecurityCategory::CodeSecurityPattern,
434                description: "innerHTML can lead to XSS vulnerabilities if used with unsanitized data".to_string(),
435                remediation: vec![
436                    "Use textContent instead of innerHTML for text".to_string(),
437                    "Sanitize HTML content before setting innerHTML".to_string(),
438                    "Consider using secure templating libraries".to_string(),
439                ],
440                cwe_id: Some("CWE-79".to_string()),
441            },
442        ]);
443        
444        // Python Rules
445        rules.insert(Language::Python, vec![
446            SecurityRule {
447                id: "py-001".to_string(),
448                name: "SQL Injection Risk".to_string(),
449                pattern: Regex::new(r#"\.execute\s*\(\s*[f]?["'][^"']*%[sd]"#)?,
450                severity: SecuritySeverity::High,
451                category: SecurityCategory::CodeSecurityPattern,
452                description: "String formatting in SQL queries can lead to SQL injection".to_string(),
453                remediation: vec![
454                    "Use parameterized queries instead of string formatting".to_string(),
455                    "Use ORM query builders where possible".to_string(),
456                    "Validate and sanitize all user inputs".to_string(),
457                ],
458                cwe_id: Some("CWE-89".to_string()),
459            },
460            SecurityRule {
461                id: "py-002".to_string(),
462                name: "Pickle Usage".to_string(),
463                pattern: Regex::new(r"\bpickle\.loads?\s*\(")?,
464                severity: SecuritySeverity::High,
465                category: SecurityCategory::CodeSecurityPattern,
466                description: "Pickle can execute arbitrary code during deserialization".to_string(),
467                remediation: vec![
468                    "Avoid pickle for untrusted data".to_string(),
469                    "Use JSON or other safe serialization formats".to_string(),
470                    "If pickle is necessary, validate data sources".to_string(),
471                ],
472                cwe_id: Some("CWE-502".to_string()),
473            },
474        ]);
475        
476        // Add more language rules as needed...
477        
478        Ok(rules)
479    }
480    
481    /// Analyze configuration files for security issues with appropriate progress tracking
482    fn analyze_configuration_security_with_progress(&self, project_root: &Path, multi_progress: &MultiProgress) -> Result<Vec<SecurityFinding>, SecurityError> {
483        debug!("Analyzing configuration security");
484        let mut findings = Vec::new();
485        
486        // Collect relevant files
487        let config_files = self.collect_config_files(project_root)?;
488        
489        if config_files.is_empty() {
490            info!("No configuration files found");
491            return Ok(findings);
492        }
493        
494        let is_verbose = log::max_level() >= log::LevelFilter::Info;
495        
496        info!("📁 Found {} configuration files to analyze", config_files.len());
497        
498        // Create appropriate progress tracking - completely skip in verbose mode
499        let file_pb = if is_verbose {
500            None // No progress bars at all in verbose mode
501        } else {
502            // Normal mode: Show detailed progress
503            let pb = multi_progress.add(ProgressBar::new(config_files.len() as u64));
504            pb.set_style(
505                ProgressStyle::default_bar()
506                    .template("  🔍 {msg} {bar:40.cyan/blue} {pos}/{len} files ({percent}%)")
507                    .unwrap()
508                    .progress_chars("████▉▊▋▌▍▎▏  "),
509            );
510            pb.set_message("Scanning configuration files...");
511            Some(pb)
512        };
513        
514        // Use atomic counter for progress updates if needed
515        use std::sync::atomic::{AtomicUsize, Ordering};
516        use std::sync::Arc;
517        let processed_count = Arc::new(AtomicUsize::new(0));
518        
519        // Analyze each file with appropriate progress tracking
520        let file_findings: Vec<Vec<SecurityFinding>> = config_files
521            .par_iter()
522            .map(|file_path| {
523                let result = self.analyze_file_for_secrets(file_path);
524                
525                // Update progress only in non-verbose mode
526                if let Some(ref pb) = file_pb {
527                    let current = processed_count.fetch_add(1, Ordering::Relaxed) + 1;
528                    if let Some(file_name) = file_path.file_name().and_then(|n| n.to_str()) {
529                        // Truncate long filenames for better display
530                        let display_name = if file_name.len() > 30 {
531                            format!("...{}", &file_name[file_name.len()-27..])
532                        } else {
533                            file_name.to_string()
534                        };
535                        pb.set_message(format!("Scanning {}", display_name));
536                    }
537                    pb.set_position(current as u64);
538                }
539                
540                result
541            })
542            .filter_map(|result| result.ok())
543            .collect();
544        
545        // Finish progress tracking
546        if let Some(pb) = file_pb {
547            pb.finish_with_message(format!("✅ Scanned {} configuration files", config_files.len()));
548        }
549        
550        for mut file_findings in file_findings {
551            findings.append(&mut file_findings);
552        }
553        
554        // Check for common insecure configurations
555        findings.extend(self.check_insecure_configurations(project_root)?);
556        
557        info!("🔍 Found {} configuration security findings", findings.len());
558        Ok(findings)
559    }
560    
561    /// Direct configuration security analysis without progress bars
562    fn analyze_configuration_security(&self, project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
563        debug!("Analyzing configuration security");
564        let mut findings = Vec::new();
565        
566        // Collect relevant files
567        let config_files = self.collect_config_files(project_root)?;
568        
569        if config_files.is_empty() {
570            info!("No configuration files found");
571            return Ok(findings);
572        }
573        
574        info!("📁 Found {} configuration files to analyze", config_files.len());
575        
576        // Analyze each file directly without progress tracking
577        let file_findings: Vec<Vec<SecurityFinding>> = config_files
578            .par_iter()
579            .map(|file_path| self.analyze_file_for_secrets(file_path))
580            .filter_map(|result| result.ok())
581            .collect();
582        
583        for mut file_findings in file_findings {
584            findings.append(&mut file_findings);
585        }
586        
587        // Check for common insecure configurations
588        findings.extend(self.check_insecure_configurations(project_root)?);
589        
590        info!("🔍 Found {} configuration security findings", findings.len());
591        Ok(findings)
592    }
593    
594    /// Analyze code for security patterns with appropriate progress tracking
595    fn analyze_code_security_patterns_with_progress(&self, project_root: &Path, languages: &[DetectedLanguage], multi_progress: &MultiProgress) -> Result<Vec<SecurityFinding>, SecurityError> {
596        debug!("Analyzing code security patterns");
597        let mut findings = Vec::new();
598        
599        // Count total source files across all languages
600        let mut total_files = 0;
601        let mut language_files = Vec::new();
602        
603        for language in languages {
604            if let Some(lang) = Language::from_string(&language.name) {
605                if let Some(_rules) = self.security_rules.get(&lang) {
606                    let source_files = self.collect_source_files(project_root, &language.name)?;
607                    total_files += source_files.len();
608                    language_files.push((language, source_files));
609                }
610            }
611        }
612        
613        if total_files == 0 {
614            info!("No source files found for code pattern analysis");
615            return Ok(findings);
616        }
617        
618        let is_verbose = log::max_level() >= log::LevelFilter::Info;
619        
620        info!("📄 Found {} source files across {} languages", total_files, language_files.len());
621        
622        // Create appropriate progress tracking
623        let code_pb = if is_verbose {
624            // Verbose mode: No sub-progress to avoid visual clutter
625            None
626        } else {
627            // Normal mode: Show detailed progress
628            let pb = multi_progress.add(ProgressBar::new(total_files as u64));
629            pb.set_style(
630                ProgressStyle::default_bar()
631                    .template("  📄 {msg} {bar:40.yellow/white} {pos}/{len} files ({percent}%)")
632                    .unwrap()
633                    .progress_chars("████▉▊▋▌▍▎▏  "),
634            );
635            pb.set_message("Scanning source code...");
636            Some(pb)
637        };
638    
639        
640        // Use atomic counter for progress if needed
641        use std::sync::atomic::{AtomicUsize, Ordering};
642        use std::sync::Arc;
643        let processed_count = Arc::new(AtomicUsize::new(0));
644        
645        // Process all languages
646        for (language, source_files) in language_files {
647            if let Some(lang) = Language::from_string(&language.name) {
648                if let Some(rules) = self.security_rules.get(&lang) {
649                let file_findings: Vec<Vec<SecurityFinding>> = source_files
650                    .par_iter()
651                    .map(|file_path| {
652                        let result = self.analyze_file_with_rules(file_path, rules);
653                        
654                        // Update progress only in non-verbose mode
655                        if let Some(ref pb) = code_pb {
656                            let current = processed_count.fetch_add(1, Ordering::Relaxed) + 1;
657                            if let Some(file_name) = file_path.file_name().and_then(|n| n.to_str()) {
658                                let display_name = if file_name.len() > 25 {
659                                    format!("...{}", &file_name[file_name.len()-22..])
660                                } else {
661                                    file_name.to_string()
662                                };
663                                pb.set_message(format!("Scanning {} ({})", display_name, language.name));
664                            }
665                            pb.set_position(current as u64);
666                        }
667                        
668                        result
669                    })
670                    .filter_map(|result| result.ok())
671                    .collect();
672                
673                for mut file_findings in file_findings {
674                    findings.append(&mut file_findings);
675                }
676                }
677            }
678        }
679        
680        // Finish progress tracking
681        if let Some(pb) = code_pb {
682            pb.finish_with_message(format!("✅ Scanned {} source files", total_files));
683        }
684        
685        info!("🔍 Found {} code security findings", findings.len());
686        Ok(findings)
687    }
688    
689    /// Direct code security analysis without progress bars
690    fn analyze_code_security_patterns(&self, project_root: &Path, languages: &[DetectedLanguage]) -> Result<Vec<SecurityFinding>, SecurityError> {
691        debug!("Analyzing code security patterns");
692        let mut findings = Vec::new();
693        
694        // Count total source files across all languages
695        let mut total_files = 0;
696        let mut language_files = Vec::new();
697        
698        for language in languages {
699            if let Some(lang) = Language::from_string(&language.name) {
700                if let Some(_rules) = self.security_rules.get(&lang) {
701                    let source_files = self.collect_source_files(project_root, &language.name)?;
702                    total_files += source_files.len();
703                    language_files.push((language, source_files));
704                }
705            }
706        }
707        
708        if total_files == 0 {
709            info!("No source files found for code pattern analysis");
710            return Ok(findings);
711        }
712        
713        info!("📄 Found {} source files across {} languages", total_files, language_files.len());
714        
715        // Process all languages without progress tracking
716        for (language, source_files) in language_files {
717            if let Some(lang) = Language::from_string(&language.name) {
718                if let Some(rules) = self.security_rules.get(&lang) {
719                let file_findings: Vec<Vec<SecurityFinding>> = source_files
720                    .par_iter()
721                    .map(|file_path| self.analyze_file_with_rules(file_path, rules))
722                    .filter_map(|result| result.ok())
723                    .collect();
724                
725                for mut file_findings in file_findings {
726                    findings.append(&mut file_findings);
727                }
728                }
729            }
730        }
731
732        info!("🔍 Found {} code security findings", findings.len());
733        Ok(findings)
734    }
735    
736    /// Analyze infrastructure configurations with appropriate progress tracking
737    fn analyze_infrastructure_security_with_progress(&self, project_root: &Path, _technologies: &[DetectedTechnology], multi_progress: &MultiProgress) -> Result<Vec<SecurityFinding>, SecurityError> {
738        debug!("Analyzing infrastructure security");
739        let mut findings = Vec::new();
740        
741        let is_verbose = log::max_level() >= log::LevelFilter::Info;
742        
743        // Create appropriate progress indicator
744        let infra_pb = if is_verbose {
745            // Verbose mode: No spinner to avoid conflicts with logs
746            None
747        } else {
748            // Normal mode: Show spinner
749            let pb = multi_progress.add(ProgressBar::new_spinner());
750            pb.set_style(
751                ProgressStyle::default_spinner()
752                    .template("  🏗️  {msg} {spinner:.magenta}")
753                    .unwrap()
754                    .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "),
755            );
756            pb.enable_steady_tick(std::time::Duration::from_millis(100));
757            Some(pb)
758        };
759        
760        // Check Dockerfile security
761        if let Some(ref pb) = infra_pb {
762            pb.set_message("Checking Dockerfiles & Compose files...");
763        }
764        findings.extend(self.analyze_dockerfile_security(project_root)?);
765        findings.extend(self.analyze_compose_security(project_root)?);
766        
767        // Check CI/CD configurations
768        if let Some(ref pb) = infra_pb {
769            pb.set_message("Checking CI/CD configurations...");
770        }
771        findings.extend(self.analyze_cicd_security(project_root)?);
772        
773        // Finish progress tracking
774        if let Some(pb) = infra_pb {
775            pb.finish_with_message("✅ Infrastructure analysis complete");
776        }
777        info!("🔍 Found {} infrastructure security findings", findings.len());
778        
779        Ok(findings)
780    }
781    
782    /// Direct infrastructure security analysis without progress bars
783    fn analyze_infrastructure_security(&self, project_root: &Path, _technologies: &[DetectedTechnology]) -> Result<Vec<SecurityFinding>, SecurityError> {
784        debug!("Analyzing infrastructure security");
785        let mut findings = Vec::new();
786        
787        // Check Dockerfile security
788        findings.extend(self.analyze_dockerfile_security(project_root)?);
789        findings.extend(self.analyze_compose_security(project_root)?);
790        
791        // Check CI/CD configurations
792        findings.extend(self.analyze_cicd_security(project_root)?);
793        
794        info!("🔍 Found {} infrastructure security findings", findings.len());
795        Ok(findings)
796    }
797    
798    /// Analyze environment variables for security issues
799    fn analyze_environment_security(&self, env_vars: &[EnvVar]) -> Vec<SecurityFinding> {
800        let mut findings = Vec::new();
801        
802        for env_var in env_vars {
803            // Check for sensitive variable names without proper protection
804            if self.is_sensitive_env_var(&env_var.name) && env_var.default_value.is_some() {
805                findings.push(SecurityFinding {
806                    id: format!("env-{}", env_var.name.to_lowercase()),
807                    title: "Sensitive Environment Variable with Default Value".to_string(),
808                    description: format!("Environment variable '{}' appears to contain sensitive data but has a default value", env_var.name),
809                    severity: SecuritySeverity::Medium,
810                    category: SecurityCategory::SecretsExposure,
811                    file_path: None,
812                    line_number: None,
813                    evidence: Some(format!("Variable: {} = {:?}", env_var.name, env_var.default_value)),
814                    remediation: vec![
815                        "Remove default value for sensitive environment variables".to_string(),
816                        "Use a secure secret management system".to_string(),
817                        "Document required environment variables separately".to_string(),
818                    ],
819                    references: vec![
820                        "https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure".to_string(),
821                    ],
822                    cwe_id: Some("CWE-200".to_string()),
823                    compliance_frameworks: vec!["SOC2".to_string(), "GDPR".to_string()],
824                });
825            }
826        }
827        
828        findings
829    }
830    
831    /// Analyze framework-specific security configurations with appropriate progress
832    fn analyze_framework_security_with_progress(&self, project_root: &Path, technologies: &[DetectedTechnology], multi_progress: &MultiProgress) -> Result<Vec<SecurityFinding>, SecurityError> {
833        debug!("Analyzing framework-specific security");
834        let mut findings = Vec::new();
835        
836        let framework_count = technologies.len();
837        if framework_count == 0 {
838            info!("No frameworks detected for security analysis");
839            return Ok(findings);
840        }
841        
842        let is_verbose = log::max_level() >= log::LevelFilter::Info;
843        
844        info!("🔧 Found {} frameworks to analyze", framework_count);
845        
846        // Create appropriate progress indicator
847        let fw_pb = if is_verbose {
848            // Verbose mode: No spinner to avoid conflicts with logs
849            None
850        } else {
851            // Normal mode: Show spinner
852            let pb = multi_progress.add(ProgressBar::new_spinner());
853            pb.set_style(
854                ProgressStyle::default_spinner()
855                    .template("  🔧 {msg} {spinner:.cyan}")
856                    .unwrap()
857                    .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "),
858            );
859            pb.enable_steady_tick(std::time::Duration::from_millis(120));
860            Some(pb)
861        };
862        
863        for tech in technologies {
864            if let Some(ref pb) = fw_pb {
865                pb.set_message(format!("Checking {} configuration...", tech.name));
866            }
867            
868            match tech.name.as_str() {
869                "Express.js" | "Express" => {
870                    findings.extend(self.analyze_express_security(project_root)?);
871                },
872                "Django" => {
873                    findings.extend(self.analyze_django_security(project_root)?);
874                },
875                "Spring Boot" => {
876                    findings.extend(self.analyze_spring_security(project_root)?);
877                },
878                "Next.js" => {
879                    findings.extend(self.analyze_nextjs_security(project_root)?);
880                },
881                // Add more frameworks as needed
882                _ => {}
883            }
884        }
885        
886        // Finish progress tracking
887        if let Some(pb) = fw_pb {
888            pb.finish_with_message("✅ Framework analysis complete");
889        }
890        info!("🔍 Found {} framework security findings", findings.len());
891        
892        Ok(findings)
893    }
894    
895    /// Direct framework security analysis without progress bars
896    fn analyze_framework_security(&self, project_root: &Path, technologies: &[DetectedTechnology]) -> Result<Vec<SecurityFinding>, SecurityError> {
897        debug!("Analyzing framework-specific security");
898        let mut findings = Vec::new();
899        
900        let framework_count = technologies.len();
901        if framework_count == 0 {
902            info!("No frameworks detected for security analysis");
903            return Ok(findings);
904        }
905        
906        info!("🔧 Found {} frameworks to analyze", framework_count);
907        
908        for tech in technologies {
909            match tech.name.as_str() {
910                "Express.js" | "Express" => {
911                    findings.extend(self.analyze_express_security(project_root)?);
912                },
913                "Django" => {
914                    findings.extend(self.analyze_django_security(project_root)?);
915                },
916                "Spring Boot" => {
917                    findings.extend(self.analyze_spring_security(project_root)?);
918                },
919                "Next.js" => {
920                    findings.extend(self.analyze_nextjs_security(project_root)?);
921                },
922                // Add more frameworks as needed
923                _ => {}
924            }
925        }
926        
927        info!("🔍 Found {} framework security findings", findings.len());
928        Ok(findings)
929    }
930    
931    // Helper methods for specific analyses...
932    
933    fn collect_config_files(&self, project_root: &Path) -> Result<Vec<PathBuf>, SecurityError> {
934        let patterns = vec![
935            "*.env*", "*.conf", "*.config", "*.ini", "*.yaml", "*.yml", 
936            "*.toml", "docker-compose*.yml", "Dockerfile*",
937            ".github/**/*.yml", ".gitlab-ci.yml", "package.json",
938            "requirements.txt", "Cargo.toml", "go.mod", "pom.xml",
939        ];
940        
941        let mut files = crate::common::file_utils::find_files_by_patterns(project_root, &patterns)
942            .map_err(|e| SecurityError::Io(e))?;
943        
944        // Filter out files matching ignore patterns
945        files.retain(|file| {
946            let file_name = file.file_name()
947                .and_then(|n| n.to_str())
948                .unwrap_or("");
949            let file_path = file.to_string_lossy();
950            
951            !self.config.ignore_patterns.iter().any(|pattern| {
952                if pattern.contains('*') {
953                    // Use glob matching for wildcard patterns
954                    glob::Pattern::new(pattern)
955                        .map(|p| p.matches(&file_path) || p.matches(file_name))
956                        .unwrap_or(false)
957                } else {
958                    // Exact string matching
959                    file_path.contains(pattern) || file_name.contains(pattern)
960                }
961            })
962        });
963        
964        Ok(files)
965    }
966    
967    fn analyze_file_for_secrets(&self, file_path: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
968        let content = fs::read_to_string(file_path)?;
969        let mut findings = Vec::new();
970        
971        for (line_num, line) in content.lines().enumerate() {
972            for pattern in &self.secret_patterns {
973                if let Some(captures) = pattern.pattern.find(line) {
974                    // Skip if it looks like a placeholder or example
975                    if self.is_likely_placeholder(line) {
976                        continue;
977                    }
978                    
979                    findings.push(SecurityFinding {
980                        id: format!("secret-{}-{}", pattern.name.to_lowercase().replace(' ', "-"), line_num),
981                        title: format!("Potential {} Exposure", pattern.name),
982                        description: pattern.description.clone(),
983                        severity: pattern.severity.clone(),
984                        category: SecurityCategory::SecretsExposure,
985                        file_path: Some(file_path.to_path_buf()),
986                        line_number: Some(line_num + 1),
987                        evidence: Some(format!("Line: {}", line.trim())),
988                        remediation: vec![
989                            "Remove sensitive data from source code".to_string(),
990                            "Use environment variables for secrets".to_string(),
991                            "Consider using a secure secret management service".to_string(),
992                            "Add this file to .gitignore if it contains secrets".to_string(),
993                        ],
994                        references: vec![
995                            "https://owasp.org/www-project-top-ten/2021/A05_2021-Security_Misconfiguration/".to_string(),
996                        ],
997                        cwe_id: Some("CWE-200".to_string()),
998                        compliance_frameworks: vec!["SOC2".to_string(), "GDPR".to_string()],
999                    });
1000                }
1001            }
1002        }
1003        
1004        Ok(findings)
1005    }
1006    
1007    fn is_likely_placeholder(&self, line: &str) -> bool {
1008        let placeholder_indicators = [
1009            "example", "placeholder", "your_", "insert_", "replace_",
1010            "xxx", "yyy", "zzz", "fake", "dummy", "test_key",
1011            "sk-xxxxxxxx", "AKIA00000000",
1012        ];
1013        
1014        let hash_indicators = [
1015            "checksum", "hash", "sha1", "sha256", "md5", "commit",
1016            "fingerprint", "digest", "advisory", "ghsa-", "cve-",
1017            "rustc_fingerprint", "last-commit", "references",
1018        ];
1019        
1020        let line_lower = line.to_lowercase();
1021        
1022        // Check for placeholder indicators
1023        if placeholder_indicators.iter().any(|indicator| line_lower.contains(indicator)) {
1024            return true;
1025        }
1026        
1027        // Check for hash/checksum context
1028        if hash_indicators.iter().any(|indicator| line_lower.contains(indicator)) {
1029            return true;
1030        }
1031        
1032        // Check if it's a URL or path (often contains hash-like strings)
1033        if line_lower.contains("http") || line_lower.contains("github.com") {
1034            return true;
1035        }
1036        
1037        // Check if it's likely a hex-only string (git commits, checksums)
1038        if let Some(potential_hash) = self.extract_potential_hash(line) {
1039            if potential_hash.len() >= 32 && self.is_hex_only(&potential_hash) {
1040                return true; // Likely a SHA hash
1041            }
1042        }
1043        
1044        false
1045    }
1046    
1047    fn extract_potential_hash(&self, line: &str) -> Option<String> {
1048        // Look for quoted strings that might be hashes
1049        if let Some(start) = line.find('"') {
1050            if let Some(end) = line[start + 1..].find('"') {
1051                let potential = &line[start + 1..start + 1 + end];
1052                if potential.len() >= 32 {
1053                    return Some(potential.to_string());
1054                }
1055            }
1056        }
1057        None
1058    }
1059    
1060    fn is_hex_only(&self, s: &str) -> bool {
1061        s.chars().all(|c| c.is_ascii_hexdigit())
1062    }
1063    
1064    fn is_sensitive_env_var(&self, name: &str) -> bool {
1065        let sensitive_patterns = [
1066            "password", "secret", "key", "token", "auth", "api",
1067            "private", "credential", "cert", "ssl", "tls",
1068        ];
1069        
1070        let name_lower = name.to_lowercase();
1071        sensitive_patterns.iter().any(|pattern| name_lower.contains(pattern))
1072    }
1073    
1074    // Placeholder implementations for specific framework analysis
1075    fn analyze_express_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1076        // TODO: Implement Express.js specific security checks
1077        Ok(vec![])
1078    }
1079    
1080    fn analyze_django_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1081        // TODO: Implement Django specific security checks
1082        Ok(vec![])
1083    }
1084    
1085    fn analyze_spring_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1086        // TODO: Implement Spring Boot specific security checks
1087        Ok(vec![])
1088    }
1089    
1090    fn analyze_nextjs_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1091        // TODO: Implement Next.js specific security checks
1092        Ok(vec![])
1093    }
1094    
1095    fn analyze_dockerfile_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1096        // TODO: Implement Dockerfile security analysis
1097        Ok(vec![])
1098    }
1099    
1100    fn analyze_compose_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1101        // TODO: Implement Docker Compose security analysis
1102        Ok(vec![])
1103    }
1104    
1105    fn analyze_cicd_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1106        // TODO: Implement CI/CD security analysis
1107        Ok(vec![])
1108    }
1109    
1110    // Additional helper methods...
1111    fn collect_source_files(&self, project_root: &Path, language: &str) -> Result<Vec<PathBuf>, SecurityError> {
1112        // TODO: Implement source file collection based on language
1113        Ok(vec![])
1114    }
1115    
1116    fn analyze_file_with_rules(&self, _file_path: &Path, _rules: &[SecurityRule]) -> Result<Vec<SecurityFinding>, SecurityError> {
1117        // TODO: Implement rule-based file analysis
1118        Ok(vec![])
1119    }
1120    
1121    fn check_insecure_configurations(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1122        // TODO: Implement insecure configuration checks
1123        Ok(vec![])
1124    }
1125    
1126    /// Deduplicate findings to avoid multiple reports for the same secret/issue
1127    fn deduplicate_findings(&self, mut findings: Vec<SecurityFinding>) -> Vec<SecurityFinding> {
1128        use std::collections::HashSet;
1129        
1130        let mut seen_secrets: HashSet<String> = HashSet::new();
1131        let mut deduplicated = Vec::new();
1132        
1133        // Sort by priority: more specific patterns first, then by severity
1134        findings.sort_by(|a, b| {
1135            // First, prioritize specific patterns over generic ones
1136            let a_priority = self.get_pattern_priority(&a.title);
1137            let b_priority = self.get_pattern_priority(&b.title);
1138            
1139            match a_priority.cmp(&b_priority) {
1140                std::cmp::Ordering::Equal => {
1141                    // If same priority, sort by severity (most critical first)
1142                    a.severity.cmp(&b.severity)
1143                }
1144                other => other
1145            }
1146        });
1147        
1148        for finding in findings {
1149            let key = self.generate_finding_key(&finding);
1150            
1151            if !seen_secrets.contains(&key) {
1152                seen_secrets.insert(key);
1153                deduplicated.push(finding);
1154            }
1155        }
1156        
1157        deduplicated
1158    }
1159    
1160    /// Generate a unique key for deduplication based on the type of finding
1161    fn generate_finding_key(&self, finding: &SecurityFinding) -> String {
1162        match finding.category {
1163            SecurityCategory::SecretsExposure => {
1164                // For secrets, deduplicate based on file path and the actual secret content
1165                if let Some(evidence) = &finding.evidence {
1166                    if let Some(file_path) = &finding.file_path {
1167                        // Extract the secret value from the evidence line
1168                        if let Some(secret_value) = self.extract_secret_value(evidence) {
1169                            return format!("secret:{}:{}", file_path.display(), secret_value);
1170                        }
1171                        // Fallback to file + line if we can't extract the value
1172                        if let Some(line_num) = finding.line_number {
1173                            return format!("secret:{}:{}", file_path.display(), line_num);
1174                        }
1175                    }
1176                }
1177                // Fallback for environment variables or other secrets without file paths
1178                format!("secret:{}", finding.title)
1179            }
1180            _ => {
1181                // For non-secret findings, use file path + line number + title
1182                if let Some(file_path) = &finding.file_path {
1183                    if let Some(line_num) = finding.line_number {
1184                        format!("other:{}:{}:{}", file_path.display(), line_num, finding.title)
1185                    } else {
1186                        format!("other:{}:{}", file_path.display(), finding.title)
1187                    }
1188                } else {
1189                    format!("other:{}", finding.title)
1190                }
1191            }
1192        }
1193    }
1194    
1195    /// Extract secret value from evidence line for deduplication
1196    fn extract_secret_value(&self, evidence: &str) -> Option<String> {
1197        // Look for patterns like "KEY=value" or "KEY: value"
1198        if let Some(pos) = evidence.find('=') {
1199            let value = evidence[pos + 1..].trim();
1200            // Remove quotes if present
1201            let value = value.trim_matches('"').trim_matches('\'');
1202            if value.len() > 10 { // Only consider substantial values
1203                return Some(value.to_string());
1204            }
1205        }
1206        
1207        // Look for patterns like "key: value" in YAML/JSON
1208        if let Some(pos) = evidence.find(':') {
1209            let value = evidence[pos + 1..].trim();
1210            let value = value.trim_matches('"').trim_matches('\'');
1211            if value.len() > 10 {
1212                return Some(value.to_string());
1213            }
1214        }
1215        
1216        None
1217    }
1218    
1219    /// Get pattern priority for deduplication (lower number = higher priority)
1220    fn get_pattern_priority(&self, title: &str) -> u8 {
1221        // Most specific patterns get highest priority (lowest number)
1222        if title.contains("AWS Access Key") { return 1; }
1223        if title.contains("AWS Secret Key") { return 1; }
1224        if title.contains("S3 Secret Key") { return 1; }
1225        if title.contains("GitHub Token") { return 1; }
1226        if title.contains("OpenAI API Key") { return 1; }
1227        if title.contains("Stripe") { return 1; }
1228        if title.contains("RSA Private Key") { return 1; }
1229        if title.contains("SSH Private Key") { return 1; }
1230        
1231        // JWT and specific API keys are more specific than generic
1232        if title.contains("JWT Secret") { return 2; }
1233        if title.contains("Database URL") { return 2; }
1234        
1235        // Generic API key patterns are less specific
1236        if title.contains("API Key") { return 3; }
1237        
1238        // Environment variable findings are less specific
1239        if title.contains("Environment Variable") { return 4; }
1240        
1241        // Generic patterns get lowest priority (highest number)
1242        if title.contains("Generic Secret") { return 5; }
1243        
1244        // Default priority for other patterns
1245        3
1246    }
1247    
1248    fn count_by_severity(&self, findings: &[SecurityFinding]) -> HashMap<SecuritySeverity, usize> {
1249        let mut counts = HashMap::new();
1250        for finding in findings {
1251            *counts.entry(finding.severity.clone()).or_insert(0) += 1;
1252        }
1253        counts
1254    }
1255    
1256    fn count_by_category(&self, findings: &[SecurityFinding]) -> HashMap<SecurityCategory, usize> {
1257        let mut counts = HashMap::new();
1258        for finding in findings {
1259            *counts.entry(finding.category.clone()).or_insert(0) += 1;
1260        }
1261        counts
1262    }
1263    
1264    fn calculate_security_score(&self, findings: &[SecurityFinding]) -> f32 {
1265        if findings.is_empty() {
1266            return 100.0;
1267        }
1268        
1269        let total_penalty = findings.iter().map(|f| match f.severity {
1270            SecuritySeverity::Critical => 25.0,
1271            SecuritySeverity::High => 15.0,
1272            SecuritySeverity::Medium => 8.0,
1273            SecuritySeverity::Low => 3.0,
1274            SecuritySeverity::Info => 1.0,
1275        }).sum::<f32>();
1276        
1277        (100.0 - total_penalty).max(0.0)
1278    }
1279    
1280    fn determine_risk_level(&self, findings: &[SecurityFinding]) -> SecuritySeverity {
1281        if findings.iter().any(|f| f.severity == SecuritySeverity::Critical) {
1282            SecuritySeverity::Critical
1283        } else if findings.iter().any(|f| f.severity == SecuritySeverity::High) {
1284            SecuritySeverity::High
1285        } else if findings.iter().any(|f| f.severity == SecuritySeverity::Medium) {
1286            SecuritySeverity::Medium
1287        } else if !findings.is_empty() {
1288            SecuritySeverity::Low
1289        } else {
1290            SecuritySeverity::Info
1291        }
1292    }
1293    
1294    fn assess_compliance(&self, _findings: &[SecurityFinding], _technologies: &[DetectedTechnology]) -> HashMap<String, ComplianceStatus> {
1295        // TODO: Implement compliance assessment
1296        HashMap::new()
1297    }
1298    
1299    fn generate_recommendations(&self, findings: &[SecurityFinding], _technologies: &[DetectedTechnology]) -> Vec<String> {
1300        let mut recommendations = Vec::new();
1301        
1302        if findings.iter().any(|f| f.category == SecurityCategory::SecretsExposure) {
1303            recommendations.push("Implement a secure secret management strategy".to_string());
1304        }
1305        
1306        if findings.iter().any(|f| f.severity == SecuritySeverity::Critical) {
1307            recommendations.push("Address critical security findings immediately".to_string());
1308        }
1309        
1310        // Add more generic recommendations...
1311        
1312        recommendations
1313    }
1314}
1315
1316
1317
1318#[cfg(test)]
1319mod tests {
1320    use super::*;
1321    
1322    #[test]
1323    fn test_security_score_calculation() {
1324        let analyzer = SecurityAnalyzer::new().unwrap();
1325        
1326        let findings = vec![
1327            SecurityFinding {
1328                id: "test-1".to_string(),
1329                title: "Test Critical".to_string(),
1330                description: "Test".to_string(),
1331                severity: SecuritySeverity::Critical,
1332                category: SecurityCategory::SecretsExposure,
1333                file_path: None,
1334                line_number: None,
1335                evidence: None,
1336                remediation: vec![],
1337                references: vec![],
1338                cwe_id: None,
1339                compliance_frameworks: vec![],
1340            }
1341        ];
1342        
1343        let score = analyzer.calculate_security_score(&findings);
1344        assert_eq!(score, 75.0); // 100 - 25 (critical penalty)
1345    }
1346    
1347    #[test]
1348    fn test_secret_pattern_matching() {
1349        let analyzer = SecurityAnalyzer::new().unwrap();
1350        
1351        // Test if placeholder detection works
1352        assert!(analyzer.is_likely_placeholder("API_KEY=sk-xxxxxxxxxxxxxxxx"));
1353        assert!(!analyzer.is_likely_placeholder("API_KEY=sk-1234567890abcdef"));
1354    }
1355    
1356    #[test]
1357    fn test_sensitive_env_var_detection() {
1358        let analyzer = SecurityAnalyzer::new().unwrap();
1359        
1360        assert!(analyzer.is_sensitive_env_var("DATABASE_PASSWORD"));
1361        assert!(analyzer.is_sensitive_env_var("JWT_SECRET"));
1362        assert!(!analyzer.is_sensitive_env_var("PORT"));
1363        assert!(!analyzer.is_sensitive_env_var("NODE_ENV"));
1364    }
1365}