codeprism_analysis/
security.rs

1//! Security analysis module
2
3use anyhow::Result;
4use regex::Regex;
5use serde_json::Value;
6use std::collections::{HashMap, HashSet};
7
8/// Security vulnerability information
9#[derive(Debug, Clone)]
10pub struct SecurityVulnerability {
11    pub vulnerability_type: String,
12    pub severity: String,
13    pub description: String,
14    pub location: Option<String>,
15    pub recommendation: String,
16    pub cvss_score: Option<f32>,
17    pub owasp_category: Option<String>,
18    pub confidence: f32,
19    pub file_path: Option<String>,
20    pub line_number: Option<usize>,
21}
22
23/// CVSS Score components for vulnerability assessment
24#[derive(Debug, Clone)]
25pub struct CvssScore {
26    pub base_score: f32,
27    pub impact_score: f32,
28    pub exploitability_score: f32,
29    pub severity_level: String,
30}
31
32/// Security analyzer for code analysis
33pub struct SecurityAnalyzer {
34    patterns: HashMap<String, Vec<VulnerabilityPattern>>,
35}
36
37#[derive(Debug, Clone)]
38pub struct VulnerabilityPattern {
39    name: String,
40    pattern: Regex,
41    severity: String,
42    description: String,
43    recommendation: String,
44    cvss_base_score: f32,
45    owasp_category: Option<String>,
46    confidence: f32,
47}
48
49impl SecurityAnalyzer {
50    pub fn new() -> Self {
51        let mut analyzer = Self {
52            patterns: HashMap::new(),
53        };
54        analyzer.initialize_patterns();
55        analyzer
56    }
57
58    fn initialize_patterns(&mut self) {
59        // SQL Injection patterns
60        let sql_patterns = vec![
61            VulnerabilityPattern {
62                name: "SQL Injection".to_string(),
63                pattern: Regex::new(r#"(?i)(query|execute|exec)\s*\([^)]*\+[^)]*\)"#).unwrap(),
64                severity: "high".to_string(),
65                description: "Potential SQL injection vulnerability detected".to_string(),
66                recommendation: "Use parameterized queries or prepared statements".to_string(),
67                cvss_base_score: 8.1,
68                owasp_category: Some("A03:2021 – Injection".to_string()),
69                confidence: 0.8,
70            },
71            VulnerabilityPattern {
72                name: "SQL Injection Format".to_string(),
73                pattern: Regex::new(
74                    r#"(?i)(query|execute|exec)\s*\(\s*['""][^'"]*%[sd][^'"]*['""]"#,
75                )
76                .unwrap(),
77                severity: "high".to_string(),
78                description: "SQL query using string formatting detected".to_string(),
79                recommendation: "Use parameterized queries instead of string formatting"
80                    .to_string(),
81                cvss_base_score: 8.1,
82                owasp_category: Some("A03:2021 – Injection".to_string()),
83                confidence: 0.9,
84            },
85        ];
86        self.patterns.insert("injection".to_string(), sql_patterns);
87
88        // XSS vulnerability patterns
89        let xss_patterns = vec![
90            VulnerabilityPattern {
91                name: "XSS via innerHTML".to_string(),
92                pattern: Regex::new(r#"(?i)\.innerHTML\s*=\s*[^;]*\+[^;]*"#).unwrap(),
93                severity: "high".to_string(),
94                description: "Potential XSS vulnerability through innerHTML assignment".to_string(),
95                recommendation: "Use textContent or properly sanitize HTML content".to_string(),
96                cvss_base_score: 7.5,
97                owasp_category: Some("A07:2021 – Cross-Site Scripting (XSS)".to_string()),
98                confidence: 0.8,
99            },
100            VulnerabilityPattern {
101                name: "XSS via document.write".to_string(),
102                pattern: Regex::new(r#"(?i)document\.write\s*\([^)]*\+[^)]*\)"#).unwrap(),
103                severity: "high".to_string(),
104                description: "Potential XSS vulnerability through document.write".to_string(),
105                recommendation: "Avoid document.write, use DOM manipulation methods".to_string(),
106                cvss_base_score: 7.5,
107                owasp_category: Some("A07:2021 – Cross-Site Scripting (XSS)".to_string()),
108                confidence: 0.9,
109            },
110            VulnerabilityPattern {
111                name: "XSS via eval".to_string(),
112                pattern: Regex::new(r#"(?i)eval\s*\([^)]*\+[^)]*\)"#).unwrap(),
113                severity: "critical".to_string(),
114                description: "Critical XSS vulnerability through eval function".to_string(),
115                recommendation: "Never use eval with user input, use JSON.parse for data"
116                    .to_string(),
117                cvss_base_score: 9.3,
118                owasp_category: Some("A07:2021 – Cross-Site Scripting (XSS)".to_string()),
119                confidence: 0.95,
120            },
121        ];
122        self.patterns.insert("xss".to_string(), xss_patterns);
123
124        // CSRF vulnerability patterns
125        let csrf_patterns = vec![
126            VulnerabilityPattern {
127                name: "Missing CSRF Token".to_string(),
128                pattern: Regex::new(r#"(?i)<form[^>]*method\s*=\s*['""]post['""][^>]*>"#).unwrap(),
129                severity: "medium".to_string(),
130                description: "POST form without visible CSRF protection".to_string(),
131                recommendation: "Implement CSRF tokens for all state-changing operations"
132                    .to_string(),
133                cvss_base_score: 6.5,
134                owasp_category: Some("A01:2021 – Broken Access Control".to_string()),
135                confidence: 0.6,
136            },
137            VulnerabilityPattern {
138                name: "AJAX without CSRF".to_string(),
139                pattern: Regex::new(
140                    r#"(?i)\$\.post\s*\([^)]*\)|fetch\s*\([^)]*method:\s*['""]POST['""]"#,
141                )
142                .unwrap(),
143                severity: "medium".to_string(),
144                description: "AJAX POST request without visible CSRF protection".to_string(),
145                recommendation: "Include CSRF tokens in AJAX requests headers".to_string(),
146                cvss_base_score: 6.5,
147                owasp_category: Some("A01:2021 – Broken Access Control".to_string()),
148                confidence: 0.5,
149            },
150        ];
151        self.patterns.insert("csrf".to_string(), csrf_patterns);
152
153        // Authentication patterns (enhanced)
154        let auth_patterns = vec![
155            VulnerabilityPattern {
156                name: "Hardcoded Password".to_string(),
157                pattern: Regex::new(r#"(?i)(password|pwd|passwd)\s*=\s*['""][^'""]{3,}['""]"#)
158                    .unwrap(),
159                severity: "critical".to_string(),
160                description: "Hardcoded password detected".to_string(),
161                recommendation:
162                    "Store passwords securely using environment variables or secure vaults"
163                        .to_string(),
164                cvss_base_score: 9.1,
165                owasp_category: Some(
166                    "A07:2021 – Identification and Authentication Failures".to_string(),
167                ),
168                confidence: 0.9,
169            },
170            VulnerabilityPattern {
171                name: "Weak Password Check".to_string(),
172                pattern: Regex::new(r#"(?i)len\s*\(\s*password\s*\)\s*[<>=]\s*[1-5]"#).unwrap(),
173                severity: "medium".to_string(),
174                description: "Weak password length requirement detected".to_string(),
175                recommendation: "Enforce stronger password requirements (minimum 8 characters)"
176                    .to_string(),
177                cvss_base_score: 5.3,
178                owasp_category: Some(
179                    "A07:2021 – Identification and Authentication Failures".to_string(),
180                ),
181                confidence: 0.8,
182            },
183            VulnerabilityPattern {
184                name: "Hardcoded API Key".to_string(),
185                pattern: Regex::new(
186                    r#"(?i)(api_key|apikey|access_key)\s*=\s*['""][a-zA-Z0-9]{16,}['""]"#,
187                )
188                .unwrap(),
189                severity: "critical".to_string(),
190                description: "Hardcoded API key detected".to_string(),
191                recommendation: "Store API keys in environment variables or secure configuration"
192                    .to_string(),
193                cvss_base_score: 9.1,
194                owasp_category: Some("A02:2021 – Cryptographic Failures".to_string()),
195                confidence: 0.95,
196            },
197        ];
198        self.patterns
199            .insert("authentication".to_string(), auth_patterns);
200
201        // Crypto patterns (enhanced)
202        let crypto_patterns = vec![
203            VulnerabilityPattern {
204                name: "Weak Crypto Algorithm".to_string(),
205                pattern: Regex::new(r#"(?i)(md5|sha1|des|rc4)\s*\("#).unwrap(),
206                severity: "high".to_string(),
207                description: "Weak cryptographic algorithm detected".to_string(),
208                recommendation: "Use stronger algorithms like SHA-256, AES, or bcrypt".to_string(),
209                cvss_base_score: 7.4,
210                owasp_category: Some("A02:2021 – Cryptographic Failures".to_string()),
211                confidence: 0.9,
212            },
213            VulnerabilityPattern {
214                name: "Hardcoded Crypto Key".to_string(),
215                pattern: Regex::new(r#"(?i)(key|secret|token)\s*=\s*['""][a-fA-F0-9]{16,}['""]"#)
216                    .unwrap(),
217                severity: "critical".to_string(),
218                description: "Hardcoded cryptographic key detected".to_string(),
219                recommendation: "Store keys securely using key management systems".to_string(),
220                cvss_base_score: 9.8,
221                owasp_category: Some("A02:2021 – Cryptographic Failures".to_string()),
222                confidence: 0.9,
223            },
224            VulnerabilityPattern {
225                name: "Weak Random Number Generation".to_string(),
226                pattern: Regex::new(r#"(?i)(Math\.random|random\.randint)\s*\("#).unwrap(),
227                severity: "medium".to_string(),
228                description: "Weak random number generation for security purposes".to_string(),
229                recommendation: "Use cryptographically secure random number generators".to_string(),
230                cvss_base_score: 5.9,
231                owasp_category: Some("A02:2021 – Cryptographic Failures".to_string()),
232                confidence: 0.7,
233            },
234        ];
235        self.patterns.insert("crypto".to_string(), crypto_patterns);
236
237        // Data exposure patterns (enhanced)
238        let exposure_patterns = vec![
239            VulnerabilityPattern {
240                name: "Debug Information Exposure".to_string(),
241                pattern: Regex::new(r#"(?i)(print|console\.log|debug|trace)\s*\([^)]*(?:password|token|key|secret)"#).unwrap(),
242                severity: "medium".to_string(),
243                description: "Sensitive information in debug output detected".to_string(),
244                recommendation: "Remove debug statements containing sensitive data".to_string(),
245                cvss_base_score: 5.3,
246                owasp_category: Some("A09:2021 – Security Logging and Monitoring Failures".to_string()),
247                confidence: 0.8,
248            },
249            VulnerabilityPattern {
250                name: "Error Information Disclosure".to_string(),
251                pattern: Regex::new(r#"(?i)except\s+\w+\s+as\s+\w+:\s*print\s*\(\s*\w+"#).unwrap(),
252                severity: "low".to_string(),
253                description: "Exception details exposed to user".to_string(),
254                recommendation: "Log errors securely without exposing internal details".to_string(),
255                cvss_base_score: 3.7,
256                owasp_category: Some("A09:2021 – Security Logging and Monitoring Failures".to_string()),
257                confidence: 0.6,
258            },
259            VulnerabilityPattern {
260                name: "Sensitive Data in URL".to_string(),
261                pattern: Regex::new(r#"(?i)(password|token|key|secret)=[^&\s]+"#).unwrap(),
262                severity: "high".to_string(),
263                description: "Sensitive information exposed in URL parameters".to_string(),
264                recommendation: "Use POST requests or secure headers for sensitive data".to_string(),
265                cvss_base_score: 7.5,
266                owasp_category: Some("A02:2021 – Cryptographic Failures".to_string()),
267                confidence: 0.9,
268            },
269        ];
270        self.patterns
271            .insert("data_exposure".to_string(), exposure_patterns);
272
273        // Unsafe patterns (enhanced)
274        let unsafe_patterns = vec![
275            VulnerabilityPattern {
276                name: "Command Injection".to_string(),
277                pattern: Regex::new(r#"(?i)(system|exec|popen|subprocess)\s*\([^)]*\+[^)]*\)"#)
278                    .unwrap(),
279                severity: "critical".to_string(),
280                description: "Potential command injection vulnerability detected".to_string(),
281                recommendation: "Validate and sanitize input, use safe alternatives".to_string(),
282                cvss_base_score: 9.8,
283                owasp_category: Some("A03:2021 – Injection".to_string()),
284                confidence: 0.9,
285            },
286            VulnerabilityPattern {
287                name: "Path Traversal".to_string(),
288                pattern: Regex::new(r#"(?i)(open|file|read)\s*\([^)]*\.\./[^)]*\)"#).unwrap(),
289                severity: "high".to_string(),
290                description: "Potential path traversal vulnerability detected".to_string(),
291                recommendation: "Validate file paths and use safe path operations".to_string(),
292                cvss_base_score: 7.5,
293                owasp_category: Some("A01:2021 – Broken Access Control".to_string()),
294                confidence: 0.8,
295            },
296            VulnerabilityPattern {
297                name: "Deserialization of Untrusted Data".to_string(),
298                pattern: Regex::new(
299                    r#"(?i)(pickle\.loads|yaml\.load|json\.loads)\s*\([^)]*input[^)]*\)"#,
300                )
301                .unwrap(),
302                severity: "critical".to_string(),
303                description: "Unsafe deserialization of user input".to_string(),
304                recommendation: "Validate and sanitize data before deserialization".to_string(),
305                cvss_base_score: 9.8,
306                owasp_category: Some("A08:2021 – Software and Data Integrity Failures".to_string()),
307                confidence: 0.85,
308            },
309        ];
310        self.patterns
311            .insert("unsafe_patterns".to_string(), unsafe_patterns);
312    }
313
314    /// Calculate CVSS score for a vulnerability
315    pub fn calculate_cvss_score(
316        &self,
317        pattern: &VulnerabilityPattern,
318        context: Option<&str>,
319    ) -> CvssScore {
320        let mut base_score = pattern.cvss_base_score;
321
322        // Adjust score based on context
323        if let Some(ctx) = context {
324            // Lower score for test files
325            if ctx.contains("test") || ctx.contains("spec") {
326                base_score *= 0.7;
327            }
328            // Higher score for production/main code
329            if ctx.contains("main") || ctx.contains("prod") {
330                base_score *= 1.1;
331            }
332        }
333
334        // Ensure score stays within CVSS range
335        base_score = base_score.clamp(0.0, 10.0);
336
337        let severity_level = match base_score {
338            0.0..=3.9 => "Low",
339            4.0..=6.9 => "Medium",
340            7.0..=8.9 => "High",
341            9.0..=10.0 => "Critical",
342            _ => "Unknown",
343        }
344        .to_string();
345
346        CvssScore {
347            base_score,
348            impact_score: base_score * 0.6, // Simplified calculation
349            exploitability_score: base_score * 0.4, // Simplified calculation
350            severity_level,
351        }
352    }
353
354    /// Analyze content for security vulnerabilities with enhanced reporting
355    pub fn analyze_content_with_location(
356        &self,
357        content: &str,
358        file_path: Option<&str>,
359        vulnerability_types: &[String],
360        severity_threshold: &str,
361    ) -> Result<Vec<SecurityVulnerability>> {
362        let mut vulnerabilities = Vec::new();
363
364        let target_types = if vulnerability_types.contains(&"all".to_string()) {
365            self.patterns.keys().cloned().collect::<Vec<_>>()
366        } else {
367            vulnerability_types.to_vec()
368        };
369
370        let lines: Vec<&str> = content.lines().collect();
371
372        for vuln_type in target_types {
373            if let Some(patterns) = self.patterns.get(&vuln_type) {
374                for pattern in patterns {
375                    if self.meets_severity_threshold(&pattern.severity, severity_threshold) {
376                        for (line_idx, line) in lines.iter().enumerate() {
377                            if let Some(capture) = pattern.pattern.find(line) {
378                                let cvss_score = self.calculate_cvss_score(pattern, file_path);
379
380                                vulnerabilities.push(SecurityVulnerability {
381                                    vulnerability_type: pattern.name.clone(),
382                                    severity: pattern.severity.clone(),
383                                    description: pattern.description.clone(),
384                                    location: Some(format!(
385                                        "Line {}: Position {}",
386                                        line_idx + 1,
387                                        capture.start()
388                                    )),
389                                    recommendation: pattern.recommendation.clone(),
390                                    cvss_score: Some(cvss_score.base_score),
391                                    owasp_category: pattern.owasp_category.clone(),
392                                    confidence: pattern.confidence,
393                                    file_path: file_path.map(|s| s.to_string()),
394                                    line_number: Some(line_idx + 1),
395                                });
396                            }
397                        }
398                    }
399                }
400            }
401        }
402
403        Ok(vulnerabilities)
404    }
405
406    /// Analyze content for security vulnerabilities (legacy method for compatibility)
407    pub fn analyze_content(
408        &self,
409        content: &str,
410        vulnerability_types: &[String],
411        severity_threshold: &str,
412    ) -> Result<Vec<SecurityVulnerability>> {
413        self.analyze_content_with_location(content, None, vulnerability_types, severity_threshold)
414    }
415
416    /// Check if severity meets threshold
417    fn meets_severity_threshold(&self, severity: &str, threshold: &str) -> bool {
418        let severity_levels = ["low", "medium", "high", "critical"];
419        let severity_idx = severity_levels
420            .iter()
421            .position(|&s| s == severity)
422            .unwrap_or(0);
423        let threshold_idx = severity_levels
424            .iter()
425            .position(|&s| s == threshold)
426            .unwrap_or(0);
427
428        severity_idx >= threshold_idx
429    }
430
431    /// Get security recommendations based on vulnerabilities with OWASP mapping
432    pub fn get_security_recommendations(
433        &self,
434        vulnerabilities: &[SecurityVulnerability],
435    ) -> Vec<String> {
436        let mut recommendations = Vec::new();
437
438        if vulnerabilities.is_empty() {
439            recommendations.push(
440                "No security vulnerabilities detected. Continue following security best practices."
441                    .to_string(),
442            );
443            return recommendations;
444        }
445
446        // Group by vulnerability type and severity
447        let mut vuln_counts = HashMap::new();
448        let mut severity_counts = HashMap::new();
449        let mut owasp_categories = HashSet::new();
450
451        for vuln in vulnerabilities {
452            *vuln_counts
453                .entry(vuln.vulnerability_type.clone())
454                .or_insert(0) += 1;
455            *severity_counts.entry(vuln.severity.clone()).or_insert(0) += 1;
456            if let Some(ref category) = vuln.owasp_category {
457                owasp_categories.insert(category.clone());
458            }
459        }
460
461        // Priority recommendations based on severity
462        if severity_counts.get("critical").unwrap_or(&0) > &0 {
463            recommendations.push("🚨 CRITICAL: Address critical vulnerabilities immediately - these pose severe security risks.".to_string());
464        }
465        if severity_counts.get("high").unwrap_or(&0) > &0 {
466            recommendations.push(
467                "⚠️  HIGH PRIORITY: High-severity vulnerabilities require urgent attention."
468                    .to_string(),
469            );
470        }
471
472        // Specific recommendations based on vulnerability types
473        if vuln_counts.contains_key("SQL Injection")
474            || vuln_counts.contains_key("SQL Injection Format")
475        {
476            recommendations.push("🛡️  Implement input validation and use parameterized queries for all database operations.".to_string());
477        }
478
479        if vuln_counts.contains_key("XSS via innerHTML")
480            || vuln_counts.contains_key("XSS via document.write")
481            || vuln_counts.contains_key("XSS via eval")
482        {
483            recommendations.push("🔒 Sanitize all user input and use safe DOM manipulation methods. Implement Content Security Policy (CSP).".to_string());
484        }
485
486        if vuln_counts.contains_key("Missing CSRF Token")
487            || vuln_counts.contains_key("AJAX without CSRF")
488        {
489            recommendations.push("🔐 Implement anti-CSRF tokens for all state-changing operations and AJAX requests.".to_string());
490        }
491
492        if vuln_counts.contains_key("Hardcoded Password")
493            || vuln_counts.contains_key("Hardcoded API Key")
494            || vuln_counts.contains_key("Hardcoded Crypto Key")
495        {
496            recommendations.push("🗝️  Use environment variables or secure key management systems for sensitive data.".to_string());
497        }
498
499        if vuln_counts.contains_key("Command Injection") {
500            recommendations.push(
501                "⚡ Validate all user input and use safe alternatives to system commands."
502                    .to_string(),
503            );
504        }
505
506        if vuln_counts.contains_key("Weak Crypto Algorithm")
507            || vuln_counts.contains_key("Weak Random Number Generation")
508        {
509            recommendations.push(
510                "🔐 Upgrade to modern, secure cryptographic algorithms (SHA-256, AES-256, etc.)."
511                    .to_string(),
512            );
513        }
514
515        if vuln_counts.contains_key("Path Traversal") {
516            recommendations.push(
517                "📁 Implement proper path validation and use safe file operations.".to_string(),
518            );
519        }
520
521        if vuln_counts.contains_key("Deserialization of Untrusted Data") {
522            recommendations.push(
523                "⚠️  Never deserialize untrusted data. Implement strict input validation."
524                    .to_string(),
525            );
526        }
527
528        // OWASP-based recommendations
529        if !owasp_categories.is_empty() {
530            recommendations.push(format!(
531                "📋 OWASP Top 10 categories affected: {}",
532                owasp_categories
533                    .iter()
534                    .cloned()
535                    .collect::<Vec<_>>()
536                    .join(", ")
537            ));
538        }
539
540        // General security recommendations
541        recommendations
542            .push("🔍 Conduct regular security audits and penetration testing.".to_string());
543        recommendations.push(
544            "📚 Follow OWASP security guidelines and implement security training for developers."
545                .to_string(),
546        );
547        recommendations.push(
548            "🛡️  Implement proper error handling that doesn't expose sensitive information."
549                .to_string(),
550        );
551        recommendations
552            .push("📊 Set up security monitoring and logging for threat detection.".to_string());
553
554        recommendations
555    }
556
557    /// Generate comprehensive security report
558    pub fn generate_security_report(
559        &self,
560        vulnerabilities: &[SecurityVulnerability],
561    ) -> serde_json::Value {
562        let mut severity_counts = HashMap::new();
563        let mut owasp_counts = HashMap::new();
564        let mut total_cvss_score = 0.0;
565        let mut high_confidence_vulns = 0;
566
567        for vuln in vulnerabilities {
568            *severity_counts.entry(vuln.severity.clone()).or_insert(0) += 1;
569            if let Some(ref owasp) = vuln.owasp_category {
570                *owasp_counts.entry(owasp.clone()).or_insert(0) += 1;
571            }
572            if let Some(score) = vuln.cvss_score {
573                total_cvss_score += score;
574            }
575            if vuln.confidence >= 0.8 {
576                high_confidence_vulns += 1;
577            }
578        }
579
580        let avg_cvss_score = if !vulnerabilities.is_empty() {
581            total_cvss_score / vulnerabilities.len() as f32
582        } else {
583            0.0
584        };
585
586        let security_score = match avg_cvss_score {
587            0.0..=3.9 => 85 + (15.0 * (4.0 - avg_cvss_score) / 4.0) as i32,
588            4.0..=6.9 => 60 + (25.0 * (7.0 - avg_cvss_score) / 3.0) as i32,
589            7.0..=8.9 => 30 + (30.0 * (9.0 - avg_cvss_score) / 2.0) as i32,
590            9.0..=10.0 => (30.0 * (10.0 - avg_cvss_score)) as i32,
591            _ => 0,
592        };
593
594        serde_json::json!({
595            "summary": {
596                "total_vulnerabilities": vulnerabilities.len(),
597                "high_confidence_findings": high_confidence_vulns,
598                "average_cvss_score": avg_cvss_score,
599                "security_score": security_score.max(0)
600            },
601            "severity_breakdown": severity_counts,
602            "owasp_categories": owasp_counts,
603            "recommendations": self.get_security_recommendations(vulnerabilities)
604        })
605    }
606
607    /// Analyze for specific vulnerability patterns
608    pub fn detect_injection_vulnerabilities(&self, content: &str) -> Result<Vec<Value>> {
609        let vulnerabilities = self.analyze_content(content, &["injection".to_string()], "low")?;
610
611        Ok(vulnerabilities
612            .into_iter()
613            .map(|v| {
614                serde_json::json!({
615                    "type": v.vulnerability_type,
616                    "severity": v.severity,
617                    "description": v.description,
618                    "location": v.location,
619                    "recommendation": v.recommendation
620                })
621            })
622            .collect())
623    }
624
625    /// Analyze for authentication issues
626    pub fn detect_authentication_issues(&self, content: &str) -> Result<Vec<Value>> {
627        let vulnerabilities =
628            self.analyze_content(content, &["authentication".to_string()], "low")?;
629
630        Ok(vulnerabilities
631            .into_iter()
632            .map(|v| {
633                serde_json::json!({
634                    "type": v.vulnerability_type,
635                    "severity": v.severity,
636                    "description": v.description,
637                    "location": v.location,
638                    "recommendation": v.recommendation
639                })
640            })
641            .collect())
642    }
643
644    /// Analyze for data exposure issues
645    pub fn detect_data_exposure_issues(&self, content: &str) -> Result<Vec<Value>> {
646        let vulnerabilities =
647            self.analyze_content(content, &["data_exposure".to_string()], "low")?;
648
649        Ok(vulnerabilities
650            .into_iter()
651            .map(|v| {
652                serde_json::json!({
653                    "type": v.vulnerability_type,
654                    "severity": v.severity,
655                    "description": v.description,
656                    "location": v.location,
657                    "recommendation": v.recommendation
658                })
659            })
660            .collect())
661    }
662}
663
664impl Default for SecurityAnalyzer {
665    fn default() -> Self {
666        Self::new()
667    }
668}
669
670#[cfg(test)]
671mod tests {
672    use super::*;
673
674    #[test]
675    fn test_sql_injection_detection() {
676        let analyzer = SecurityAnalyzer::new();
677
678        let vulnerable_code = r#"query("SELECT * FROM users WHERE id = " + user_id)"#;
679        let vulnerabilities = analyzer
680            .analyze_content(vulnerable_code, &["injection".to_string()], "low")
681            .unwrap();
682
683        assert!(!vulnerabilities.is_empty());
684        assert_eq!(vulnerabilities[0].vulnerability_type, "SQL Injection");
685    }
686
687    #[test]
688    fn test_hardcoded_password_detection() {
689        let analyzer = SecurityAnalyzer::new();
690
691        let vulnerable_code = r#"password = "admin123""#;
692        let vulnerabilities = analyzer
693            .analyze_content(vulnerable_code, &["authentication".to_string()], "low")
694            .unwrap();
695
696        assert!(!vulnerabilities.is_empty());
697        assert_eq!(vulnerabilities[0].vulnerability_type, "Hardcoded Password");
698    }
699
700    #[test]
701    fn test_weak_crypto_detection() {
702        let analyzer = SecurityAnalyzer::new();
703
704        let vulnerable_code = r#"hash = md5(password)"#;
705        let vulnerabilities = analyzer
706            .analyze_content(vulnerable_code, &["crypto".to_string()], "low")
707            .unwrap();
708
709        assert!(!vulnerabilities.is_empty());
710        assert_eq!(
711            vulnerabilities[0].vulnerability_type,
712            "Weak Crypto Algorithm"
713        );
714    }
715
716    #[test]
717    fn test_severity_threshold() {
718        let analyzer = SecurityAnalyzer::new();
719
720        assert!(analyzer.meets_severity_threshold("high", "medium"));
721        assert!(!analyzer.meets_severity_threshold("low", "high"));
722        assert!(analyzer.meets_severity_threshold("critical", "high"));
723    }
724
725    #[test]
726    fn test_security_recommendations() {
727        let analyzer = SecurityAnalyzer::new();
728
729        let vulnerabilities = vec![SecurityVulnerability {
730            vulnerability_type: "SQL Injection".to_string(),
731            severity: "high".to_string(),
732            description: "Test".to_string(),
733            location: None,
734            recommendation: "Test".to_string(),
735            cvss_score: None,
736            owasp_category: None,
737            confidence: 0.0,
738            file_path: None,
739            line_number: None,
740        }];
741
742        let recommendations = analyzer.get_security_recommendations(&vulnerabilities);
743        assert!(!recommendations.is_empty());
744    }
745}