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#[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 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, }
102
103#[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
155const 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
178pub fn scan_binary(analysis: &BinaryAnalysis) -> ScanResult {
183 let scan_id = Uuid::new_v4();
184 let scan_timestamp = Utc::now();
185
186 let vulnerability_findings = detect_vulnerabilities(analysis);
188
189 let security_findings = perform_basic_security_analysis(analysis);
191
192 let risk_assessment = calculate_risk_assessment(&vulnerability_findings, &security_findings);
194
195 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
213pub fn enterprise_scan_binary(analysis: &BinaryAnalysis) -> EnterpriseScanResult {
217 let scan_id = Uuid::new_v4();
218 let scan_timestamp = Utc::now();
219
220 let oss_result = scan_binary(analysis);
222
223 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 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 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 let exploitability_assessments =
248 perform_exploitability_assessments(analysis, &enhanced_vulnerability_findings);
249
250 let enterprise_risk_assessment = calculate_enterprise_risk_assessment(
252 &enhanced_vulnerability_findings,
253 &enhanced_security_findings,
254 &exploitability_assessments,
255 );
256
257 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
284fn detect_vulnerabilities(analysis: &BinaryAnalysis) -> Vec<VulnerabilityFinding> {
287 let mut findings = Vec::new();
288
289 let keywords = extract_component_keywords(analysis);
291
292 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 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 keywords.extend(extract_library_keywords_from_strings(
346 &analysis.embedded_strings,
347 ));
348
349 keywords.sort();
350 keywords.dedup();
351 keywords
352}
353
354fn perform_basic_security_analysis(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
357 let mut findings = Vec::new();
358
359 findings.extend(check_insecure_functions(analysis));
361
362 findings.extend(check_hardcoded_secrets(analysis));
364
365 findings.extend(check_weak_crypto_indicators(analysis));
367
368 findings.extend(check_suspicious_network_behavior(analysis));
370
371 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 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
523fn 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 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 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, 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 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 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 !vulnerabilities.is_empty() {
612 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 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 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
677fn 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 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 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, 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 base_assessment.exploitable_vulnerabilities = exploitability_assessments
746 .iter()
747 .filter(|assessment| assessment.exploitability.is_reachable)
748 .count() as u32;
749
750 if base_assessment.exploitable_vulnerabilities > 0 {
752 base_assessment.security_score *= 0.7; }
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
771fn 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 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
860fn 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, }
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
889fn 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 }
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, }
911}
912
913fn 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 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 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#[allow(dead_code)]
1154pub fn scan_binary_vulnerabilities(analysis: &BinaryAnalysis) -> Vec<ScanResult> {
1155 vec![scan_binary(analysis)]
1156}
1157
1158#[allow(dead_code)]
1160pub fn enterprise_scan_binary_vulnerabilities(
1161 analysis: &BinaryAnalysis,
1162) -> Vec<EnterpriseScanResult> {
1163 vec![enterprise_scan_binary(analysis)]
1164}