@@ -176,27 +176,42 @@ def run_npm_audit(self, package_dir: Path) -> Optional[Dict]:
176176 logger .error (f"Error running npm audit in { package_dir } : { e } " )
177177 return None
178178
179- def parse_audit_results (self , audit_data : Dict , package_dir : Path , vulnerability_class ) -> List :
180- """Parse npm audit JSON output into Vulnerability objects"""
179+ def parse_audit_results (
180+ self , audit_data : Dict , package_dir : Path , vulnerability_class
181+ ) -> Optional [List ]:
182+ """Parse npm audit JSON output, returning None when it is incomplete or invalid."""
181183 vulnerabilities = []
182184
183185 # Extract main dependency information from package_dir path
184186 main_dep_name = package_dir .name # Get the directory name as main dependency
185187 main_dep_path = str (package_dir .relative_to (self .repo_path )) # Relative path from repo root
186188
187189 try :
190+ if not isinstance (audit_data , dict ):
191+ raise ValueError ("npm audit JSON root is not an object" )
192+
188193 # npm audit v7+ format
189194 if "vulnerabilities" in audit_data :
190- for vuln_name , vuln_data in audit_data ["vulnerabilities" ].items ():
195+ audit_vulnerabilities = audit_data ["vulnerabilities" ]
196+ if not isinstance (audit_vulnerabilities , dict ):
197+ raise ValueError ("npm audit vulnerabilities field is not an object" )
198+
199+ for vuln_name , vuln_data in audit_vulnerabilities .items ():
191200 try :
201+ if not isinstance (vuln_data , dict ):
202+ raise ValueError ("vulnerability entry is not an object" )
203+
192204 # Extract vulnerability information
193205 severity = vuln_data .get ("severity" , "unknown" )
194206 via = vuln_data .get ("via" , [])
195207 fix_available = vuln_data .get ("fixAvailable" , False )
196208 range_info = vuln_data .get ("range" , "unknown" )
197209
198210 # Handle different via formats - create separate vulnerabilities for each advisory
199- if isinstance (via , list ) and via :
211+ if not isinstance (via , list ):
212+ raise ValueError ("vulnerability via field is not a list" )
213+
214+ if via :
200215 for via_item in via :
201216 if isinstance (via_item , dict ):
202217 # Extract individual advisory information
@@ -231,7 +246,7 @@ def parse_audit_results(self, audit_data: Dict, package_dir: Path, vulnerability
231246 main_dep_path = main_dep_path
232247 )
233248 vulnerabilities .append (vulnerability )
234- else :
249+ elif isinstance ( via_item , str ) :
235250 # Handle string via items (legacy format)
236251 vuln_id = str (via_item )
237252 url = f"https://github.com/advisories?query={ vuln_id } "
@@ -249,6 +264,8 @@ def parse_audit_results(self, audit_data: Dict, package_dir: Path, vulnerability
249264 main_dep_path = main_dep_path
250265 )
251266 vulnerabilities .append (vulnerability )
267+ else :
268+ raise ValueError ("vulnerability via item is not an object or string" )
252269 else :
253270 # No via information, create basic vulnerability
254271 vuln_id = f"npm-{ vuln_name } "
@@ -270,12 +287,19 @@ def parse_audit_results(self, audit_data: Dict, package_dir: Path, vulnerability
270287
271288 except Exception as e :
272289 logger .error (f"Error parsing vulnerability { vuln_name } : { e } " )
273- continue
290+ return None
274291
275292 # Legacy npm audit format (fallback)
276293 elif "advisories" in audit_data :
277- for advisory_id , advisory in audit_data ["advisories" ].items ():
294+ audit_advisories = audit_data ["advisories" ]
295+ if not isinstance (audit_advisories , dict ):
296+ raise ValueError ("npm audit advisories field is not an object" )
297+
298+ for advisory_id , advisory in audit_advisories .items ():
278299 try :
300+ if not isinstance (advisory , dict ):
301+ raise ValueError ("advisory entry is not an object" )
302+
279303 vulnerability = vulnerability_class (
280304 id = f"npm-{ advisory_id } " ,
281305 url = advisory .get ("url" , f"https://npmjs.com/advisories/{ advisory_id } " ),
@@ -291,14 +315,16 @@ def parse_audit_results(self, audit_data: Dict, package_dir: Path, vulnerability
291315 vulnerabilities .append (vulnerability )
292316 except Exception as e :
293317 logger .error (f"Error parsing advisory { advisory_id } : { e } " )
294- continue
318+ return None
319+ else :
320+ raise ValueError ("unrecognized npm audit JSON format" )
295321
296322 logger .info (f"Parsed { len (vulnerabilities )} vulnerabilities from { package_dir } " )
297323 return vulnerabilities
298324
299325 except Exception as e :
300326 logger .error (f"Error parsing audit results from { package_dir } : { e } " )
301- return []
327+ return None
302328
303329 def check_npm_vulnerabilities (self , vulnerability_class ) -> List :
304330 """Main method to check npm vulnerabilities across all package.json files"""
@@ -329,6 +355,10 @@ def check_npm_vulnerabilities(self, vulnerability_class) -> List:
329355
330356 # Parse vulnerabilities
331357 vulnerabilities = self .parse_audit_results (audit_data , package_dir , vulnerability_class )
358+ if vulnerabilities is None :
359+ logger .warning (f"Skipping vulnerability parsing for { package_dir } due to parse failure" )
360+ self .failed_packages .append (f"{ package_dir } : npm audit parse failed" )
361+ continue
332362 all_vulnerabilities .extend (vulnerabilities )
333363
334364 except Exception as e :
0 commit comments