Skip to content

Commit 99d3541

Browse files
Alex HolmbergAlex Holmberg
authored andcommitted
patch: fixed vulnerabilities output for different languages
1 parent fa2340b commit 99d3541

11 files changed

Lines changed: 1064 additions & 281 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ target/
55

66
# These are backup files generated by rustfmt
77
**/*.rs.bk
8+
.qoder
89

910
# MSVC Windows builds of rustc generate these, which store debugging information
1011
*.pdb

src/analyzer/runtime/detection.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,11 @@ impl RuntimeDetectionEngine {
1414
}
1515

1616
/// Get all available package managers in a project
17-
pub fn get_all_package_managers(project_path: &Path) -> Vec<String> {
17+
pub fn get_all_package_managers(project_path: &Path) -> Vec<super::javascript::PackageManager> {
1818
use super::javascript::RuntimeDetector;
1919

2020
let js_detector = RuntimeDetector::new(project_path.to_path_buf());
2121
js_detector.detect_all_package_managers()
22-
.into_iter()
23-
.map(|pm| pm.as_str().to_string())
24-
.collect()
2522
}
2623

2724
/// Check if a project uses a specific runtime

src/analyzer/security_analyzer.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1551,7 +1551,7 @@ impl SecurityAnalyzer {
15511551
}
15521552

15531553
// Additional helper methods...
1554-
fn collect_source_files(&self, project_root: &Path, language: &str) -> Result<Vec<PathBuf>, SecurityError> {
1554+
fn collect_source_files(&self, _project_root: &Path, _language: &str) -> Result<Vec<PathBuf>, SecurityError> {
15551555
// TODO: Implement source file collection based on language
15561556
Ok(vec![])
15571557
}
@@ -1874,7 +1874,7 @@ mod tests {
18741874
config.skip_gitignored_files = false;
18751875
config.downgrade_gitignored_severity = true;
18761876

1877-
let analyzer = SecurityAnalyzer::with_config(config).unwrap();
1877+
let _analyzer = SecurityAnalyzer::with_config(config).unwrap();
18781878
// Additional test logic could be added here for downgrade behavior
18791879
}
18801880

@@ -2027,7 +2027,7 @@ mod tests {
20272027

20282028
for pattern in &legitimate_patterns {
20292029
// These should either not match any secret pattern, or be filtered out by context detection
2030-
let matches_old_generic_pattern = pattern.to_lowercase().contains("secret") ||
2030+
let _matches_old_generic_pattern = pattern.to_lowercase().contains("secret") ||
20312031
pattern.to_lowercase().contains("key");
20322032

20332033
// Our new patterns should be more specific and not match env var access
Lines changed: 148 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,161 @@
11
use std::path::Path;
2-
use log::info;
3-
2+
use std::process::Command;
3+
use log::{info, warn};
44
use crate::analyzer::dependency_parser::DependencyInfo;
5-
use super::{LanguageVulnerabilityChecker, VulnerableDependency, VulnerabilityError};
5+
use crate::analyzer::tool_management::ToolDetector;
6+
use crate::analyzer::vulnerability::{VulnerableDependency, VulnerabilityError, VulnerabilityInfo, VulnerabilitySeverity};
7+
use super::MutableLanguageVulnerabilityChecker;
68

7-
pub struct GoVulnerabilityChecker;
9+
pub struct GoVulnerabilityChecker {
10+
tool_detector: ToolDetector,
11+
}
812

913
impl GoVulnerabilityChecker {
1014
pub fn new() -> Self {
11-
Self
15+
Self {
16+
tool_detector: ToolDetector::new(),
17+
}
18+
}
19+
20+
fn execute_govulncheck(
21+
&mut self,
22+
project_path: &Path,
23+
dependencies: &[DependencyInfo],
24+
) -> Result<Option<Vec<VulnerableDependency>>, VulnerabilityError> {
25+
// Check if govulncheck is available
26+
let govulncheck_status = self.tool_detector.detect_tool("govulncheck");
27+
if !govulncheck_status.available {
28+
warn!("govulncheck not found, skipping Go vulnerability check. Install with: go install golang.org/x/vuln/cmd/govulncheck@latest");
29+
return Ok(None);
30+
}
31+
32+
info!("Executing govulncheck in {}", project_path.display());
33+
34+
// Execute govulncheck -json
35+
let output = Command::new("govulncheck")
36+
.args(&["-json", "./..."])
37+
.current_dir(project_path)
38+
.output()
39+
.map_err(|e| VulnerabilityError::CommandError(
40+
format!("Failed to run govulncheck: {}", e)
41+
))?;
42+
43+
// govulncheck returns 0 even when vulnerabilities are found
44+
// Non-zero exit code indicates an actual error
45+
if !output.status.success() && output.stdout.is_empty() {
46+
return Err(VulnerabilityError::CommandError(
47+
format!("govulncheck failed with exit code {}: {}",
48+
output.status.code().unwrap_or(-1),
49+
String::from_utf8_lossy(&output.stderr))
50+
));
51+
}
52+
53+
if output.stdout.is_empty() {
54+
return Ok(None);
55+
}
56+
57+
// Parse govulncheck output
58+
self.parse_govulncheck_output(&output.stdout, dependencies)
59+
}
60+
61+
fn parse_govulncheck_output(
62+
&self,
63+
output: &[u8],
64+
dependencies: &[DependencyInfo],
65+
) -> Result<Option<Vec<VulnerableDependency>>, VulnerabilityError> {
66+
let mut vulnerable_deps: Vec<VulnerableDependency> = Vec::new();
67+
68+
// Split output by lines and parse each JSON object
69+
let output_str = String::from_utf8_lossy(output);
70+
for line in output_str.lines() {
71+
if line.trim().is_empty() {
72+
continue;
73+
}
74+
75+
let audit_data: serde_json::Value = serde_json::from_str(line)
76+
.map_err(|e| VulnerabilityError::ParseError(
77+
format!("Failed to parse govulncheck output line: {}", e)
78+
))?;
79+
80+
// Govulncheck JSON structure parsing
81+
if audit_data.get("finding").is_some() {
82+
if let Some(finding) = audit_data.get("finding").and_then(|f| f.as_object()) {
83+
let package_name = finding.get("package").and_then(|p| p.as_str())
84+
.unwrap_or("").to_string();
85+
let module = finding.get("module").and_then(|m| m.as_str())
86+
.unwrap_or("").to_string();
87+
88+
// Find matching dependency
89+
if let Some(dep) = dependencies.iter().find(|d|
90+
d.name == package_name || d.name == module ||
91+
package_name.starts_with(&format!("{}/", d.name)) ||
92+
module.starts_with(&format!("{}/", d.name))) {
93+
94+
let vuln_id = finding.get("osv").and_then(|o| o.as_str())
95+
.unwrap_or("unknown").to_string();
96+
let title = finding.get("summary").and_then(|s| s.as_str())
97+
.unwrap_or("Unknown vulnerability").to_string();
98+
let description = finding.get("details").and_then(|d| d.as_str())
99+
.unwrap_or("").to_string();
100+
let severity = VulnerabilitySeverity::Medium; // Govulncheck doesn't provide severity directly
101+
let fixed_version = finding.get("fixed_version").and_then(|v| v.as_str())
102+
.map(|s| s.to_string());
103+
104+
let vuln_info = VulnerabilityInfo {
105+
id: vuln_id,
106+
vuln_type: "security".to_string(), // Security vulnerability
107+
severity,
108+
title,
109+
description,
110+
cve: None, // Govulncheck uses OSV IDs
111+
ghsa: None, // Govulncheck uses OSV IDs
112+
affected_versions: "*".to_string(), // Govulncheck doesn't provide this directly
113+
patched_versions: fixed_version,
114+
published_date: None,
115+
references: Vec::new(), // Govulncheck doesn't provide references in this format
116+
};
117+
118+
// Check if we already have this dependency
119+
if let Some(existing) = vulnerable_deps.iter_mut()
120+
.find(|vuln_dep| vuln_dep.name == dep.name)
121+
{
122+
// Avoid duplicate vulnerabilities
123+
if !existing.vulnerabilities.iter().any(|v| v.id == vuln_info.id) {
124+
existing.vulnerabilities.push(vuln_info);
125+
}
126+
} else {
127+
vulnerable_deps.push(VulnerableDependency {
128+
name: dep.name.clone(),
129+
version: dep.version.clone(),
130+
language: crate::analyzer::dependency_parser::Language::Go,
131+
vulnerabilities: vec![vuln_info],
132+
});
133+
}
134+
}
135+
}
136+
}
137+
}
138+
139+
if vulnerable_deps.is_empty() {
140+
Ok(None)
141+
} else {
142+
Ok(Some(vulnerable_deps))
143+
}
12144
}
13145
}
14146

15-
impl LanguageVulnerabilityChecker for GoVulnerabilityChecker {
147+
impl MutableLanguageVulnerabilityChecker for GoVulnerabilityChecker {
16148
fn check_vulnerabilities(
17-
&self,
18-
_dependencies: &[DependencyInfo],
19-
_project_path: &Path,
149+
&mut self,
150+
dependencies: &[DependencyInfo],
151+
project_path: &Path,
20152
) -> Result<Vec<VulnerableDependency>, VulnerabilityError> {
21-
info!("Go vulnerability checking - implementation placeholder");
22-
Ok(vec![])
153+
info!("Checking Go dependencies");
154+
155+
match self.execute_govulncheck(project_path, dependencies) {
156+
Ok(Some(vulns)) => Ok(vulns),
157+
Ok(None) => Ok(vec![]),
158+
Err(e) => Err(e),
159+
}
23160
}
24161
}

0 commit comments

Comments
 (0)