use anyhow::Result;
use chrono::Utc;
use flate2::read::GzDecoder;
use home::home_dir;
use once_cell::sync::Lazy;
use regex::Regex;
use serde::Serialize;
use serde_json::Value;
use std::{fs::File, io::BufReader, path::PathBuf};
use uuid::Uuid;
use super::BinaryAnalysis;
use crate::enterprise::secure::behavioral_analysis::BehavioralAnalysisResult;
use crate::enterprise::secure::control_flow::{ControlFlowGraph, ExploitabilityAnalysis};
use crate::enterprise::secure::crypto_analysis::CryptoAnalysisResult;
use crate::enterprise::secure::static_analysis::StaticAnalysisResult;
use crate::enterprise::secure::supply_chain::SupplyChainAnalysisResult;
use crate::enterprise::{
analyze_behavioral_security, analyze_crypto_security, analyze_static_security,
analyze_supply_chain_security,
};
#[derive(Debug, Clone, Serialize)]
pub struct ScanResult {
pub scan_id: Uuid,
pub target_file: String,
pub scan_timestamp: chrono::DateTime<chrono::Utc>,
pub vulnerability_findings: Vec<VulnerabilityFinding>,
pub security_findings: Vec<SecurityFinding>,
pub risk_assessment: RiskAssessment,
pub recommendations: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct EnterpriseScanResult {
pub scan_id: Uuid,
pub target_file: String,
pub scan_timestamp: chrono::DateTime<chrono::Utc>,
pub vulnerability_findings: Vec<VulnerabilityFinding>,
pub security_findings: Vec<SecurityFinding>,
pub risk_assessment: RiskAssessment,
pub recommendations: Vec<String>,
pub static_analysis: StaticAnalysisResult,
pub behavioral_analysis: BehavioralAnalysisResult,
pub crypto_analysis: CryptoAnalysisResult,
pub supply_chain_analysis: SupplyChainAnalysisResult,
pub exploitability_assessments: Vec<ExploitabilityAssessment>,
}
#[derive(Debug, Clone, Serialize)]
pub struct VulnerabilityFinding {
pub cve_id: Option<String>,
pub title: String,
pub description: String,
pub severity: SeverityLevel,
pub matched_components: Vec<String>,
pub confidence: ConfidenceLevel,
pub references: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct SecurityFinding {
pub finding_id: Uuid,
pub category: SecurityCategory,
pub title: String,
pub description: String,
pub severity: SeverityLevel,
pub confidence: ConfidenceLevel,
pub affected_components: Vec<String>,
pub remediation: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct ExploitabilityAssessment {
pub vulnerability_id: String,
pub exploitability: ExploitabilityAnalysis,
pub attack_surface_analysis: AttackSurfaceAnalysis,
}
#[derive(Debug, Clone, Serialize)]
pub struct AttackSurfaceAnalysis {
pub exposed_functions: Vec<String>,
pub network_interfaces: Vec<String>,
pub privilege_requirements: PrivilegeLevel,
pub user_interaction_required: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct RiskAssessment {
pub overall_risk: RiskLevel,
pub critical_findings: u32,
pub high_findings: u32,
pub medium_findings: u32,
pub low_findings: u32,
pub info_findings: u32,
pub exploitable_vulnerabilities: u32,
pub security_score: f32, }
#[derive(Debug, Clone, Serialize, PartialEq)]
pub enum SeverityLevel {
Critical,
High,
Medium,
Low,
Info,
}
#[derive(Debug, Clone, Serialize)]
pub enum ConfidenceLevel {
High,
Medium,
Low,
}
#[derive(Debug, Clone, Serialize)]
#[allow(dead_code)]
pub enum SecurityCategory {
VulnerableComponent,
InsecureConfiguration,
WeakCryptography,
MemorySafety,
NetworkSecurity,
DataProtection,
AccessControl,
CodeQuality,
SupplyChain,
ComplianceViolation,
}
#[derive(Debug, Clone, Serialize)]
pub enum RiskLevel {
Critical,
High,
Medium,
Low,
Minimal,
}
#[derive(Debug, Clone, Serialize)]
#[allow(dead_code)]
pub enum PrivilegeLevel {
System,
Administrator,
User,
Guest,
None,
}
const CVE_BULK_DATA_URL: &str =
"https://nvd.nist.gov/feeds/json/cve/2.0/nvdcve-2.0-modified.json.gz";
pub struct CveEntry {
id: String,
description: String,
cpes: Vec<String>,
severity: Option<String>,
}
static CVE_DB: Lazy<Vec<CveEntry>> = Lazy::new(|| match load_cve_db() {
Ok(db) => {
tracing::info!("Loaded {} CVE records", db.len());
db
}
Err(e) => {
tracing::error!("Failed to load CVE DB: {}", e);
Vec::new()
}
});
pub fn scan_binary(analysis: &BinaryAnalysis) -> ScanResult {
let scan_id = Uuid::new_v4();
let scan_timestamp = Utc::now();
let vulnerability_findings = detect_vulnerabilities(analysis);
let security_findings = perform_basic_security_analysis(analysis);
let risk_assessment = calculate_risk_assessment(&vulnerability_findings, &security_findings);
let recommendations = generate_recommendations(
&vulnerability_findings,
&security_findings,
&risk_assessment,
);
ScanResult {
scan_id,
target_file: analysis.file_name.clone(),
scan_timestamp,
vulnerability_findings,
security_findings,
risk_assessment,
recommendations,
}
}
pub fn enterprise_scan_binary(analysis: &BinaryAnalysis) -> EnterpriseScanResult {
let scan_id = Uuid::new_v4();
let scan_timestamp = Utc::now();
let oss_result = scan_binary(analysis);
let static_analysis = analyze_static_security(analysis);
let behavioral_analysis = analyze_behavioral_security(analysis);
let crypto_analysis = analyze_crypto_security(analysis);
let supply_chain_analysis = analyze_supply_chain_security(analysis);
let enhanced_vulnerability_findings = enhance_vulnerabilities_with_enterprise_analysis(
&oss_result.vulnerability_findings,
&static_analysis,
&behavioral_analysis,
&supply_chain_analysis,
);
let enhanced_security_findings = enhance_security_findings_with_enterprise_analysis(
&oss_result.security_findings,
&static_analysis,
&behavioral_analysis,
&crypto_analysis,
&supply_chain_analysis,
);
let exploitability_assessments =
perform_exploitability_assessments(analysis, &enhanced_vulnerability_findings);
let enterprise_risk_assessment = calculate_enterprise_risk_assessment(
&enhanced_vulnerability_findings,
&enhanced_security_findings,
&exploitability_assessments,
);
let enhanced_recommendations = generate_enterprise_recommendations(
&enhanced_vulnerability_findings,
&enhanced_security_findings,
&enterprise_risk_assessment,
&static_analysis,
&behavioral_analysis,
&crypto_analysis,
&supply_chain_analysis,
);
EnterpriseScanResult {
scan_id,
target_file: analysis.file_name.clone(),
scan_timestamp,
vulnerability_findings: enhanced_vulnerability_findings,
security_findings: enhanced_security_findings,
risk_assessment: enterprise_risk_assessment,
recommendations: enhanced_recommendations,
static_analysis,
behavioral_analysis,
crypto_analysis,
supply_chain_analysis,
exploitability_assessments,
}
}
fn detect_vulnerabilities(analysis: &BinaryAnalysis) -> Vec<VulnerabilityFinding> {
let mut findings = Vec::new();
let keywords = extract_component_keywords(analysis);
for entry in CVE_DB.iter() {
for keyword in &keywords {
if keyword.is_empty() {
continue;
}
if entry.description.to_lowercase().contains(keyword)
|| entry.cpes.iter().any(|c| c.contains(keyword))
{
let severity = map_cve_severity(&entry.severity);
let confidence =
calculate_match_confidence(keyword, &entry.description, &entry.cpes);
findings.push(VulnerabilityFinding {
cve_id: Some(entry.id.clone()),
title: format!("Potential vulnerability in {}", keyword),
description: entry.description.clone(),
severity,
matched_components: vec![keyword.clone()],
confidence,
references: vec![format!("https://nvd.nist.gov/vuln/detail/{}", entry.id)],
});
break;
}
}
}
findings
}
fn extract_component_keywords(analysis: &BinaryAnalysis) -> Vec<String> {
let mut keywords: Vec<String> = analysis
.linked_libraries
.iter()
.chain(analysis.imports.iter())
.map(|s| s.to_lowercase())
.collect();
if let Some(cpe_candidates) = analysis
.metadata
.get("cpe_candidates")
.and_then(|c| c.as_array())
{
keywords.extend(
cpe_candidates
.iter()
.filter_map(|c| c.as_str().map(|s| s.to_string())),
);
}
keywords.extend(extract_library_keywords_from_strings(
&analysis.embedded_strings,
));
keywords.sort();
keywords.dedup();
keywords
}
fn perform_basic_security_analysis(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
let mut findings = Vec::new();
findings.extend(check_insecure_functions(analysis));
findings.extend(check_hardcoded_secrets(analysis));
findings.extend(check_weak_crypto_indicators(analysis));
findings.extend(check_suspicious_network_behavior(analysis));
findings.extend(check_missing_security_features(analysis));
findings
}
fn check_insecure_functions(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
let mut findings = Vec::new();
let dangerous_functions = [
(
"strcpy",
"Buffer overflow vulnerability - use strcpy_s or strncpy",
),
(
"strcat",
"Buffer overflow vulnerability - use strcat_s or strncat",
),
("sprintf", "Buffer overflow vulnerability - use snprintf"),
("gets", "Buffer overflow vulnerability - use fgets"),
(
"scanf",
"Input validation vulnerability - use specific format specifiers",
),
];
for (func, desc) in &dangerous_functions {
if analysis.imports.iter().any(|imp| imp.contains(func)) {
findings.push(SecurityFinding {
finding_id: Uuid::new_v4(),
category: SecurityCategory::MemorySafety,
title: format!("Dangerous function detected: {}", func),
description: desc.to_string(),
severity: SeverityLevel::High,
confidence: ConfidenceLevel::High,
affected_components: vec![func.to_string()],
remediation: Some(format!("Replace {} with safer alternatives", func)),
});
}
}
findings
}
fn check_hardcoded_secrets(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
let mut findings = Vec::new();
for secret in &analysis.suspected_secrets {
findings.push(SecurityFinding {
finding_id: Uuid::new_v4(),
category: SecurityCategory::DataProtection,
title: "Potential hardcoded secret detected".to_string(),
description: format!("Potential secret or credential found: {}", secret),
severity: SeverityLevel::High,
confidence: ConfidenceLevel::Medium,
affected_components: vec!["embedded strings".to_string()],
remediation: Some(
"Store secrets in secure configuration or environment variables".to_string(),
),
});
}
findings
}
fn check_weak_crypto_indicators(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
let mut findings = Vec::new();
let weak_crypto = ["md5", "sha1", "des", "rc4"];
for weak in &weak_crypto {
if analysis
.imports
.iter()
.any(|imp| imp.to_lowercase().contains(weak))
|| analysis
.embedded_strings
.iter()
.any(|s| s.to_lowercase().contains(weak))
{
findings.push(SecurityFinding {
finding_id: Uuid::new_v4(),
category: SecurityCategory::WeakCryptography,
title: format!(
"Weak cryptographic algorithm detected: {}",
weak.to_uppercase()
),
description: format!(
"{} is considered cryptographically weak",
weak.to_uppercase()
),
severity: SeverityLevel::Medium,
confidence: ConfidenceLevel::Medium,
affected_components: vec![weak.to_string()],
remediation: Some(
"Use stronger cryptographic algorithms like SHA-256, AES, etc.".to_string(),
),
});
}
}
findings
}
fn check_suspicious_network_behavior(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
let mut findings = Vec::new();
let network_functions = ["socket", "bind", "listen", "connect", "recv", "send"];
let network_count = analysis
.imports
.iter()
.filter(|imp| network_functions.iter().any(|nf| imp.contains(nf)))
.count();
if network_count > 3 {
findings.push(SecurityFinding {
finding_id: Uuid::new_v4(),
category: SecurityCategory::NetworkSecurity,
title: "High network activity detected".to_string(),
description: "Binary exhibits significant network functionality".to_string(),
severity: SeverityLevel::Info,
confidence: ConfidenceLevel::High,
affected_components: vec!["network functions".to_string()],
remediation: Some("Review network functionality for security implications".to_string()),
});
}
findings
}
fn check_missing_security_features(analysis: &BinaryAnalysis) -> Vec<SecurityFinding> {
let mut findings = Vec::new();
if !analysis
.linked_libraries
.iter()
.any(|lib| lib.contains("stack_chk"))
{
findings.push(SecurityFinding {
finding_id: Uuid::new_v4(),
category: SecurityCategory::InsecureConfiguration,
title: "Stack protection not detected".to_string(),
description: "Binary may lack stack canary protection".to_string(),
severity: SeverityLevel::Medium,
confidence: ConfidenceLevel::Medium,
affected_components: vec!["compilation flags".to_string()],
remediation: Some("Compile with -fstack-protector-strong".to_string()),
});
}
findings
}
fn enhance_vulnerabilities_with_enterprise_analysis(
base_vulnerabilities: &[VulnerabilityFinding],
static_analysis: &StaticAnalysisResult,
_behavioral_analysis: &BehavioralAnalysisResult,
supply_chain_analysis: &SupplyChainAnalysisResult,
) -> Vec<VulnerabilityFinding> {
let mut enhanced = base_vulnerabilities.to_vec();
for unsafe_func in &static_analysis.unsafe_functions {
enhanced.push(VulnerabilityFinding {
cve_id: None,
title: format!("Unsafe function usage: {}", unsafe_func.function_name),
description: unsafe_func.description.clone(),
severity: map_severity_level(&unsafe_func.risk_level),
matched_components: vec![unsafe_func.function_name.clone()],
confidence: ConfidenceLevel::High,
references: vec![],
});
}
for malicious_pattern in &supply_chain_analysis.malicious_patterns {
enhanced.push(VulnerabilityFinding {
cve_id: None,
title: "Supply chain security concern".to_string(),
description: malicious_pattern.description.clone(),
severity: SeverityLevel::High, matched_components: vec![format!("{:?}", malicious_pattern.pattern_type)],
confidence: map_confidence_level(&malicious_pattern.confidence),
references: vec![],
});
}
enhanced
}
fn enhance_security_findings_with_enterprise_analysis(
base_findings: &[SecurityFinding],
_static_analysis: &StaticAnalysisResult,
behavioral_analysis: &BehavioralAnalysisResult,
crypto_analysis: &CryptoAnalysisResult,
_supply_chain_analysis: &SupplyChainAnalysisResult,
) -> Vec<SecurityFinding> {
let mut enhanced = base_findings.to_vec();
for key_issue in &crypto_analysis.key_issues {
enhanced.push(SecurityFinding {
finding_id: Uuid::new_v4(),
category: SecurityCategory::WeakCryptography,
title: format!("Cryptographic key issue: {:?}", key_issue.issue_type),
description: format!("Key issue detected: {:?}", key_issue.issue_type),
severity: map_severity_level(&key_issue.severity),
confidence: ConfidenceLevel::High,
affected_components: vec![key_issue.location.file_path.clone()],
remediation: Some(key_issue.recommendation.clone()),
});
}
for anomaly in &behavioral_analysis.control_flow_anomalies {
enhanced.push(SecurityFinding {
finding_id: Uuid::new_v4(),
category: SecurityCategory::CodeQuality,
title: format!("Control flow anomaly: {:?}", anomaly.anomaly_type),
description: anomaly.description.clone(),
severity: SeverityLevel::Medium,
confidence: map_confidence_level(&anomaly.confidence),
affected_components: vec![anomaly.location.file_path.clone()],
remediation: Some(
"Review control flow for potential security implications".to_string(),
),
});
}
enhanced
}
fn perform_exploitability_assessments(
analysis: &BinaryAnalysis,
vulnerabilities: &[VulnerabilityFinding],
) -> Vec<ExploitabilityAssessment> {
let mut assessments = Vec::new();
if !vulnerabilities.is_empty() {
let mock_assessment = ExploitabilityAssessment {
vulnerability_id: vulnerabilities[0]
.cve_id
.clone()
.unwrap_or_else(|| "mock_vuln_id".to_string()),
exploitability: ExploitabilityAnalysis {
is_reachable: true,
path: Some(vec!["mock_source".to_string(), "mock_sink".to_string()]),
sink: "mock_sink".to_string(),
confidence: 0.9,
attack_vectors: vec![],
},
attack_surface_analysis: AttackSurfaceAnalysis {
exposed_functions: vec!["mock_exposed_func".to_string()],
network_interfaces: vec!["mock_network_interface".to_string()],
privilege_requirements: PrivilegeLevel::User,
user_interaction_required: true,
},
};
assessments.push(mock_assessment);
}
if let Ok(cfg) = ControlFlowGraph::build_cfg(analysis) {
let sources: Vec<String> = analysis
.imports
.iter()
.filter(|i| i.contains("recv") || i.contains("read") || i.contains("socket"))
.cloned()
.collect();
for vuln in vulnerabilities {
if let Some(cve_id) = &vuln.cve_id {
let exploitability = ExploitabilityAnalysis::analyze(
&cfg,
&sources,
&vuln
.matched_components
.first()
.unwrap_or(&"unknown".to_string()),
);
let attack_surface = AttackSurfaceAnalysis {
exposed_functions: analysis.exports.clone(),
network_interfaces: sources.clone(),
privilege_requirements: PrivilegeLevel::User,
user_interaction_required: false,
};
if exploitability.is_reachable {
assessments.push(ExploitabilityAssessment {
vulnerability_id: cve_id.clone(),
exploitability,
attack_surface_analysis: attack_surface,
});
}
}
}
}
assessments
}
fn calculate_risk_assessment(
vulnerabilities: &[VulnerabilityFinding],
security_findings: &[SecurityFinding],
) -> RiskAssessment {
let mut critical = 0;
let mut high = 0;
let mut medium = 0;
let mut low = 0;
let mut info = 0;
for vuln in vulnerabilities {
match vuln.severity {
SeverityLevel::Critical => critical += 1,
SeverityLevel::High => high += 1,
SeverityLevel::Medium => medium += 1,
SeverityLevel::Low => low += 1,
SeverityLevel::Info => info += 1,
}
}
for finding in security_findings {
match finding.severity {
SeverityLevel::Critical => critical += 1,
SeverityLevel::High => high += 1,
SeverityLevel::Medium => medium += 1,
SeverityLevel::Low => low += 1,
SeverityLevel::Info => info += 1,
}
}
let overall_risk = if critical > 0 {
RiskLevel::Critical
} else if high > 0 {
RiskLevel::High
} else if medium > 0 {
RiskLevel::Medium
} else if low > 0 {
RiskLevel::Low
} else {
RiskLevel::Minimal
};
let security_score = calculate_security_score(critical, high, medium, low, info);
RiskAssessment {
overall_risk,
critical_findings: critical,
high_findings: high,
medium_findings: medium,
low_findings: low,
info_findings: info,
exploitable_vulnerabilities: 0, security_score,
}
}
fn calculate_enterprise_risk_assessment(
vulnerabilities: &[VulnerabilityFinding],
security_findings: &[SecurityFinding],
exploitability_assessments: &[ExploitabilityAssessment],
) -> RiskAssessment {
let mut base_assessment = calculate_risk_assessment(vulnerabilities, security_findings);
base_assessment.exploitable_vulnerabilities = exploitability_assessments
.iter()
.filter(|assessment| assessment.exploitability.is_reachable)
.count() as u32;
if base_assessment.exploitable_vulnerabilities > 0 {
base_assessment.security_score *= 0.7; }
base_assessment
}
fn calculate_security_score(critical: u32, high: u32, medium: u32, low: u32, info: u32) -> f32 {
let total_issues = critical + high + medium + low + info;
if total_issues == 0 {
return 100.0;
}
let weighted_score = (critical * 10) + (high * 5) + (medium * 2) + (low * 1);
let max_possible_score = total_issues * 10;
let normalized_score = 100.0 - ((weighted_score as f32 / max_possible_score as f32) * 100.0);
normalized_score.max(0.0)
}
fn generate_recommendations(
vulnerabilities: &[VulnerabilityFinding],
security_findings: &[SecurityFinding],
risk_assessment: &RiskAssessment,
) -> Vec<String> {
let mut recommendations = Vec::new();
if risk_assessment.critical_findings > 0 {
recommendations.push("URGENT: Address critical security findings immediately".to_string());
}
if risk_assessment.high_findings > 0 {
recommendations.push("Prioritize resolution of high-severity security issues".to_string());
}
if vulnerabilities.iter().any(|v| v.cve_id.is_some()) {
recommendations.push("Update vulnerable components to latest secure versions".to_string());
}
if security_findings
.iter()
.any(|f| matches!(f.category, SecurityCategory::MemorySafety))
{
recommendations
.push("Review memory-unsafe operations and implement bounds checking".to_string());
}
if security_findings
.iter()
.any(|f| matches!(f.category, SecurityCategory::WeakCryptography))
{
recommendations
.push("Upgrade to modern cryptographic algorithms and key sizes".to_string());
}
recommendations.push("Implement security testing in your development pipeline".to_string());
recommendations
.push("Regular security assessments and penetration testing recommended".to_string());
recommendations
}
fn generate_enterprise_recommendations(
vulnerabilities: &[VulnerabilityFinding],
security_findings: &[SecurityFinding],
risk_assessment: &RiskAssessment,
static_analysis: &StaticAnalysisResult,
behavioral_analysis: &BehavioralAnalysisResult,
crypto_analysis: &CryptoAnalysisResult,
supply_chain_analysis: &SupplyChainAnalysisResult,
) -> Vec<String> {
let mut recommendations =
generate_recommendations(vulnerabilities, security_findings, risk_assessment);
if risk_assessment.exploitable_vulnerabilities > 0 {
recommendations.insert(
0,
"CRITICAL: Exploitable vulnerabilities detected - immediate remediation required"
.to_string(),
);
}
if !static_analysis.unsafe_functions.is_empty() {
recommendations.push("Replace unsafe functions with secure alternatives".to_string());
}
if !crypto_analysis.key_issues.is_empty() {
recommendations.push("Implement proper key management and rotation policies".to_string());
}
if !supply_chain_analysis.malicious_patterns.is_empty() {
recommendations
.push("Review supply chain security and implement component verification".to_string());
}
if !behavioral_analysis.network_patterns.is_empty() {
recommendations.push("Monitor network behavior and implement egress filtering".to_string());
}
recommendations.push("Deploy runtime application self-protection (RASP) solutions".to_string());
recommendations
.push("Implement continuous security monitoring and threat detection".to_string());
recommendations
}
fn map_cve_severity(severity: &Option<String>) -> SeverityLevel {
match severity.as_ref().map(|s| s.to_lowercase()) {
Some(ref s) if s == "critical" => SeverityLevel::Critical,
Some(ref s) if s == "high" => SeverityLevel::High,
Some(ref s) if s == "medium" => SeverityLevel::Medium,
Some(ref s) if s == "low" => SeverityLevel::Low,
_ => SeverityLevel::Medium, }
}
fn calculate_match_confidence(
keyword: &str,
description: &str,
cpes: &[String],
) -> ConfidenceLevel {
let exact_matches = cpes.iter().filter(|cpe| cpe.contains(keyword)).count();
let description_matches = description.to_lowercase().matches(keyword).count();
if exact_matches > 0 || description_matches > 2 {
ConfidenceLevel::High
} else if description_matches > 0 {
ConfidenceLevel::Medium
} else {
ConfidenceLevel::Low
}
}
fn map_severity_level(
enterprise_severity: &crate::enterprise::types::SeverityLevel,
) -> SeverityLevel {
match enterprise_severity {
crate::enterprise::types::SeverityLevel::Critical => SeverityLevel::Critical,
crate::enterprise::types::SeverityLevel::High => SeverityLevel::High,
crate::enterprise::types::SeverityLevel::Medium => SeverityLevel::Medium,
crate::enterprise::types::SeverityLevel::Low => SeverityLevel::Low,
}
}
fn map_confidence_level(
enterprise_confidence: &crate::enterprise::types::ConfidenceLevel,
) -> ConfidenceLevel {
match enterprise_confidence {
crate::enterprise::types::ConfidenceLevel::High => ConfidenceLevel::High,
crate::enterprise::types::ConfidenceLevel::Medium => ConfidenceLevel::Medium,
crate::enterprise::types::ConfidenceLevel::Low => ConfidenceLevel::Low,
crate::enterprise::types::ConfidenceLevel::Critical => ConfidenceLevel::High, }
}
fn get_cve_cache_path() -> Result<PathBuf> {
let home = home_dir().ok_or_else(|| anyhow::anyhow!("Could not find home directory"))?;
let nabla_dir = home.join(".nabla");
if !nabla_dir.exists() {
std::fs::create_dir_all(&nabla_dir)?;
}
Ok(nabla_dir.join("cve_cache.json"))
}
pub fn load_cve_db() -> Result<Vec<CveEntry>> {
let cache_path = get_cve_cache_path()?;
if cache_path.exists() {
if let Ok(file) = File::open(&cache_path) {
let reader = BufReader::new(file);
if let Ok(v) = serde_json::from_reader::<_, Value>(reader) {
tracing::info!("Loading CVE database from cache: {}", cache_path.display());
return parse_cve_json(v);
}
}
}
tracing::info!("Downloading CVE database from NVD (this may take a moment)...");
download_and_cache_cve_db(cache_path)
}
fn download_and_cache_cve_db(cache_path: PathBuf) -> Result<Vec<CveEntry>> {
tracing::info!(
"Downloading complete CVE database from NVD bulk feed (this may take a few minutes)..."
);
let response = ureq::get(CVE_BULK_DATA_URL)
.call()
.map_err(|e| anyhow::anyhow!("Failed to download CVE bulk data: {}", e))?;
let mut gz_decoder = GzDecoder::new(response.into_reader());
let v: Value = serde_json::from_reader(&mut gz_decoder)
.map_err(|e| anyhow::anyhow!("Failed to parse compressed CVE JSON: {}", e))?;
if let Ok(file) = std::fs::File::create(&cache_path) {
let _ = serde_json::to_writer(file, &v);
tracing::info!("Cached CVE database to: {}", cache_path.display());
}
parse_cve_json(v)
}
fn parse_cve_json(v: Value) -> Result<Vec<CveEntry>> {
let mut entries = Vec::new();
let items = if let Some(items) = v.get("CVE_Items").and_then(|x| x.as_array()) {
items
} else if let Some(items) = v.get("vulnerabilities").and_then(|x| x.as_array()) {
items
} else {
return Ok(entries);
};
for item in items {
let (id, description) = if let Some(cve) = item.get("cve") {
let id = cve
.get("id")
.and_then(|i| i.as_str())
.unwrap_or("")
.to_string();
let description = cve
.get("descriptions")
.and_then(|arr| arr.as_array())
.and_then(|arr| {
arr.iter()
.find(|d| d.get("lang").and_then(|l| l.as_str()) == Some("en"))
})
.and_then(|d| d.get("value"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
(id, description)
} else {
let id = item
.get("cve")
.and_then(|c| c.get("CVE_data_meta"))
.and_then(|m| m.get("ID"))
.and_then(|i| i.as_str())
.unwrap_or("")
.to_string();
let description = item
.get("cve")
.and_then(|c| c.get("description"))
.and_then(|d| d.get("description_data"))
.and_then(|arr| arr.as_array())
.and_then(|arr| arr.first())
.and_then(|d| d.get("value"))
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
(id, description)
};
let mut cpes = Vec::new();
if let Some(configs) = item.get("configurations").and_then(|c| c.get("nodes")) {
collect_cpes(configs, &mut cpes);
}
let severity = extract_severity(&item);
entries.push(CveEntry {
id,
description,
cpes,
severity,
});
}
Ok(entries)
}
fn extract_severity(item: &Value) -> Option<String> {
item.get("impact")
.and_then(|impact| impact.get("baseMetricV3"))
.and_then(|metric| metric.get("cvssV3"))
.and_then(|cvss| cvss.get("baseSeverity"))
.and_then(|severity| severity.as_str())
.map(|s| s.to_string())
.or_else(|| {
item.get("impact")
.and_then(|impact| impact.get("baseMetricV2"))
.and_then(|metric| metric.get("severity"))
.and_then(|severity| severity.as_str())
.map(|s| s.to_string())
})
}
pub fn collect_cpes(value: &Value, out: &mut Vec<String>) {
match value {
Value::Array(arr) => {
for v in arr {
collect_cpes(v, out);
}
}
Value::Object(map) => {
if let Some(cpe_matches) = map.get("cpe_match") {
if let Some(arr) = cpe_matches.as_array() {
for cm in arr {
if let Some(uri) = cm.get("cpe23Uri").and_then(|u| u.as_str()) {
out.push(uri.to_lowercase());
}
}
}
}
if let Some(children) = map.get("children") {
collect_cpes(children, out);
}
}
_ => {}
}
}
fn extract_library_keywords_from_strings(embedded_strings: &[String]) -> Vec<String> {
let mut keywords = Vec::new();
for string in embedded_strings {
let lower = string.to_lowercase();
let words: Vec<&str> = lower.split_whitespace().collect();
for word in &words {
if is_potential_library_name(word) {
keywords.push(word.to_string());
}
}
keywords.extend(extract_name_version_pairs(&lower));
keywords.extend(extract_component_names(&lower));
}
keywords.sort();
keywords.dedup();
keywords
}
fn is_potential_library_name(word: &str) -> bool {
if word.len() < 3 || word.chars().all(|c| c.is_numeric() || c == '.') {
return false;
}
let skip_words = [
"the", "and", "for", "with", "this", "that", "from", "into", "version", "server", "web",
"tool", "system",
];
if skip_words.contains(&word) {
return false;
}
word.chars().any(|c| c.is_alphabetic()) && word.len() <= 20
}
fn extract_name_version_pairs(s: &str) -> Vec<String> {
let mut pairs = Vec::new();
if let Ok(re) = Regex::new(r"([a-zA-Z][a-zA-Z0-9_-]*)\s+([0-9]+\.[0-9]+[a-zA-Z0-9.-]*)") {
for cap in re.captures_iter(s) {
if let (Some(name), Some(_version)) = (cap.get(1), cap.get(2)) {
pairs.push(name.as_str().to_lowercase());
}
}
}
if let Ok(re) = Regex::new(r"([a-zA-Z][a-zA-Z0-9_]*)-([0-9]+\.[0-9]+[a-zA-Z0-9.-]*)") {
for cap in re.captures_iter(s) {
if let (Some(name), Some(_version)) = (cap.get(1), cap.get(2)) {
pairs.push(name.as_str().to_lowercase());
}
}
}
pairs
}
fn extract_component_names(s: &str) -> Vec<String> {
let mut components = Vec::new();
for delimiter in [" ", "-", "_", "/", "\\", ":", ";"] {
for part in s.split(delimiter) {
let cleaned = part.trim_matches(|c: char| !c.is_alphanumeric());
if is_potential_library_name(cleaned) {
components.push(cleaned.to_lowercase());
}
}
}
components
}
#[allow(dead_code)]
pub fn scan_binary_vulnerabilities(analysis: &BinaryAnalysis) -> Vec<ScanResult> {
vec![scan_binary(analysis)]
}
#[allow(dead_code)]
pub fn enterprise_scan_binary_vulnerabilities(
analysis: &BinaryAnalysis,
) -> Vec<EnterpriseScanResult> {
vec![enterprise_scan_binary(analysis)]
}