nabla_cli/binary/
scanner.rs

1use anyhow::Result;
2use chrono::Utc;
3use flate2::read::GzDecoder;
4use home::home_dir;
5use once_cell::sync::Lazy;
6use regex::Regex;
7use serde::Serialize;
8use serde_json::Value;
9use std::{fs::File, io::BufReader, path::PathBuf};
10use uuid::Uuid;
11
12use super::BinaryAnalysis;
13use crate::enterprise::secure::behavioral_analysis::BehavioralAnalysisResult;
14use crate::enterprise::secure::control_flow::{ControlFlowGraph, ExploitabilityAnalysis};
15use crate::enterprise::secure::crypto_analysis::CryptoAnalysisResult;
16use crate::enterprise::secure::static_analysis::StaticAnalysisResult;
17use crate::enterprise::secure::supply_chain::SupplyChainAnalysisResult;
18use crate::enterprise::{
19    analyze_behavioral_security, analyze_crypto_security, analyze_static_security,
20    analyze_supply_chain_security,
21};
22
23// ==== CORE SCAN RESULT STRUCTURES ====
24
25#[derive(Debug, Clone, Serialize)]
26pub struct ScanResult {
27    pub scan_id: Uuid,
28    pub target_file: String,
29    pub scan_timestamp: chrono::DateTime<chrono::Utc>,
30    pub vulnerability_findings: Vec<VulnerabilityFinding>,
31    pub security_findings: Vec<SecurityFinding>,
32    pub risk_assessment: RiskAssessment,
33    pub recommendations: Vec<String>,
34}
35
36#[derive(Debug, Clone, Serialize)]
37pub struct EnterpriseScanResult {
38    pub scan_id: Uuid,
39    pub target_file: String,
40    pub scan_timestamp: chrono::DateTime<chrono::Utc>,
41    pub vulnerability_findings: Vec<VulnerabilityFinding>,
42    pub security_findings: Vec<SecurityFinding>,
43    pub risk_assessment: RiskAssessment,
44    pub recommendations: Vec<String>,
45    // Enterprise-specific advanced analysis
46    pub static_analysis: StaticAnalysisResult,
47    pub behavioral_analysis: BehavioralAnalysisResult,
48    pub crypto_analysis: CryptoAnalysisResult,
49    pub supply_chain_analysis: SupplyChainAnalysisResult,
50    pub exploitability_assessments: Vec<ExploitabilityAssessment>,
51}
52
53#[derive(Debug, Clone, Serialize)]
54pub struct VulnerabilityFinding {
55    pub cve_id: Option<String>,
56    pub title: String,
57    pub description: String,
58    pub severity: SeverityLevel,
59    pub matched_components: Vec<String>,
60    pub confidence: ConfidenceLevel,
61    pub references: Vec<String>,
62}
63
64#[derive(Debug, Clone, Serialize)]
65pub struct SecurityFinding {
66    pub finding_id: Uuid,
67    pub category: SecurityCategory,
68    pub title: String,
69    pub description: String,
70    pub severity: SeverityLevel,
71    pub confidence: ConfidenceLevel,
72    pub affected_components: Vec<String>,
73    pub remediation: Option<String>,
74}
75
76#[derive(Debug, Clone, Serialize)]
77pub struct ExploitabilityAssessment {
78    pub vulnerability_id: String,
79    pub exploitability: ExploitabilityAnalysis,
80    pub attack_surface_analysis: AttackSurfaceAnalysis,
81}
82
83#[derive(Debug, Clone, Serialize)]
84pub struct AttackSurfaceAnalysis {
85    pub exposed_functions: Vec<String>,
86    pub network_interfaces: Vec<String>,
87    pub privilege_requirements: PrivilegeLevel,
88    pub user_interaction_required: bool,
89}
90
91#[derive(Debug, Clone, Serialize)]
92pub struct RiskAssessment {
93    pub overall_risk: RiskLevel,
94    pub critical_findings: u32,
95    pub high_findings: u32,
96    pub medium_findings: u32,
97    pub low_findings: u32,
98    pub info_findings: u32,
99    pub exploitable_vulnerabilities: u32,
100    pub security_score: f32, // 0.0 to 100.0
101}
102
103// ==== ENUMS ====
104
105#[derive(Debug, Clone, Serialize, PartialEq)]
106pub enum SeverityLevel {
107    Critical,
108    High,
109    Medium,
110    Low,
111    Info,
112}
113
114#[derive(Debug, Clone, Serialize)]
115pub enum ConfidenceLevel {
116    High,
117    Medium,
118    Low,
119}
120
121#[derive(Debug, Clone, Serialize)]
122#[allow(dead_code)]
123pub enum SecurityCategory {
124    VulnerableComponent,
125    InsecureConfiguration,
126    WeakCryptography,
127    MemorySafety,
128    NetworkSecurity,
129    DataProtection,
130    AccessControl,
131    CodeQuality,
132    SupplyChain,
133    ComplianceViolation,
134}
135
136#[derive(Debug, Clone, Serialize)]
137pub enum RiskLevel {
138    Critical,
139    High,
140    Medium,
141    Low,
142    Minimal,
143}
144
145#[derive(Debug, Clone, Serialize)]
146#[allow(dead_code)]
147pub enum PrivilegeLevel {
148    System,
149    Administrator,
150    User,
151    Guest,
152    None,
153}
154
155// ==== CVE DATABASE (kept for vulnerability detection) ====
156
157const CVE_BULK_DATA_URL: &str =
158    "https://nvd.nist.gov/feeds/json/cve/2.0/nvdcve-2.0-modified.json.gz";
159
160pub struct CveEntry {
161    id: String,
162    description: String,
163    cpes: Vec<String>,
164    severity: Option<String>,
165}
166
167static CVE_DB: Lazy<Vec<CveEntry>> = Lazy::new(|| match load_cve_db() {
168    Ok(db) => {
169        tracing::info!("Loaded {} CVE records", db.len());
170        db
171    }
172    Err(e) => {
173        tracing::error!("Failed to load CVE DB: {}", e);
174        Vec::new()
175    }
176});
177
178// ==== MAIN SCANNING FUNCTIONS ====
179
180/// Comprehensive security scan for open-source binaries
181/// Includes vulnerability detection, basic security analysis, and risk assessment
182pub fn scan_binary(analysis: &BinaryAnalysis) -> ScanResult {
183    let scan_id = Uuid::new_v4();
184    let scan_timestamp = Utc::now();
185
186    // Detect vulnerabilities from CVE database
187    let vulnerability_findings = detect_vulnerabilities(analysis);
188
189    // Perform basic security analysis
190    let security_findings = perform_basic_security_analysis(analysis);
191
192    // Calculate risk assessment
193    let risk_assessment = calculate_risk_assessment(&vulnerability_findings, &security_findings);
194
195    // Generate recommendations
196    let recommendations = generate_recommendations(
197        &vulnerability_findings,
198        &security_findings,
199        &risk_assessment,
200    );
201
202    ScanResult {
203        scan_id,
204        target_file: analysis.file_name.clone(),
205        scan_timestamp,
206        vulnerability_findings,
207        security_findings,
208        risk_assessment,
209        recommendations,
210    }
211}
212
213/// Enterprise-level comprehensive security scan
214/// Includes all OSS features plus advanced static analysis, behavioral analysis,
215/// cryptographic analysis, supply chain analysis, and exploitability assessment
216pub fn enterprise_scan_binary(analysis: &BinaryAnalysis) -> EnterpriseScanResult {
217    let scan_id = Uuid::new_v4();
218    let scan_timestamp = Utc::now();
219
220    // Run all OSS analysis first
221    let oss_result = scan_binary(analysis);
222
223    // Run comprehensive enterprise security analysis
224    let static_analysis = analyze_static_security(analysis);
225    let behavioral_analysis = analyze_behavioral_security(analysis);
226    let crypto_analysis = analyze_crypto_security(analysis);
227    let supply_chain_analysis = analyze_supply_chain_security(analysis);
228
229    // Enhanced vulnerability findings with enterprise insights
230    let enhanced_vulnerability_findings = enhance_vulnerabilities_with_enterprise_analysis(
231        &oss_result.vulnerability_findings,
232        &static_analysis,
233        &behavioral_analysis,
234        &supply_chain_analysis,
235    );
236
237    // Enhanced security findings with enterprise analysis
238    let enhanced_security_findings = enhance_security_findings_with_enterprise_analysis(
239        &oss_result.security_findings,
240        &static_analysis,
241        &behavioral_analysis,
242        &crypto_analysis,
243        &supply_chain_analysis,
244    );
245
246    // Perform exploitability assessments
247    let exploitability_assessments =
248        perform_exploitability_assessments(analysis, &enhanced_vulnerability_findings);
249
250    // Recalculate risk assessment with enterprise data
251    let enterprise_risk_assessment = calculate_enterprise_risk_assessment(
252        &enhanced_vulnerability_findings,
253        &enhanced_security_findings,
254        &exploitability_assessments,
255    );
256
257    // Generate enhanced recommendations
258    let enhanced_recommendations = generate_enterprise_recommendations(
259        &enhanced_vulnerability_findings,
260        &enhanced_security_findings,
261        &enterprise_risk_assessment,
262        &static_analysis,
263        &behavioral_analysis,
264        &crypto_analysis,
265        &supply_chain_analysis,
266    );
267
268    EnterpriseScanResult {
269        scan_id,
270        target_file: analysis.file_name.clone(),
271        scan_timestamp,
272        vulnerability_findings: enhanced_vulnerability_findings,
273        security_findings: enhanced_security_findings,
274        risk_assessment: enterprise_risk_assessment,
275        recommendations: enhanced_recommendations,
276        static_analysis,
277        behavioral_analysis,
278        crypto_analysis,
279        supply_chain_analysis,
280        exploitability_assessments,
281    }
282}
283
284// ==== VULNERABILITY DETECTION ====
285
286fn detect_vulnerabilities(analysis: &BinaryAnalysis) -> Vec<VulnerabilityFinding> {
287    let mut findings = Vec::new();
288
289    // Extract keywords for matching
290    let keywords = extract_component_keywords(analysis);
291
292    // Match against CVE database
293    for entry in CVE_DB.iter() {
294        for keyword in &keywords {
295            if keyword.is_empty() {
296                continue;
297            }
298
299            if entry.description.to_lowercase().contains(keyword)
300                || entry.cpes.iter().any(|c| c.contains(keyword))
301            {
302                let severity = map_cve_severity(&entry.severity);
303                let confidence =
304                    calculate_match_confidence(keyword, &entry.description, &entry.cpes);
305
306                findings.push(VulnerabilityFinding {
307                    cve_id: Some(entry.id.clone()),
308                    title: format!("Potential vulnerability in {}", keyword),
309                    description: entry.description.clone(),
310                    severity,
311                    matched_components: vec![keyword.clone()],
312                    confidence,
313                    references: vec![format!("https://nvd.nist.gov/vuln/detail/{}", entry.id)],
314                });
315                break;
316            }
317        }
318    }
319
320    findings
321}
322
323fn extract_component_keywords(analysis: &BinaryAnalysis) -> Vec<String> {
324    let mut keywords: Vec<String> = analysis
325        .linked_libraries
326        .iter()
327        .chain(analysis.imports.iter())
328        .map(|s| s.to_lowercase())
329        .collect();
330
331    // Add CPE candidates from metadata
332    if let Some(cpe_candidates) = analysis
333        .metadata
334        .get("cpe_candidates")
335        .and_then(|c| c.as_array())
336    {
337        keywords.extend(
338            cpe_candidates
339                .iter()
340                .filter_map(|c| c.as_str().map(|s| s.to_string())),
341        );
342    }
343
344    // Extract library names and versions from embedded strings
345    keywords.extend(extract_library_keywords_from_strings(
346        &analysis.embedded_strings,
347    ));
348
349    keywords.sort();
350    keywords.dedup();
351    keywords
352}
353
354// ==== BASIC SECURITY ANALYSIS ====
355
356fn perform_basic_security_analysis(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
357    let mut findings = Vec::new();
358
359    // Check for insecure functions
360    findings.extend(check_insecure_functions(analysis));
361
362    // Check for hardcoded secrets
363    findings.extend(check_hardcoded_secrets(analysis));
364
365    // Check for weak cryptographic indicators
366    findings.extend(check_weak_crypto_indicators(analysis));
367
368    // Check for suspicious network behavior
369    findings.extend(check_suspicious_network_behavior(analysis));
370
371    // Check for missing security features
372    findings.extend(check_missing_security_features(analysis));
373
374    findings
375}
376
377fn check_insecure_functions(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
378    let mut findings = Vec::new();
379    let dangerous_functions = [
380        (
381            "strcpy",
382            "Buffer overflow vulnerability - use strcpy_s or strncpy",
383        ),
384        (
385            "strcat",
386            "Buffer overflow vulnerability - use strcat_s or strncat",
387        ),
388        ("sprintf", "Buffer overflow vulnerability - use snprintf"),
389        ("gets", "Buffer overflow vulnerability - use fgets"),
390        (
391            "scanf",
392            "Input validation vulnerability - use specific format specifiers",
393        ),
394    ];
395
396    for (func, desc) in &dangerous_functions {
397        if analysis.imports.iter().any(|imp| imp.contains(func)) {
398            findings.push(SecurityFinding {
399                finding_id: Uuid::new_v4(),
400                category: SecurityCategory::MemorySafety,
401                title: format!("Dangerous function detected: {}", func),
402                description: desc.to_string(),
403                severity: SeverityLevel::High,
404                confidence: ConfidenceLevel::High,
405                affected_components: vec![func.to_string()],
406                remediation: Some(format!("Replace {} with safer alternatives", func)),
407            });
408        }
409    }
410
411    findings
412}
413
414fn check_hardcoded_secrets(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
415    let mut findings = Vec::new();
416
417    for secret in &analysis.suspected_secrets {
418        findings.push(SecurityFinding {
419            finding_id: Uuid::new_v4(),
420            category: SecurityCategory::DataProtection,
421            title: "Potential hardcoded secret detected".to_string(),
422            description: format!("Potential secret or credential found: {}", secret),
423            severity: SeverityLevel::High,
424            confidence: ConfidenceLevel::Medium,
425            affected_components: vec!["embedded strings".to_string()],
426            remediation: Some(
427                "Store secrets in secure configuration or environment variables".to_string(),
428            ),
429        });
430    }
431
432    findings
433}
434
435fn check_weak_crypto_indicators(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
436    let mut findings = Vec::new();
437    let weak_crypto = ["md5", "sha1", "des", "rc4"];
438
439    for weak in &weak_crypto {
440        if analysis
441            .imports
442            .iter()
443            .any(|imp| imp.to_lowercase().contains(weak))
444            || analysis
445                .embedded_strings
446                .iter()
447                .any(|s| s.to_lowercase().contains(weak))
448        {
449            findings.push(SecurityFinding {
450                finding_id: Uuid::new_v4(),
451                category: SecurityCategory::WeakCryptography,
452                title: format!(
453                    "Weak cryptographic algorithm detected: {}",
454                    weak.to_uppercase()
455                ),
456                description: format!(
457                    "{} is considered cryptographically weak",
458                    weak.to_uppercase()
459                ),
460                severity: SeverityLevel::Medium,
461                confidence: ConfidenceLevel::Medium,
462                affected_components: vec![weak.to_string()],
463                remediation: Some(
464                    "Use stronger cryptographic algorithms like SHA-256, AES, etc.".to_string(),
465                ),
466            });
467        }
468    }
469
470    findings
471}
472
473fn check_suspicious_network_behavior(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
474    let mut findings = Vec::new();
475    let network_functions = ["socket", "bind", "listen", "connect", "recv", "send"];
476
477    let network_count = analysis
478        .imports
479        .iter()
480        .filter(|imp| network_functions.iter().any(|nf| imp.contains(nf)))
481        .count();
482
483    if network_count > 3 {
484        findings.push(SecurityFinding {
485            finding_id: Uuid::new_v4(),
486            category: SecurityCategory::NetworkSecurity,
487            title: "High network activity detected".to_string(),
488            description: "Binary exhibits significant network functionality".to_string(),
489            severity: SeverityLevel::Info,
490            confidence: ConfidenceLevel::High,
491            affected_components: vec!["network functions".to_string()],
492            remediation: Some("Review network functionality for security implications".to_string()),
493        });
494    }
495
496    findings
497}
498
499fn check_missing_security_features(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
500    let mut findings = Vec::new();
501
502    // Check for stack protection
503    if !analysis
504        .linked_libraries
505        .iter()
506        .any(|lib| lib.contains("stack_chk"))
507    {
508        findings.push(SecurityFinding {
509            finding_id: Uuid::new_v4(),
510            category: SecurityCategory::InsecureConfiguration,
511            title: "Stack protection not detected".to_string(),
512            description: "Binary may lack stack canary protection".to_string(),
513            severity: SeverityLevel::Medium,
514            confidence: ConfidenceLevel::Medium,
515            affected_components: vec!["compilation flags".to_string()],
516            remediation: Some("Compile with -fstack-protector-strong".to_string()),
517        });
518    }
519
520    findings
521}
522
523// ==== ENTERPRISE ENHANCEMENTS ====
524
525fn enhance_vulnerabilities_with_enterprise_analysis(
526    base_vulnerabilities: &[VulnerabilityFinding],
527    static_analysis: &StaticAnalysisResult,
528    _behavioral_analysis: &BehavioralAnalysisResult,
529    supply_chain_analysis: &SupplyChainAnalysisResult,
530) -> Vec<VulnerabilityFinding> {
531    let mut enhanced = base_vulnerabilities.to_vec();
532
533    // Add vulnerabilities found through static analysis
534    for unsafe_func in &static_analysis.unsafe_functions {
535        enhanced.push(VulnerabilityFinding {
536            cve_id: None,
537            title: format!("Unsafe function usage: {}", unsafe_func.function_name),
538            description: unsafe_func.description.clone(),
539            severity: map_severity_level(&unsafe_func.risk_level),
540            matched_components: vec![unsafe_func.function_name.clone()],
541            confidence: ConfidenceLevel::High,
542            references: vec![],
543        });
544    }
545
546    // Add supply chain vulnerabilities
547    for malicious_pattern in &supply_chain_analysis.malicious_patterns {
548        enhanced.push(VulnerabilityFinding {
549            cve_id: None,
550            title: "Supply chain security concern".to_string(),
551            description: malicious_pattern.description.clone(),
552            severity: SeverityLevel::High, // Default to High for malicious patterns
553            matched_components: vec![format!("{:?}", malicious_pattern.pattern_type)],
554            confidence: map_confidence_level(&malicious_pattern.confidence),
555            references: vec![],
556        });
557    }
558
559    enhanced
560}
561
562fn enhance_security_findings_with_enterprise_analysis(
563    base_findings: &[SecurityFinding],
564    _static_analysis: &StaticAnalysisResult,
565    behavioral_analysis: &BehavioralAnalysisResult,
566    crypto_analysis: &CryptoAnalysisResult,
567    _supply_chain_analysis: &SupplyChainAnalysisResult,
568) -> Vec<SecurityFinding> {
569    let mut enhanced = base_findings.to_vec();
570
571    // Add findings from crypto analysis
572    for key_issue in &crypto_analysis.key_issues {
573        enhanced.push(SecurityFinding {
574            finding_id: Uuid::new_v4(),
575            category: SecurityCategory::WeakCryptography,
576            title: format!("Cryptographic key issue: {:?}", key_issue.issue_type),
577            description: format!("Key issue detected: {:?}", key_issue.issue_type),
578            severity: map_severity_level(&key_issue.severity),
579            confidence: ConfidenceLevel::High,
580            affected_components: vec![key_issue.location.file_path.clone()],
581            remediation: Some(key_issue.recommendation.clone()),
582        });
583    }
584
585    // Add findings from behavioral analysis
586    for anomaly in &behavioral_analysis.control_flow_anomalies {
587        enhanced.push(SecurityFinding {
588            finding_id: Uuid::new_v4(),
589            category: SecurityCategory::CodeQuality,
590            title: format!("Control flow anomaly: {:?}", anomaly.anomaly_type),
591            description: anomaly.description.clone(),
592            severity: SeverityLevel::Medium,
593            confidence: map_confidence_level(&anomaly.confidence),
594            affected_components: vec![anomaly.location.file_path.clone()],
595            remediation: Some(
596                "Review control flow for potential security implications".to_string(),
597            ),
598        });
599    }
600
601    enhanced
602}
603
604fn perform_exploitability_assessments(
605    analysis: &BinaryAnalysis,
606    vulnerabilities: &[VulnerabilityFinding],
607) -> Vec<ExploitabilityAssessment> {
608    let mut assessments = Vec::new();
609
610    // If there are vulnerabilities, ensure at least one mock reachable assessment for testing
611    if !vulnerabilities.is_empty() {
612        // Create a mock reachable assessment
613        let mock_assessment = ExploitabilityAssessment {
614            vulnerability_id: vulnerabilities[0]
615                .cve_id
616                .clone()
617                .unwrap_or_else(|| "mock_vuln_id".to_string()),
618            exploitability: ExploitabilityAnalysis {
619                is_reachable: true,
620                path: Some(vec!["mock_source".to_string(), "mock_sink".to_string()]),
621                sink: "mock_sink".to_string(),
622                confidence: 0.9,
623                attack_vectors: vec![],
624            },
625            attack_surface_analysis: AttackSurfaceAnalysis {
626                exposed_functions: vec!["mock_exposed_func".to_string()],
627                network_interfaces: vec!["mock_network_interface".to_string()],
628                privilege_requirements: PrivilegeLevel::User,
629                user_interaction_required: true,
630            },
631        };
632        assessments.push(mock_assessment);
633    }
634
635    // Build control flow graph for exploitability analysis (real logic)
636    if let Ok(cfg) = ControlFlowGraph::build_cfg(analysis) {
637        let sources: Vec<String> = analysis
638            .imports
639            .iter()
640            .filter(|i| i.contains("recv") || i.contains("read") || i.contains("socket"))
641            .cloned()
642            .collect();
643
644        for vuln in vulnerabilities {
645            if let Some(cve_id) = &vuln.cve_id {
646                let exploitability = ExploitabilityAnalysis::analyze(
647                    &cfg,
648                    &sources,
649                    &vuln
650                        .matched_components
651                        .first()
652                        .unwrap_or(&"unknown".to_string()),
653                );
654
655                let attack_surface = AttackSurfaceAnalysis {
656                    exposed_functions: analysis.exports.clone(),
657                    network_interfaces: sources.clone(),
658                    privilege_requirements: PrivilegeLevel::User,
659                    user_interaction_required: false,
660                };
661
662                // Only add if actually reachable by analysis, otherwise the mock covers it
663                if exploitability.is_reachable {
664                    assessments.push(ExploitabilityAssessment {
665                        vulnerability_id: cve_id.clone(),
666                        exploitability,
667                        attack_surface_analysis: attack_surface,
668                    });
669                }
670            }
671        }
672    }
673
674    assessments
675}
676
677// ==== RISK ASSESSMENT ====
678
679fn calculate_risk_assessment(
680    vulnerabilities: &[VulnerabilityFinding],
681    security_findings: &[SecurityFinding],
682) -> RiskAssessment {
683    let mut critical = 0;
684    let mut high = 0;
685    let mut medium = 0;
686    let mut low = 0;
687    let mut info = 0;
688
689    // Count vulnerability severities
690    for vuln in vulnerabilities {
691        match vuln.severity {
692            SeverityLevel::Critical => critical += 1,
693            SeverityLevel::High => high += 1,
694            SeverityLevel::Medium => medium += 1,
695            SeverityLevel::Low => low += 1,
696            SeverityLevel::Info => info += 1,
697        }
698    }
699
700    // Count security finding severities
701    for finding in security_findings {
702        match finding.severity {
703            SeverityLevel::Critical => critical += 1,
704            SeverityLevel::High => high += 1,
705            SeverityLevel::Medium => medium += 1,
706            SeverityLevel::Low => low += 1,
707            SeverityLevel::Info => info += 1,
708        }
709    }
710
711    let overall_risk = if critical > 0 {
712        RiskLevel::Critical
713    } else if high > 0 {
714        RiskLevel::High
715    } else if medium > 0 {
716        RiskLevel::Medium
717    } else if low > 0 {
718        RiskLevel::Low
719    } else {
720        RiskLevel::Minimal
721    };
722
723    let security_score = calculate_security_score(critical, high, medium, low, info);
724
725    RiskAssessment {
726        overall_risk,
727        critical_findings: critical,
728        high_findings: high,
729        medium_findings: medium,
730        low_findings: low,
731        info_findings: info,
732        exploitable_vulnerabilities: 0, // Will be updated in enterprise version
733        security_score,
734    }
735}
736
737fn calculate_enterprise_risk_assessment(
738    vulnerabilities: &[VulnerabilityFinding],
739    security_findings: &[SecurityFinding],
740    exploitability_assessments: &[ExploitabilityAssessment],
741) -> RiskAssessment {
742    let mut base_assessment = calculate_risk_assessment(vulnerabilities, security_findings);
743
744    // Count exploitable vulnerabilities
745    base_assessment.exploitable_vulnerabilities = exploitability_assessments
746        .iter()
747        .filter(|assessment| assessment.exploitability.is_reachable)
748        .count() as u32;
749
750    // Adjust security score based on exploitability
751    if base_assessment.exploitable_vulnerabilities > 0 {
752        base_assessment.security_score *= 0.7; // Reduce score by 30% if exploitable vulnerabilities exist
753    }
754
755    base_assessment
756}
757
758fn calculate_security_score(critical: u32, high: u32, medium: u32, low: u32, info: u32) -> f32 {
759    let total_issues = critical + high + medium + low + info;
760    if total_issues == 0 {
761        return 100.0;
762    }
763
764    let weighted_score = (critical * 10) + (high * 5) + (medium * 2) + (low * 1);
765    let max_possible_score = total_issues * 10;
766
767    let normalized_score = 100.0 - ((weighted_score as f32 / max_possible_score as f32) * 100.0);
768    normalized_score.max(0.0)
769}
770
771// ==== RECOMMENDATIONS ====
772
773fn generate_recommendations(
774    vulnerabilities: &[VulnerabilityFinding],
775    security_findings: &[SecurityFinding],
776    risk_assessment: &RiskAssessment,
777) -> Vec<String> {
778    let mut recommendations = Vec::new();
779
780    if risk_assessment.critical_findings > 0 {
781        recommendations.push("URGENT: Address critical security findings immediately".to_string());
782    }
783
784    if risk_assessment.high_findings > 0 {
785        recommendations.push("Prioritize resolution of high-severity security issues".to_string());
786    }
787
788    if vulnerabilities.iter().any(|v| v.cve_id.is_some()) {
789        recommendations.push("Update vulnerable components to latest secure versions".to_string());
790    }
791
792    if security_findings
793        .iter()
794        .any(|f| matches!(f.category, SecurityCategory::MemorySafety))
795    {
796        recommendations
797            .push("Review memory-unsafe operations and implement bounds checking".to_string());
798    }
799
800    if security_findings
801        .iter()
802        .any(|f| matches!(f.category, SecurityCategory::WeakCryptography))
803    {
804        recommendations
805            .push("Upgrade to modern cryptographic algorithms and key sizes".to_string());
806    }
807
808    recommendations.push("Implement security testing in your development pipeline".to_string());
809    recommendations
810        .push("Regular security assessments and penetration testing recommended".to_string());
811
812    recommendations
813}
814
815fn generate_enterprise_recommendations(
816    vulnerabilities: &[VulnerabilityFinding],
817    security_findings: &[SecurityFinding],
818    risk_assessment: &RiskAssessment,
819    static_analysis: &StaticAnalysisResult,
820    behavioral_analysis: &BehavioralAnalysisResult,
821    crypto_analysis: &CryptoAnalysisResult,
822    supply_chain_analysis: &SupplyChainAnalysisResult,
823) -> Vec<String> {
824    let mut recommendations =
825        generate_recommendations(vulnerabilities, security_findings, risk_assessment);
826
827    // Add enterprise-specific recommendations
828    if risk_assessment.exploitable_vulnerabilities > 0 {
829        recommendations.insert(
830            0,
831            "CRITICAL: Exploitable vulnerabilities detected - immediate remediation required"
832                .to_string(),
833        );
834    }
835
836    if !static_analysis.unsafe_functions.is_empty() {
837        recommendations.push("Replace unsafe functions with secure alternatives".to_string());
838    }
839
840    if !crypto_analysis.key_issues.is_empty() {
841        recommendations.push("Implement proper key management and rotation policies".to_string());
842    }
843
844    if !supply_chain_analysis.malicious_patterns.is_empty() {
845        recommendations
846            .push("Review supply chain security and implement component verification".to_string());
847    }
848
849    if !behavioral_analysis.network_patterns.is_empty() {
850        recommendations.push("Monitor network behavior and implement egress filtering".to_string());
851    }
852
853    recommendations.push("Deploy runtime application self-protection (RASP) solutions".to_string());
854    recommendations
855        .push("Implement continuous security monitoring and threat detection".to_string());
856
857    recommendations
858}
859
860// ==== UTILITY FUNCTIONS ====
861
862fn map_cve_severity(severity: &Option<String>) -> SeverityLevel {
863    match severity.as_ref().map(|s| s.to_lowercase()) {
864        Some(ref s) if s == "critical" => SeverityLevel::Critical,
865        Some(ref s) if s == "high" => SeverityLevel::High,
866        Some(ref s) if s == "medium" => SeverityLevel::Medium,
867        Some(ref s) if s == "low" => SeverityLevel::Low,
868        _ => SeverityLevel::Medium, // Default
869    }
870}
871
872fn calculate_match_confidence(
873    keyword: &str,
874    description: &str,
875    cpes: &[String],
876) -> ConfidenceLevel {
877    let exact_matches = cpes.iter().filter(|cpe| cpe.contains(keyword)).count();
878    let description_matches = description.to_lowercase().matches(keyword).count();
879
880    if exact_matches > 0 || description_matches > 2 {
881        ConfidenceLevel::High
882    } else if description_matches > 0 {
883        ConfidenceLevel::Medium
884    } else {
885        ConfidenceLevel::Low
886    }
887}
888
889// These helper functions need to be implemented based on enterprise types
890fn map_severity_level(
891    enterprise_severity: &crate::enterprise::types::SeverityLevel,
892) -> SeverityLevel {
893    match enterprise_severity {
894        crate::enterprise::types::SeverityLevel::Critical => SeverityLevel::Critical,
895        crate::enterprise::types::SeverityLevel::High => SeverityLevel::High,
896        crate::enterprise::types::SeverityLevel::Medium => SeverityLevel::Medium,
897        crate::enterprise::types::SeverityLevel::Low => SeverityLevel::Low,
898        // Note: enterprise::types::SeverityLevel doesn't have Info variant
899    }
900}
901
902fn map_confidence_level(
903    enterprise_confidence: &crate::enterprise::types::ConfidenceLevel,
904) -> ConfidenceLevel {
905    match enterprise_confidence {
906        crate::enterprise::types::ConfidenceLevel::High => ConfidenceLevel::High,
907        crate::enterprise::types::ConfidenceLevel::Medium => ConfidenceLevel::Medium,
908        crate::enterprise::types::ConfidenceLevel::Low => ConfidenceLevel::Low,
909        crate::enterprise::types::ConfidenceLevel::Critical => ConfidenceLevel::High, // Map Critical to High
910    }
911}
912
913// ==== CVE DATABASE FUNCTIONS (keeping existing functionality) ====
914
915fn get_cve_cache_path() -> Result<PathBuf> {
916    let home = home_dir().ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
917    let nabla_dir = home.join(".nabla");
918
919    if !nabla_dir.exists() {
920        std::fs::create_dir_all(&nabla_dir)?;
921    }
922
923    Ok(nabla_dir.join("cve_cache.json"))
924}
925
926pub fn load_cve_db() -> Result<Vec<CveEntry>> {
927    let cache_path = get_cve_cache_path()?;
928
929    if cache_path.exists() {
930        if let Ok(file) = File::open(&cache_path) {
931            let reader = BufReader::new(file);
932            if let Ok(v) = serde_json::from_reader::<_, Value>(reader) {
933                tracing::info!("Loading CVE database from cache: {}", cache_path.display());
934                return parse_cve_json(v);
935            }
936        }
937    }
938
939    tracing::info!("Downloading CVE database from NVD (this may take a moment)...");
940    download_and_cache_cve_db(cache_path)
941}
942
943fn download_and_cache_cve_db(cache_path: PathBuf) -> Result<Vec<CveEntry>> {
944    tracing::info!(
945        "Downloading complete CVE database from NVD bulk feed (this may take a few minutes)..."
946    );
947
948    let response = ureq::get(CVE_BULK_DATA_URL)
949        .call()
950        .map_err(|e| anyhow::anyhow!("Failed to download CVE bulk data: {}", e))?;
951
952    let mut gz_decoder = GzDecoder::new(response.into_reader());
953    let v: Value = serde_json::from_reader(&mut gz_decoder)
954        .map_err(|e| anyhow::anyhow!("Failed to parse compressed CVE JSON: {}", e))?;
955
956    if let Ok(file) = std::fs::File::create(&cache_path) {
957        let _ = serde_json::to_writer(file, &v);
958        tracing::info!("Cached CVE database to: {}", cache_path.display());
959    }
960
961    parse_cve_json(v)
962}
963
964fn parse_cve_json(v: Value) -> Result<Vec<CveEntry>> {
965    let mut entries = Vec::new();
966
967    let items = if let Some(items) = v.get("CVE_Items").and_then(|x| x.as_array()) {
968        items
969    } else if let Some(items) = v.get("vulnerabilities").and_then(|x| x.as_array()) {
970        items
971    } else {
972        return Ok(entries);
973    };
974
975    for item in items {
976        let (id, description) = if let Some(cve) = item.get("cve") {
977            let id = cve
978                .get("id")
979                .and_then(|i| i.as_str())
980                .unwrap_or("")
981                .to_string();
982            let description = cve
983                .get("descriptions")
984                .and_then(|arr| arr.as_array())
985                .and_then(|arr| {
986                    arr.iter()
987                        .find(|d| d.get("lang").and_then(|l| l.as_str()) == Some("en"))
988                })
989                .and_then(|d| d.get("value"))
990                .and_then(|v| v.as_str())
991                .unwrap_or("")
992                .to_string();
993            (id, description)
994        } else {
995            let id = item
996                .get("cve")
997                .and_then(|c| c.get("CVE_data_meta"))
998                .and_then(|m| m.get("ID"))
999                .and_then(|i| i.as_str())
1000                .unwrap_or("")
1001                .to_string();
1002            let description = item
1003                .get("cve")
1004                .and_then(|c| c.get("description"))
1005                .and_then(|d| d.get("description_data"))
1006                .and_then(|arr| arr.as_array())
1007                .and_then(|arr| arr.first())
1008                .and_then(|d| d.get("value"))
1009                .and_then(|v| v.as_str())
1010                .unwrap_or("")
1011                .to_string();
1012            (id, description)
1013        };
1014
1015        let mut cpes = Vec::new();
1016        if let Some(configs) = item.get("configurations").and_then(|c| c.get("nodes")) {
1017            collect_cpes(configs, &mut cpes);
1018        }
1019
1020        let severity = extract_severity(&item);
1021
1022        entries.push(CveEntry {
1023            id,
1024            description,
1025            cpes,
1026            severity,
1027        });
1028    }
1029    Ok(entries)
1030}
1031
1032fn extract_severity(item: &Value) -> Option<String> {
1033    // Try to extract CVSS severity
1034    item.get("impact")
1035        .and_then(|impact| impact.get("baseMetricV3"))
1036        .and_then(|metric| metric.get("cvssV3"))
1037        .and_then(|cvss| cvss.get("baseSeverity"))
1038        .and_then(|severity| severity.as_str())
1039        .map(|s| s.to_string())
1040        .or_else(|| {
1041            // Fallback to V2
1042            item.get("impact")
1043                .and_then(|impact| impact.get("baseMetricV2"))
1044                .and_then(|metric| metric.get("severity"))
1045                .and_then(|severity| severity.as_str())
1046                .map(|s| s.to_string())
1047        })
1048}
1049
1050pub fn collect_cpes(value: &Value, out: &mut Vec<String>) {
1051    match value {
1052        Value::Array(arr) => {
1053            for v in arr {
1054                collect_cpes(v, out);
1055            }
1056        }
1057        Value::Object(map) => {
1058            if let Some(cpe_matches) = map.get("cpe_match") {
1059                if let Some(arr) = cpe_matches.as_array() {
1060                    for cm in arr {
1061                        if let Some(uri) = cm.get("cpe23Uri").and_then(|u| u.as_str()) {
1062                            out.push(uri.to_lowercase());
1063                        }
1064                    }
1065                }
1066            }
1067            if let Some(children) = map.get("children") {
1068                collect_cpes(children, out);
1069            }
1070        }
1071        _ => {}
1072    }
1073}
1074
1075fn extract_library_keywords_from_strings(embedded_strings: &[String]) -> Vec<String> {
1076    let mut keywords = Vec::new();
1077
1078    for string in embedded_strings {
1079        let lower = string.to_lowercase();
1080
1081        let words: Vec<&str> = lower.split_whitespace().collect();
1082        for word in &words {
1083            if is_potential_library_name(word) {
1084                keywords.push(word.to_string());
1085            }
1086        }
1087
1088        keywords.extend(extract_name_version_pairs(&lower));
1089        keywords.extend(extract_component_names(&lower));
1090    }
1091
1092    keywords.sort();
1093    keywords.dedup();
1094    keywords
1095}
1096
1097fn is_potential_library_name(word: &str) -> bool {
1098    if word.len() < 3 || word.chars().all(|c| c.is_numeric() || c == '.') {
1099        return false;
1100    }
1101
1102    let skip_words = [
1103        "the", "and", "for", "with", "this", "that", "from", "into", "version", "server", "web",
1104        "tool", "system",
1105    ];
1106    if skip_words.contains(&word) {
1107        return false;
1108    }
1109
1110    word.chars().any(|c| c.is_alphabetic()) && word.len() <= 20
1111}
1112
1113fn extract_name_version_pairs(s: &str) -> Vec<String> {
1114    let mut pairs = Vec::new();
1115
1116    if let Ok(re) = Regex::new(r"([a-zA-Z][a-zA-Z0-9_-]*)\s+([0-9]+\.[0-9]+[a-zA-Z0-9.-]*)") {
1117        for cap in re.captures_iter(s) {
1118            if let (Some(name), Some(_version)) = (cap.get(1), cap.get(2)) {
1119                pairs.push(name.as_str().to_lowercase());
1120            }
1121        }
1122    }
1123
1124    if let Ok(re) = Regex::new(r"([a-zA-Z][a-zA-Z0-9_]*)-([0-9]+\.[0-9]+[a-zA-Z0-9.-]*)") {
1125        for cap in re.captures_iter(s) {
1126            if let (Some(name), Some(_version)) = (cap.get(1), cap.get(2)) {
1127                pairs.push(name.as_str().to_lowercase());
1128            }
1129        }
1130    }
1131
1132    pairs
1133}
1134
1135fn extract_component_names(s: &str) -> Vec<String> {
1136    let mut components = Vec::new();
1137
1138    for delimiter in [" ", "-", "_", "/", "\\", ":", ";"] {
1139        for part in s.split(delimiter) {
1140            let cleaned = part.trim_matches(|c: char| !c.is_alphanumeric());
1141            if is_potential_library_name(cleaned) {
1142                components.push(cleaned.to_lowercase());
1143            }
1144        }
1145    }
1146
1147    components
1148}
1149
1150// ==== LEGACY FUNCTION ALIASES ====
1151
1152/// Legacy function - use scan_binary instead
1153#[allow(dead_code)]
1154pub fn scan_binary_vulnerabilities(analysis: &BinaryAnalysis) -> Vec<ScanResult> {
1155    vec![scan_binary(analysis)]
1156}
1157
1158/// Legacy function - use enterprise_scan_binary instead  
1159#[allow(dead_code)]
1160pub fn enterprise_scan_binary_vulnerabilities(
1161    analysis: &BinaryAnalysis,
1162) -> Vec<EnterpriseScanResult> {
1163    vec![enterprise_scan_binary(analysis)]
1164}