@@ -37,78 +37,58 @@ def find_line_in_file(packagename: str, packageversion: str, manifest_file: str)
3737 """
3838 Finds the line number and snippet of code for the given package/version in a manifest file.
3939 Returns a 2-tuple: (line_number, snippet_or_message).
40-
41- Supports:
42- 1) JSON-based manifest files (package-lock.json, Pipfile.lock, composer.lock)
43- - Locates a dictionary entry with the matching package & version
44- - Does a rough line-based search to find the actual line in the raw text
45- 2) Text-based (requirements.txt, package.json, yarn.lock, etc.)
46- - Uses compiled regex patterns to detect a match line by line
4740 """
48- # Extract just the file name to detect manifest type
4941 file_type = Path (manifest_file ).name
50-
42+ logging .debug ("Processing manifest file: %s" , manifest_file )
43+
5144 # ----------------------------------------------------
5245 # 1) JSON-based manifest files
5346 # ----------------------------------------------------
5447 if file_type in ["package-lock.json" , "Pipfile.lock" , "composer.lock" ]:
5548 try :
56- # Read entire file so we can parse JSON and also do raw line checks
5749 with open (manifest_file , "r" , encoding = "utf-8" ) as f :
5850 raw_text = f .read ()
51+ logging .debug ("Raw text length: %d" , len (raw_text ))
52+ try :
53+ data = json .loads (raw_text )
54+ except json .JSONDecodeError :
55+ data = {}
56+ logging .debug ("JSON decode failed for %s" , manifest_file )
5957
60- # Attempt JSON parse
61- data = json .loads (raw_text )
62-
63- # In practice, you may need to check data["dependencies"], data["default"], etc.
64- # This is an example approach.
6558 packages_dict = (
6659 data .get ("packages" )
6760 or data .get ("default" )
6861 or data .get ("dependencies" )
6962 or {}
7063 )
71-
64+ logging . debug ( "Packages dict keys: %s" , list ( packages_dict . keys ()))
7265 found_key = None
7366 found_info = None
74- # Locate a dictionary entry whose 'version' matches
7567 for key , value in packages_dict .items ():
76- # For NPM package-lock, keys might look like "node_modules/axios"
7768 if key .endswith (packagename ) and "version" in value :
7869 if value ["version" ] == packageversion :
7970 found_key = key
8071 found_info = value
8172 break
8273
8374 if found_key and found_info :
84- # Search lines to approximate the correct line number
85- needle_key = f'"{ found_key } ":' # e.g. "node_modules/axios":
75+ needle_key = f'"{ found_key } ":'
8676 needle_version = f'"version": "{ packageversion } "'
8777 lines = raw_text .splitlines ()
88- best_line = 1
89- snippet = None
90-
78+ logging .debug ("Total lines: %d" , len (lines ))
9179 for i , line in enumerate (lines , start = 1 ):
9280 if (needle_key in line ) or (needle_version in line ):
93- best_line = i
94- snippet = line .strip ()
95- break # On first match, stop
96-
97- # If we found an approximate line, return it; else fallback to line 1
98- if best_line > 0 and snippet :
99- return best_line , snippet
100- else :
101- return 1 , f'"{ found_key } ": { found_info } '
81+ logging .debug ("Found match at line %d: %s" , i , line .strip ())
82+ return i , line .strip ()
83+ return 1 , f'"{ found_key } ": { found_info } '
10284 else :
10385 return 1 , f"{ packagename } { packageversion } (not found in { manifest_file } )"
104-
10586 except (FileNotFoundError , json .JSONDecodeError ):
10687 return 1 , f"Error reading { manifest_file } "
107-
88+
10889 # ----------------------------------------------------
10990 # 2) Text-based / line-based manifests
11091 # ----------------------------------------------------
111- # Define a dictionary of patterns for common manifest types
11292 search_patterns = {
11393 "package.json" : rf'"{ packagename } ":\s*"{ packageversion } "' ,
11494 "yarn.lock" : rf'{ packagename } @{ packageversion } ' ,
@@ -134,18 +114,16 @@ def find_line_in_file(packagename: str, packageversion: str, manifest_file: str)
134114 "conanfile.txt" : rf'{ re .escape (packagename )} /{ re .escape (packageversion )} ' ,
135115 "vcpkg.json" : rf'"{ re .escape (packagename )} ":\s*"{ re .escape (packageversion )} "' ,
136116 }
137-
138- # If no specific pattern is found for this file name, fallback to a naive approach
139117 searchstring = search_patterns .get (file_type , rf'{ re .escape (packagename )} .*{ re .escape (packageversion )} ' )
118+ logging .debug ("Using search pattern for %s: %s" , file_type , searchstring )
140119 try :
141- # Read file lines and search for a match
142120 with open (manifest_file , 'r' , encoding = "utf-8" ) as file :
143121 lines = [line .rstrip ("\n " ) for line in file ]
122+ logging .debug ("Total lines in %s: %d" , manifest_file , len (lines ))
144123 for line_number , line_content in enumerate (lines , start = 1 ):
145- # For Python conditional dependencies, ignore everything after first ';'
146124 line_main = line_content .split (";" , 1 )[0 ].strip ()
147- # Use a case-insensitive regex search
148125 if re .search (searchstring , line_main , re .IGNORECASE ):
126+ logging .debug ("Match found in %s at line %d: %s" , manifest_file , line_number , line_content .strip ())
149127 return line_number , line_content .strip ()
150128 except FileNotFoundError :
151129 return 1 , f"{ manifest_file } not found"
@@ -180,15 +158,20 @@ def get_manifest_type_url(manifest_file: str, pkg_name: str, pkg_version: str) -
180158 "composer.json" : "composer" ,
181159 "vcpkg.json" : "vcpkg" ,
182160 }
183-
184161 file_type = Path (manifest_file ).name
185162 url_prefix = manifest_to_url_prefix .get (file_type , "unknown" )
186163 return f"https://socket.dev/{ url_prefix } /package/{ pkg_name } /alerts/{ pkg_version } "
187164
188165 @staticmethod
189166 def create_security_comment_sarif (diff ) -> dict :
190167 """
191- Create SARIF-compliant output from the diff report.
168+ Create SARIF-compliant output from the diff report, including dynamic URL generation
169+ based on manifest type and improved <br/> formatting for GitHub SARIF display.
170+
171+ This function now:
172+ - Accepts multiple manifest files from alert.introduced_by or alert.manifests.
173+ - Generates one SARIF location per manifest file.
174+ - Falls back to a default ("requirements.txt") if none is found.
192175 """
193176 if len (diff .new_alerts ) == 0 :
194177 for alert in diff .new_alerts :
@@ -219,7 +202,7 @@ def create_security_comment_sarif(diff) -> dict:
219202 rule_id = f"{ pkg_name } =={ pkg_version } "
220203 severity = alert .severity
221204
222- # --- Extract manifest files ---
205+ # --- Extract manifest files from alert data ---
223206 manifest_files = []
224207 if alert .introduced_by and isinstance (alert .introduced_by , list ):
225208 for entry in alert .introduced_by :
@@ -230,16 +213,12 @@ def create_security_comment_sarif(diff) -> dict:
230213 elif hasattr (alert , 'manifests' ) and alert .manifests :
231214 manifest_files = [mf .strip () for mf in alert .manifests .split (";" ) if mf .strip ()]
232215
233- # Log the extracted manifest files
234216 logging .debug ("Alert %s manifest_files before fallback: %s" , rule_id , manifest_files )
235-
236217 if not manifest_files :
237218 manifest_files = ["requirements.txt" ]
238219 logging .debug ("Alert %s: Falling back to manifest_files: %s" , rule_id , manifest_files )
239220
240- # Log the manifest file used for URL generation
241221 logging .debug ("Alert %s: Using manifest_file for URL: %s" , rule_id , manifest_files [0 ])
242-
243222 socket_url = Messages .get_manifest_type_url (manifest_files [0 ], pkg_name , pkg_version )
244223 short_desc = (f"{ alert .props .get ('note' , '' )} <br/><br/>Suggested Action:<br/>{ alert .suggestion } "
245224 f"<br/><a href=\" { socket_url } \" >{ socket_url } </a>" )
@@ -257,13 +236,13 @@ def create_security_comment_sarif(diff) -> dict:
257236 },
258237 }
259238
260- # Create a SARIF location for each manifest file and log each result .
239+ # Create a SARIF location for each manifest file.
261240 locations = []
262241 for mf in manifest_files :
263242 line_number , line_content = Messages .find_line_in_file (pkg_name , pkg_version , mf )
264243 if line_number < 1 :
265244 line_number = 1
266- logging .debug ("Alert %s: Manifest %s, line %s : %s" , rule_id , mf , line_number , line_content )
245+ logging .debug ("Alert %s: Manifest %s, line %d : %s" , rule_id , mf , line_number , line_content )
267246 locations .append ({
268247 "physicalLocation" : {
269248 "artifactLocation" : {"uri" : mf },
@@ -285,7 +264,7 @@ def create_security_comment_sarif(diff) -> dict:
285264 sarif_data ["runs" ][0 ]["results" ] = results_list
286265
287266 return sarif_data
288-
267+
289268 @staticmethod
290269 def create_security_comment_json (diff : Diff ) -> dict :
291270 scan_failed = False
0 commit comments