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(_rules) = self.security_rules.get(&Language::from_string(&language.name)) {
605                let source_files = self.collect_source_files(project_root, &language.name)?;
606                total_files += source_files.len();
607                language_files.push((language, source_files));
608            }
609        }
610        
611        if total_files == 0 {
612            info!("No source files found for code pattern analysis");
613            return Ok(findings);
614        }
615        
616        let is_verbose = log::max_level() >= log::LevelFilter::Info;
617        
618        info!("📄 Found {} source files across {} languages", total_files, language_files.len());
619        
620        // Create appropriate progress tracking
621        let code_pb = if is_verbose {
622            // Verbose mode: No sub-progress to avoid visual clutter
623            None
624        } else {
625            // Normal mode: Show detailed progress
626            let pb = multi_progress.add(ProgressBar::new(total_files as u64));
627            pb.set_style(
628                ProgressStyle::default_bar()
629                    .template("  📄 {msg} {bar:40.yellow/white} {pos}/{len} files ({percent}%)")
630                    .unwrap()
631                    .progress_chars("████▉▊▋▌▍▎▏  "),
632            );
633            pb.set_message("Scanning source code...");
634            Some(pb)
635        };
636        
637        // Use atomic counter for progress if needed
638        use std::sync::atomic::{AtomicUsize, Ordering};
639        use std::sync::Arc;
640        let processed_count = Arc::new(AtomicUsize::new(0));
641        
642        // Process all languages
643        for (language, source_files) in language_files {
644            if let Some(rules) = self.security_rules.get(&Language::from_string(&language.name)) {
645                let file_findings: Vec<Vec<SecurityFinding>> = source_files
646                    .par_iter()
647                    .map(|file_path| {
648                        let result = self.analyze_file_with_rules(file_path, rules);
649                        
650                        // Update progress only in non-verbose mode
651                        if let Some(ref pb) = code_pb {
652                            let current = processed_count.fetch_add(1, Ordering::Relaxed) + 1;
653                            if let Some(file_name) = file_path.file_name().and_then(|n| n.to_str()) {
654                                let display_name = if file_name.len() > 25 {
655                                    format!("...{}", &file_name[file_name.len()-22..])
656                                } else {
657                                    file_name.to_string()
658                                };
659                                pb.set_message(format!("Scanning {} ({})", display_name, language.name));
660                            }
661                            pb.set_position(current as u64);
662                        }
663                        
664                        result
665                    })
666                    .filter_map(|result| result.ok())
667                    .collect();
668                
669                for mut file_findings in file_findings {
670                    findings.append(&mut file_findings);
671                }
672            }
673        }
674        
675        // Finish progress tracking
676        if let Some(pb) = code_pb {
677            pb.finish_with_message(format!("✅ Scanned {} source files", total_files));
678        }
679        
680        info!("🔍 Found {} code security findings", findings.len());
681        Ok(findings)
682    }
683    
684    /// Direct code security analysis without progress bars
685    fn analyze_code_security_patterns(&self, project_root: &Path, languages: &[DetectedLanguage]) -> Result<Vec<SecurityFinding>, SecurityError> {
686        debug!("Analyzing code security patterns");
687        let mut findings = Vec::new();
688        
689        // Count total source files across all languages
690        let mut total_files = 0;
691        let mut language_files = Vec::new();
692        
693        for language in languages {
694            if let Some(_rules) = self.security_rules.get(&Language::from_string(&language.name)) {
695                let source_files = self.collect_source_files(project_root, &language.name)?;
696                total_files += source_files.len();
697                language_files.push((language, source_files));
698            }
699        }
700        
701        if total_files == 0 {
702            info!("No source files found for code pattern analysis");
703            return Ok(findings);
704        }
705        
706        info!("📄 Found {} source files across {} languages", total_files, language_files.len());
707        
708        // Process all languages without progress tracking
709        for (language, source_files) in language_files {
710            if let Some(rules) = self.security_rules.get(&Language::from_string(&language.name)) {
711                let file_findings: Vec<Vec<SecurityFinding>> = source_files
712                    .par_iter()
713                    .map(|file_path| self.analyze_file_with_rules(file_path, rules))
714                    .filter_map(|result| result.ok())
715                    .collect();
716                
717                for mut file_findings in file_findings {
718                    findings.append(&mut file_findings);
719                }
720            }
721        }
722        
723        info!("🔍 Found {} code security findings", findings.len());
724        Ok(findings)
725    }
726    
727    /// Analyze infrastructure configurations with appropriate progress tracking
728    fn analyze_infrastructure_security_with_progress(&self, project_root: &Path, _technologies: &[DetectedTechnology], multi_progress: &MultiProgress) -> Result<Vec<SecurityFinding>, SecurityError> {
729        debug!("Analyzing infrastructure security");
730        let mut findings = Vec::new();
731        
732        let is_verbose = log::max_level() >= log::LevelFilter::Info;
733        
734        // Create appropriate progress indicator
735        let infra_pb = if is_verbose {
736            // Verbose mode: No spinner to avoid conflicts with logs
737            None
738        } else {
739            // Normal mode: Show spinner
740            let pb = multi_progress.add(ProgressBar::new_spinner());
741            pb.set_style(
742                ProgressStyle::default_spinner()
743                    .template("  🏗️  {msg} {spinner:.magenta}")
744                    .unwrap()
745                    .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "),
746            );
747            pb.enable_steady_tick(std::time::Duration::from_millis(100));
748            Some(pb)
749        };
750        
751        // Check Dockerfile security
752        if let Some(ref pb) = infra_pb {
753            pb.set_message("Checking Dockerfiles & Compose files...");
754        }
755        findings.extend(self.analyze_dockerfile_security(project_root)?);
756        findings.extend(self.analyze_compose_security(project_root)?);
757        
758        // Check CI/CD configurations
759        if let Some(ref pb) = infra_pb {
760            pb.set_message("Checking CI/CD configurations...");
761        }
762        findings.extend(self.analyze_cicd_security(project_root)?);
763        
764        // Finish progress tracking
765        if let Some(pb) = infra_pb {
766            pb.finish_with_message("✅ Infrastructure analysis complete");
767        }
768        info!("🔍 Found {} infrastructure security findings", findings.len());
769        
770        Ok(findings)
771    }
772    
773    /// Direct infrastructure security analysis without progress bars
774    fn analyze_infrastructure_security(&self, project_root: &Path, _technologies: &[DetectedTechnology]) -> Result<Vec<SecurityFinding>, SecurityError> {
775        debug!("Analyzing infrastructure security");
776        let mut findings = Vec::new();
777        
778        // Check Dockerfile security
779        findings.extend(self.analyze_dockerfile_security(project_root)?);
780        findings.extend(self.analyze_compose_security(project_root)?);
781        
782        // Check CI/CD configurations
783        findings.extend(self.analyze_cicd_security(project_root)?);
784        
785        info!("🔍 Found {} infrastructure security findings", findings.len());
786        Ok(findings)
787    }
788    
789    /// Analyze environment variables for security issues
790    fn analyze_environment_security(&self, env_vars: &[EnvVar]) -> Vec<SecurityFinding> {
791        let mut findings = Vec::new();
792        
793        for env_var in env_vars {
794            // Check for sensitive variable names without proper protection
795            if self.is_sensitive_env_var(&env_var.name) && env_var.default_value.is_some() {
796                findings.push(SecurityFinding {
797                    id: format!("env-{}", env_var.name.to_lowercase()),
798                    title: "Sensitive Environment Variable with Default Value".to_string(),
799                    description: format!("Environment variable '{}' appears to contain sensitive data but has a default value", env_var.name),
800                    severity: SecuritySeverity::Medium,
801                    category: SecurityCategory::SecretsExposure,
802                    file_path: None,
803                    line_number: None,
804                    evidence: Some(format!("Variable: {} = {:?}", env_var.name, env_var.default_value)),
805                    remediation: vec![
806                        "Remove default value for sensitive environment variables".to_string(),
807                        "Use a secure secret management system".to_string(),
808                        "Document required environment variables separately".to_string(),
809                    ],
810                    references: vec![
811                        "https://owasp.org/www-project-top-ten/2017/A3_2017-Sensitive_Data_Exposure".to_string(),
812                    ],
813                    cwe_id: Some("CWE-200".to_string()),
814                    compliance_frameworks: vec!["SOC2".to_string(), "GDPR".to_string()],
815                });
816            }
817        }
818        
819        findings
820    }
821    
822    /// Analyze framework-specific security configurations with appropriate progress
823    fn analyze_framework_security_with_progress(&self, project_root: &Path, technologies: &[DetectedTechnology], multi_progress: &MultiProgress) -> Result<Vec<SecurityFinding>, SecurityError> {
824        debug!("Analyzing framework-specific security");
825        let mut findings = Vec::new();
826        
827        let framework_count = technologies.len();
828        if framework_count == 0 {
829            info!("No frameworks detected for security analysis");
830            return Ok(findings);
831        }
832        
833        let is_verbose = log::max_level() >= log::LevelFilter::Info;
834        
835        info!("🔧 Found {} frameworks to analyze", framework_count);
836        
837        // Create appropriate progress indicator
838        let fw_pb = if is_verbose {
839            // Verbose mode: No spinner to avoid conflicts with logs
840            None
841        } else {
842            // Normal mode: Show spinner
843            let pb = multi_progress.add(ProgressBar::new_spinner());
844            pb.set_style(
845                ProgressStyle::default_spinner()
846                    .template("  🔧 {msg} {spinner:.cyan}")
847                    .unwrap()
848                    .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "),
849            );
850            pb.enable_steady_tick(std::time::Duration::from_millis(120));
851            Some(pb)
852        };
853        
854        for tech in technologies {
855            if let Some(ref pb) = fw_pb {
856                pb.set_message(format!("Checking {} configuration...", tech.name));
857            }
858            
859            match tech.name.as_str() {
860                "Express.js" | "Express" => {
861                    findings.extend(self.analyze_express_security(project_root)?);
862                },
863                "Django" => {
864                    findings.extend(self.analyze_django_security(project_root)?);
865                },
866                "Spring Boot" => {
867                    findings.extend(self.analyze_spring_security(project_root)?);
868                },
869                "Next.js" => {
870                    findings.extend(self.analyze_nextjs_security(project_root)?);
871                },
872                // Add more frameworks as needed
873                _ => {}
874            }
875        }
876        
877        // Finish progress tracking
878        if let Some(pb) = fw_pb {
879            pb.finish_with_message("✅ Framework analysis complete");
880        }
881        info!("🔍 Found {} framework security findings", findings.len());
882        
883        Ok(findings)
884    }
885    
886    /// Direct framework security analysis without progress bars
887    fn analyze_framework_security(&self, project_root: &Path, technologies: &[DetectedTechnology]) -> Result<Vec<SecurityFinding>, SecurityError> {
888        debug!("Analyzing framework-specific security");
889        let mut findings = Vec::new();
890        
891        let framework_count = technologies.len();
892        if framework_count == 0 {
893            info!("No frameworks detected for security analysis");
894            return Ok(findings);
895        }
896        
897        info!("🔧 Found {} frameworks to analyze", framework_count);
898        
899        for tech in technologies {
900            match tech.name.as_str() {
901                "Express.js" | "Express" => {
902                    findings.extend(self.analyze_express_security(project_root)?);
903                },
904                "Django" => {
905                    findings.extend(self.analyze_django_security(project_root)?);
906                },
907                "Spring Boot" => {
908                    findings.extend(self.analyze_spring_security(project_root)?);
909                },
910                "Next.js" => {
911                    findings.extend(self.analyze_nextjs_security(project_root)?);
912                },
913                // Add more frameworks as needed
914                _ => {}
915            }
916        }
917        
918        info!("🔍 Found {} framework security findings", findings.len());
919        Ok(findings)
920    }
921    
922    // Helper methods for specific analyses...
923    
924    fn collect_config_files(&self, project_root: &Path) -> Result<Vec<PathBuf>, SecurityError> {
925        let patterns = vec![
926            "*.env*", "*.conf", "*.config", "*.ini", "*.yaml", "*.yml", 
927            "*.toml", "docker-compose*.yml", "Dockerfile*",
928            ".github/**/*.yml", ".gitlab-ci.yml", "package.json",
929            "requirements.txt", "Cargo.toml", "go.mod", "pom.xml",
930        ];
931        
932        let mut files = crate::common::file_utils::find_files_by_patterns(project_root, &patterns)
933            .map_err(|e| SecurityError::Io(e))?;
934        
935        // Filter out files matching ignore patterns
936        files.retain(|file| {
937            let file_name = file.file_name()
938                .and_then(|n| n.to_str())
939                .unwrap_or("");
940            let file_path = file.to_string_lossy();
941            
942            !self.config.ignore_patterns.iter().any(|pattern| {
943                if pattern.contains('*') {
944                    // Use glob matching for wildcard patterns
945                    glob::Pattern::new(pattern)
946                        .map(|p| p.matches(&file_path) || p.matches(file_name))
947                        .unwrap_or(false)
948                } else {
949                    // Exact string matching
950                    file_path.contains(pattern) || file_name.contains(pattern)
951                }
952            })
953        });
954        
955        Ok(files)
956    }
957    
958    fn analyze_file_for_secrets(&self, file_path: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
959        let content = fs::read_to_string(file_path)?;
960        let mut findings = Vec::new();
961        
962        for (line_num, line) in content.lines().enumerate() {
963            for pattern in &self.secret_patterns {
964                if let Some(captures) = pattern.pattern.find(line) {
965                    // Skip if it looks like a placeholder or example
966                    if self.is_likely_placeholder(line) {
967                        continue;
968                    }
969                    
970                    findings.push(SecurityFinding {
971                        id: format!("secret-{}-{}", pattern.name.to_lowercase().replace(' ', "-"), line_num),
972                        title: format!("Potential {} Exposure", pattern.name),
973                        description: pattern.description.clone(),
974                        severity: pattern.severity.clone(),
975                        category: SecurityCategory::SecretsExposure,
976                        file_path: Some(file_path.to_path_buf()),
977                        line_number: Some(line_num + 1),
978                        evidence: Some(format!("Line: {}", line.trim())),
979                        remediation: vec![
980                            "Remove sensitive data from source code".to_string(),
981                            "Use environment variables for secrets".to_string(),
982                            "Consider using a secure secret management service".to_string(),
983                            "Add this file to .gitignore if it contains secrets".to_string(),
984                        ],
985                        references: vec![
986                            "https://owasp.org/www-project-top-ten/2021/A05_2021-Security_Misconfiguration/".to_string(),
987                        ],
988                        cwe_id: Some("CWE-200".to_string()),
989                        compliance_frameworks: vec!["SOC2".to_string(), "GDPR".to_string()],
990                    });
991                }
992            }
993        }
994        
995        Ok(findings)
996    }
997    
998    fn is_likely_placeholder(&self, line: &str) -> bool {
999        let placeholder_indicators = [
1000            "example", "placeholder", "your_", "insert_", "replace_",
1001            "xxx", "yyy", "zzz", "fake", "dummy", "test_key",
1002            "sk-xxxxxxxx", "AKIA00000000",
1003        ];
1004        
1005        let hash_indicators = [
1006            "checksum", "hash", "sha1", "sha256", "md5", "commit",
1007            "fingerprint", "digest", "advisory", "ghsa-", "cve-",
1008            "rustc_fingerprint", "last-commit", "references",
1009        ];
1010        
1011        let line_lower = line.to_lowercase();
1012        
1013        // Check for placeholder indicators
1014        if placeholder_indicators.iter().any(|indicator| line_lower.contains(indicator)) {
1015            return true;
1016        }
1017        
1018        // Check for hash/checksum context
1019        if hash_indicators.iter().any(|indicator| line_lower.contains(indicator)) {
1020            return true;
1021        }
1022        
1023        // Check if it's a URL or path (often contains hash-like strings)
1024        if line_lower.contains("http") || line_lower.contains("github.com") {
1025            return true;
1026        }
1027        
1028        // Check if it's likely a hex-only string (git commits, checksums)
1029        if let Some(potential_hash) = self.extract_potential_hash(line) {
1030            if potential_hash.len() >= 32 && self.is_hex_only(&potential_hash) {
1031                return true; // Likely a SHA hash
1032            }
1033        }
1034        
1035        false
1036    }
1037    
1038    fn extract_potential_hash(&self, line: &str) -> Option<String> {
1039        // Look for quoted strings that might be hashes
1040        if let Some(start) = line.find('"') {
1041            if let Some(end) = line[start + 1..].find('"') {
1042                let potential = &line[start + 1..start + 1 + end];
1043                if potential.len() >= 32 {
1044                    return Some(potential.to_string());
1045                }
1046            }
1047        }
1048        None
1049    }
1050    
1051    fn is_hex_only(&self, s: &str) -> bool {
1052        s.chars().all(|c| c.is_ascii_hexdigit())
1053    }
1054    
1055    fn is_sensitive_env_var(&self, name: &str) -> bool {
1056        let sensitive_patterns = [
1057            "password", "secret", "key", "token", "auth", "api",
1058            "private", "credential", "cert", "ssl", "tls",
1059        ];
1060        
1061        let name_lower = name.to_lowercase();
1062        sensitive_patterns.iter().any(|pattern| name_lower.contains(pattern))
1063    }
1064    
1065    // Placeholder implementations for specific framework analysis
1066    fn analyze_express_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1067        // TODO: Implement Express.js specific security checks
1068        Ok(vec![])
1069    }
1070    
1071    fn analyze_django_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1072        // TODO: Implement Django specific security checks
1073        Ok(vec![])
1074    }
1075    
1076    fn analyze_spring_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1077        // TODO: Implement Spring Boot specific security checks
1078        Ok(vec![])
1079    }
1080    
1081    fn analyze_nextjs_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1082        // TODO: Implement Next.js specific security checks
1083        Ok(vec![])
1084    }
1085    
1086    fn analyze_dockerfile_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1087        // TODO: Implement Dockerfile security analysis
1088        Ok(vec![])
1089    }
1090    
1091    fn analyze_compose_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1092        // TODO: Implement Docker Compose security analysis
1093        Ok(vec![])
1094    }
1095    
1096    fn analyze_cicd_security(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1097        // TODO: Implement CI/CD security analysis
1098        Ok(vec![])
1099    }
1100    
1101    // Additional helper methods...
1102    fn collect_source_files(&self, project_root: &Path, language: &str) -> Result<Vec<PathBuf>, SecurityError> {
1103        // TODO: Implement source file collection based on language
1104        Ok(vec![])
1105    }
1106    
1107    fn analyze_file_with_rules(&self, _file_path: &Path, _rules: &[SecurityRule]) -> Result<Vec<SecurityFinding>, SecurityError> {
1108        // TODO: Implement rule-based file analysis
1109        Ok(vec![])
1110    }
1111    
1112    fn check_insecure_configurations(&self, _project_root: &Path) -> Result<Vec<SecurityFinding>, SecurityError> {
1113        // TODO: Implement insecure configuration checks
1114        Ok(vec![])
1115    }
1116    
1117    /// Deduplicate findings to avoid multiple reports for the same secret/issue
1118    fn deduplicate_findings(&self, mut findings: Vec<SecurityFinding>) -> Vec<SecurityFinding> {
1119        use std::collections::HashSet;
1120        
1121        let mut seen_secrets: HashSet<String> = HashSet::new();
1122        let mut deduplicated = Vec::new();
1123        
1124        // Sort by priority: more specific patterns first, then by severity
1125        findings.sort_by(|a, b| {
1126            // First, prioritize specific patterns over generic ones
1127            let a_priority = self.get_pattern_priority(&a.title);
1128            let b_priority = self.get_pattern_priority(&b.title);
1129            
1130            match a_priority.cmp(&b_priority) {
1131                std::cmp::Ordering::Equal => {
1132                    // If same priority, sort by severity (most critical first)
1133                    a.severity.cmp(&b.severity)
1134                }
1135                other => other
1136            }
1137        });
1138        
1139        for finding in findings {
1140            let key = self.generate_finding_key(&finding);
1141            
1142            if !seen_secrets.contains(&key) {
1143                seen_secrets.insert(key);
1144                deduplicated.push(finding);
1145            }
1146        }
1147        
1148        deduplicated
1149    }
1150    
1151    /// Generate a unique key for deduplication based on the type of finding
1152    fn generate_finding_key(&self, finding: &SecurityFinding) -> String {
1153        match finding.category {
1154            SecurityCategory::SecretsExposure => {
1155                // For secrets, deduplicate based on file path and the actual secret content
1156                if let Some(evidence) = &finding.evidence {
1157                    if let Some(file_path) = &finding.file_path {
1158                        // Extract the secret value from the evidence line
1159                        if let Some(secret_value) = self.extract_secret_value(evidence) {
1160                            return format!("secret:{}:{}", file_path.display(), secret_value);
1161                        }
1162                        // Fallback to file + line if we can't extract the value
1163                        if let Some(line_num) = finding.line_number {
1164                            return format!("secret:{}:{}", file_path.display(), line_num);
1165                        }
1166                    }
1167                }
1168                // Fallback for environment variables or other secrets without file paths
1169                format!("secret:{}", finding.title)
1170            }
1171            _ => {
1172                // For non-secret findings, use file path + line number + title
1173                if let Some(file_path) = &finding.file_path {
1174                    if let Some(line_num) = finding.line_number {
1175                        format!("other:{}:{}:{}", file_path.display(), line_num, finding.title)
1176                    } else {
1177                        format!("other:{}:{}", file_path.display(), finding.title)
1178                    }
1179                } else {
1180                    format!("other:{}", finding.title)
1181                }
1182            }
1183        }
1184    }
1185    
1186    /// Extract secret value from evidence line for deduplication
1187    fn extract_secret_value(&self, evidence: &str) -> Option<String> {
1188        // Look for patterns like "KEY=value" or "KEY: value"
1189        if let Some(pos) = evidence.find('=') {
1190            let value = evidence[pos + 1..].trim();
1191            // Remove quotes if present
1192            let value = value.trim_matches('"').trim_matches('\'');
1193            if value.len() > 10 { // Only consider substantial values
1194                return Some(value.to_string());
1195            }
1196        }
1197        
1198        // Look for patterns like "key: value" in YAML/JSON
1199        if let Some(pos) = evidence.find(':') {
1200            let value = evidence[pos + 1..].trim();
1201            let value = value.trim_matches('"').trim_matches('\'');
1202            if value.len() > 10 {
1203                return Some(value.to_string());
1204            }
1205        }
1206        
1207        None
1208    }
1209    
1210    /// Get pattern priority for deduplication (lower number = higher priority)
1211    fn get_pattern_priority(&self, title: &str) -> u8 {
1212        // Most specific patterns get highest priority (lowest number)
1213        if title.contains("AWS Access Key") { return 1; }
1214        if title.contains("AWS Secret Key") { return 1; }
1215        if title.contains("S3 Secret Key") { return 1; }
1216        if title.contains("GitHub Token") { return 1; }
1217        if title.contains("OpenAI API Key") { return 1; }
1218        if title.contains("Stripe") { return 1; }
1219        if title.contains("RSA Private Key") { return 1; }
1220        if title.contains("SSH Private Key") { return 1; }
1221        
1222        // JWT and specific API keys are more specific than generic
1223        if title.contains("JWT Secret") { return 2; }
1224        if title.contains("Database URL") { return 2; }
1225        
1226        // Generic API key patterns are less specific
1227        if title.contains("API Key") { return 3; }
1228        
1229        // Environment variable findings are less specific
1230        if title.contains("Environment Variable") { return 4; }
1231        
1232        // Generic patterns get lowest priority (highest number)
1233        if title.contains("Generic Secret") { return 5; }
1234        
1235        // Default priority for other patterns
1236        3
1237    }
1238    
1239    fn count_by_severity(&self, findings: &[SecurityFinding]) -> HashMap<SecuritySeverity, usize> {
1240        let mut counts = HashMap::new();
1241        for finding in findings {
1242            *counts.entry(finding.severity.clone()).or_insert(0) += 1;
1243        }
1244        counts
1245    }
1246    
1247    fn count_by_category(&self, findings: &[SecurityFinding]) -> HashMap<SecurityCategory, usize> {
1248        let mut counts = HashMap::new();
1249        for finding in findings {
1250            *counts.entry(finding.category.clone()).or_insert(0) += 1;
1251        }
1252        counts
1253    }
1254    
1255    fn calculate_security_score(&self, findings: &[SecurityFinding]) -> f32 {
1256        if findings.is_empty() {
1257            return 100.0;
1258        }
1259        
1260        let total_penalty = findings.iter().map(|f| match f.severity {
1261            SecuritySeverity::Critical => 25.0,
1262            SecuritySeverity::High => 15.0,
1263            SecuritySeverity::Medium => 8.0,
1264            SecuritySeverity::Low => 3.0,
1265            SecuritySeverity::Info => 1.0,
1266        }).sum::<f32>();
1267        
1268        (100.0 - total_penalty).max(0.0)
1269    }
1270    
1271    fn determine_risk_level(&self, findings: &[SecurityFinding]) -> SecuritySeverity {
1272        if findings.iter().any(|f| f.severity == SecuritySeverity::Critical) {
1273            SecuritySeverity::Critical
1274        } else if findings.iter().any(|f| f.severity == SecuritySeverity::High) {
1275            SecuritySeverity::High
1276        } else if findings.iter().any(|f| f.severity == SecuritySeverity::Medium) {
1277            SecuritySeverity::Medium
1278        } else if !findings.is_empty() {
1279            SecuritySeverity::Low
1280        } else {
1281            SecuritySeverity::Info
1282        }
1283    }
1284    
1285    fn assess_compliance(&self, _findings: &[SecurityFinding], _technologies: &[DetectedTechnology]) -> HashMap<String, ComplianceStatus> {
1286        // TODO: Implement compliance assessment
1287        HashMap::new()
1288    }
1289    
1290    fn generate_recommendations(&self, findings: &[SecurityFinding], _technologies: &[DetectedTechnology]) -> Vec<String> {
1291        let mut recommendations = Vec::new();
1292        
1293        if findings.iter().any(|f| f.category == SecurityCategory::SecretsExposure) {
1294            recommendations.push("Implement a secure secret management strategy".to_string());
1295        }
1296        
1297        if findings.iter().any(|f| f.severity == SecuritySeverity::Critical) {
1298            recommendations.push("Address critical security findings immediately".to_string());
1299        }
1300        
1301        // Add more generic recommendations...
1302        
1303        recommendations
1304    }
1305}
1306
1307impl Language {
1308    fn from_string(name: &str) -> Self {
1309        match name.to_lowercase().as_str() {
1310            "rust" => Language::Rust,
1311            "javascript" | "js" => Language::JavaScript,
1312            "typescript" | "ts" => Language::TypeScript,
1313            "python" | "py" => Language::Python,
1314            "go" | "golang" => Language::Go,
1315            "java" => Language::Java,
1316            "kotlin" => Language::Kotlin,
1317            _ => Language::Unknown,
1318        }
1319    }
1320}
1321
1322#[cfg(test)]
1323mod tests {
1324    use super::*;
1325    
1326    #[test]
1327    fn test_security_score_calculation() {
1328        let analyzer = SecurityAnalyzer::new().unwrap();
1329        
1330        let findings = vec![
1331            SecurityFinding {
1332                id: "test-1".to_string(),
1333                title: "Test Critical".to_string(),
1334                description: "Test".to_string(),
1335                severity: SecuritySeverity::Critical,
1336                category: SecurityCategory::SecretsExposure,
1337                file_path: None,
1338                line_number: None,
1339                evidence: None,
1340                remediation: vec![],
1341                references: vec![],
1342                cwe_id: None,
1343                compliance_frameworks: vec![],
1344            }
1345        ];
1346        
1347        let score = analyzer.calculate_security_score(&findings);
1348        assert_eq!(score, 75.0); // 100 - 25 (critical penalty)
1349    }
1350    
1351    #[test]
1352    fn test_secret_pattern_matching() {
1353        let analyzer = SecurityAnalyzer::new().unwrap();
1354        
1355        // Test if placeholder detection works
1356        assert!(analyzer.is_likely_placeholder("API_KEY=sk-xxxxxxxxxxxxxxxx"));
1357        assert!(!analyzer.is_likely_placeholder("API_KEY=sk-1234567890abcdef"));
1358    }
1359    
1360    #[test]
1361    fn test_sensitive_env_var_detection() {
1362        let analyzer = SecurityAnalyzer::new().unwrap();
1363        
1364        assert!(analyzer.is_sensitive_env_var("DATABASE_PASSWORD"));
1365        assert!(analyzer.is_sensitive_env_var("JWT_SECRET"));
1366        assert!(!analyzer.is_sensitive_env_var("PORT"));
1367        assert!(!analyzer.is_sensitive_env_var("NODE_ENV"));
1368    }
1369}