@@ -31,15 +31,38 @@ impl GoVulnerabilityChecker {
3131
3232 info ! ( "Executing govulncheck in {}" , project_path. display( ) ) ;
3333
34- // Execute govulncheck -json
35- let output = Command :: new ( "govulncheck" )
34+ // Execute govulncheck using the full path if available
35+ let mut command = if let Some ( exec_path) = & govulncheck_status. execution_path {
36+ // Use the full path when tool is not in PATH
37+ Command :: new ( exec_path)
38+ } else {
39+ // Use tool name directly when in PATH
40+ Command :: new ( "govulncheck" )
41+ } ;
42+
43+ let output = command
3644 . args ( & [ "-json" , "./..." ] )
3745 . current_dir ( project_path)
3846 . output ( )
3947 . map_err ( |e| VulnerabilityError :: CommandError (
4048 format ! ( "Failed to run govulncheck: {}" , e)
4149 ) ) ?;
4250
51+ // Log debug information about the command output
52+ info ! ( "govulncheck stdout length: {}, stderr length: {}" ,
53+ output. stdout. len( ) , output. stderr. len( ) ) ;
54+ info ! ( "govulncheck exit code: {:?}" , output. status. code( ) ) ;
55+
56+ if !output. stderr . is_empty ( ) {
57+ let stderr_str = String :: from_utf8_lossy ( & output. stderr ) ;
58+ info ! ( "govulncheck stderr: {}" , stderr_str) ;
59+ }
60+
61+ // Log first few lines of stdout for debugging
62+ let stdout_str = String :: from_utf8_lossy ( & output. stdout ) ;
63+ let stdout_lines: Vec < & str > = stdout_str. lines ( ) . take ( 20 ) . collect ( ) ;
64+ info ! ( "govulncheck stdout first 20 lines: {:?}" , stdout_lines) ;
65+
4366 // govulncheck returns 0 even when vulnerabilities are found
4467 // Non-zero exit code indicates an actual error
4568 if !output. status . success ( ) && output. stdout . is_empty ( ) {
@@ -50,11 +73,12 @@ impl GoVulnerabilityChecker {
5073 ) ) ;
5174 }
5275
76+ // Parse govulncheck output
5377 if output. stdout . is_empty ( ) {
78+ info ! ( "govulncheck returned empty output, no vulnerabilities found" ) ;
5479 return Ok ( None ) ;
5580 }
5681
57- // Parse govulncheck output
5882 self . parse_govulncheck_output ( & output. stdout , dependencies)
5983 }
6084
@@ -65,73 +89,98 @@ impl GoVulnerabilityChecker {
6589 ) -> Result < Option < Vec < VulnerableDependency > > , VulnerabilityError > {
6690 let mut vulnerable_deps: Vec < VulnerableDependency > = Vec :: new ( ) ;
6791
68- // Split output by lines and parse each JSON object
92+ // Convert output to string
6993 let output_str = String :: from_utf8_lossy ( output) ;
70- for line in output_str. lines ( ) {
71- if line. trim ( ) . is_empty ( ) {
94+
95+ // Check if output is empty or only whitespace
96+ if output_str. trim ( ) . is_empty ( ) {
97+ info ! ( "govulncheck output is empty, no vulnerabilities found" ) ;
98+ return Ok ( None ) ;
99+ }
100+
101+ // Govulncheck outputs a stream of JSON objects separated by newlines
102+ // Process each line and only parse lines that look like complete JSON objects
103+ for ( line_num, line) in output_str. lines ( ) . enumerate ( ) {
104+ let trimmed_line = line. trim ( ) ;
105+ if trimmed_line. is_empty ( ) {
72106 continue ;
73107 }
74108
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- ) ) ? ;
109+ // Only try to parse lines that look like JSON objects (start with { and end with } )
110+ if !trimmed_line . starts_with ( '{' ) || !trimmed_line . ends_with ( '}' ) {
111+ continue ;
112+ }
79113
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) ;
114+ // Try to parse as JSON, but handle errors gracefully
115+ match serde_json:: from_str :: < serde_json:: Value > ( trimmed_line) {
116+ Ok ( audit_data) => {
117+ // Govulncheck JSON structure parsing
118+ if audit_data. get ( "finding" ) . is_some ( ) {
119+ if let Some ( finding) = audit_data. get ( "finding" ) . and_then ( |f| f. as_object ( ) ) {
120+ let package_name = finding. get ( "package" ) . and_then ( |p| p. as_str ( ) )
121+ . unwrap_or ( "" ) . to_string ( ) ;
122+ let module = finding. get ( "module" ) . and_then ( |m| m. as_str ( ) )
123+ . unwrap_or ( "" ) . to_string ( ) ;
124+
125+ // Find matching dependency
126+ if let Some ( dep) = dependencies. iter ( ) . find ( |d|
127+ d. name == package_name || d. name == module ||
128+ package_name. starts_with ( & format ! ( "{}/" , d. name) ) ||
129+ module. starts_with ( & format ! ( "{}/" , d. name) ) ) {
130+
131+ let vuln_id = finding. get ( "osv" ) . and_then ( |o| o. as_str ( ) )
132+ . unwrap_or ( "unknown" ) . to_string ( ) ;
133+ let title = finding. get ( "summary" ) . and_then ( |s| s. as_str ( ) )
134+ . unwrap_or ( "Unknown vulnerability" ) . to_string ( ) ;
135+ let description = finding. get ( "details" ) . and_then ( |d| d. as_str ( ) )
136+ . unwrap_or ( "" ) . to_string ( ) ;
137+ let severity = VulnerabilitySeverity :: Medium ; // Govulncheck doesn't provide severity directly
138+ let fixed_version = finding. get ( "fixed_version" ) . and_then ( |v| v. as_str ( ) )
139+ . map ( |s| s. to_string ( ) ) ;
140+
141+ let vuln_info = VulnerabilityInfo {
142+ id : vuln_id,
143+ vuln_type : "security" . to_string ( ) , // Security vulnerability
144+ severity,
145+ title,
146+ description,
147+ cve : None , // Govulncheck uses OSV IDs
148+ ghsa : None , // Govulncheck uses OSV IDs
149+ affected_versions : "*" . to_string ( ) , // Govulncheck doesn't provide this directly
150+ patched_versions : fixed_version,
151+ published_date : None ,
152+ references : Vec :: new ( ) , // Govulncheck doesn't provide references in this format
153+ } ;
154+
155+ // Check if we already have this dependency
156+ if let Some ( existing) = vulnerable_deps. iter_mut ( )
157+ . find ( |vuln_dep| vuln_dep. name == dep. name )
158+ {
159+ // Avoid duplicate vulnerabilities
160+ if !existing. vulnerabilities . iter ( ) . any ( |v| v. id == vuln_info. id ) {
161+ existing. vulnerabilities . push ( vuln_info) ;
162+ }
163+ } else {
164+ vulnerable_deps. push ( VulnerableDependency {
165+ name : dep. name . clone ( ) ,
166+ version : dep. version . clone ( ) ,
167+ language : crate :: analyzer:: dependency_parser:: Language :: Go ,
168+ vulnerabilities : vec ! [ vuln_info] ,
169+ } ) ;
170+ }
125171 }
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- } ) ;
133172 }
134173 }
174+ } ,
175+ Err ( e) => {
176+ // Log the error but continue processing other lines
177+ // Only log detailed errors for lines that look like they should be valid JSON
178+ if trimmed_line. starts_with ( '{' ) && trimmed_line. ends_with ( '}' ) {
179+ warn ! ( "Failed to parse govulncheck output line {}: {}. Line content: {}" ,
180+ line_num + 1 , e, trimmed_line) ;
181+ }
182+ // Continue with next line instead of failing completely
183+ continue ;
135184 }
136185 }
137186 }
0 commit comments