11use std:: path:: Path ;
2- use log :: info ;
3-
2+ use std :: process :: Command ;
3+ use log :: { info , warn } ;
44use 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
913impl 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