@@ -41,16 +41,13 @@ def find_line_in_file(packagename: str, packageversion: str, manifest_file: str)
4141 Supports:
4242 1) JSON-based manifest files (package-lock.json, Pipfile.lock, composer.lock)
4343 - Locates a dictionary entry with the matching package & version
44- - Does a rough line-based search (by matching the key) in the raw text
44+ - Searches the raw text for the key
4545 2) Text-based (requirements.txt, package.json, yarn.lock, etc.)
46- - Uses compiled regex patterns to detect a match line by line
46+ - Uses regex patterns to detect a match line by line
4747 """
4848 file_type = Path (manifest_file ).name
4949 logging .debug ("Processing file for line lookup: %s" , manifest_file )
5050
51- # ----------------------------------------------------
52- # 1) JSON-based manifest files
53- # ----------------------------------------------------
5451 if file_type in ["package-lock.json" , "Pipfile.lock" , "composer.lock" ]:
5552 try :
5653 with open (manifest_file , "r" , encoding = "utf-8" ) as f :
@@ -67,21 +64,19 @@ def find_line_in_file(packagename: str, packageversion: str, manifest_file: str)
6764 found_key = None
6865 found_info = None
6966 for key , value in packages_dict .items ():
70- # For NPM package-lock, keys might look like "node_modules/axios"
7167 if key .endswith (packagename ) and "version" in value :
7268 if value ["version" ] == packageversion :
7369 found_key = key
7470 found_info = value
7571 break
7672
7773 if found_key and found_info :
78- # Only use the found key to locate the line
7974 needle_key = f'"{ found_key } ":'
8075 lines = raw_text .splitlines ()
8176 logging .debug ("Total lines in %s: %d" , manifest_file , len (lines ))
8277 for i , line in enumerate (lines , start = 1 ):
8378 if needle_key in line :
84- logging .debug ("Match found at line %d in %s: %s" , i , manifest_file , line .strip ())
79+ logging .debug ("Found match at line %d in %s: %s" , i , manifest_file , line .strip ())
8580 return i , line .strip ()
8681 return 1 , f'"{ found_key } ": { found_info } '
8782 else :
@@ -90,9 +85,7 @@ def find_line_in_file(packagename: str, packageversion: str, manifest_file: str)
9085 logging .error ("Error reading %s: %s" , manifest_file , e )
9186 return 1 , f"Error reading { manifest_file } "
9287
93- # ----------------------------------------------------
94- # 2) Text-based / line-based manifests
95- # ----------------------------------------------------
88+ # Text-based manifests
9689 search_patterns = {
9790 "package.json" : rf'"{ packagename } ":\s*"[\^~]?{ re .escape (packageversion )} "' ,
9891 "yarn.lock" : rf'{ packagename } @{ packageversion } ' ,
@@ -174,7 +167,8 @@ def create_security_comment_sarif(diff) -> dict:
174167
175168 This function now:
176169 - Accepts multiple manifest files from alert.introduced_by or alert.manifests.
177- - Generates one SARIF location per manifest file.
170+ - Generates an individual SARIF result for each manifest file.
171+ - Appends the manifest file name to the alert name to make each result unique.
178172 - Does NOT fall back to 'requirements.txt' if no manifest file is provided.
179173 - Adds detailed logging to validate our assumptions.
180174 """
@@ -204,11 +198,11 @@ def create_security_comment_sarif(diff) -> dict:
204198 for alert in diff .new_alerts :
205199 pkg_name = alert .pkg_name
206200 pkg_version = alert .pkg_version
207- rule_id = f"{ pkg_name } =={ pkg_version } "
201+ base_rule_id = f"{ pkg_name } =={ pkg_version } "
208202 severity = alert .severity
209203
210204 # Log raw alert data for manifest extraction.
211- logging .debug ("Alert %s - introduced_by: %s, manifests: %s" , rule_id , alert .introduced_by , getattr (alert , 'manifests' , None ))
205+ logging .debug ("Alert %s - introduced_by: %s, manifests: %s" , base_rule_id , alert .introduced_by , getattr (alert , 'manifests' , None ))
212206
213207 manifest_files = []
214208 if alert .introduced_by and isinstance (alert .introduced_by , list ):
@@ -221,56 +215,57 @@ def create_security_comment_sarif(diff) -> dict:
221215 elif hasattr (alert , 'manifests' ) and alert .manifests :
222216 manifest_files = [mf .strip () for mf in alert .manifests .split (";" ) if mf .strip ()]
223217
224- logging .debug ("Alert %s - extracted manifest_files: %s" , rule_id , manifest_files )
225-
218+ logging .debug ("Alert %s - extracted manifest_files: %s" , base_rule_id , manifest_files )
226219 if not manifest_files :
227- logging .error ("Alert %s: No manifest file found; cannot determine file location." , rule_id )
220+ logging .error ("Alert %s: No manifest file found; cannot determine file location." , base_rule_id )
228221 continue
229222
230- logging .debug ("Alert %s - using manifest_files for processing: %s" , rule_id , manifest_files )
231- # Use the first manifest for URL generation.
232- logging .debug ("Alert %s - Using file for URL generation: %s" , rule_id , manifest_files [0 ])
233- socket_url = Messages .get_manifest_type_url (manifest_files [0 ], pkg_name , pkg_version )
234- short_desc = (f"{ alert .props .get ('note' , '' )} <br/><br/>Suggested Action:<br/>{ alert .suggestion } "
235- f"<br/><a href=\" { socket_url } \" >{ socket_url } </a>" )
236- full_desc = "{} - {}" .format (alert .title , alert .description .replace ('\r \n ' , '<br/>' ))
237-
238- if rule_id not in rules_map :
239- rules_map [rule_id ] = {
240- "id" : rule_id ,
241- "name" : f"{ pkg_name } =={ pkg_version } " ,
242- "shortDescription" : {"text" : f"Alert generated for { rule_id } by Socket Security" },
243- "fullDescription" : {"text" : full_desc },
244- "helpUri" : socket_url ,
245- "defaultConfiguration" : {
246- "level" : Messages .map_severity_to_sarif (severity )
247- },
248- }
223+ logging .debug ("Alert %s - using manifest_files for processing: %s" , base_rule_id , manifest_files )
249224
250- # Create a SARIF location for each manifest file.
251- locations = []
225+ # For each manifest file, create an individual SARIF result.
252226 for mf in manifest_files :
253- logging .debug ("Alert %s - Processing manifest file: %s" , rule_id , mf )
227+ logging .debug ("Alert %s - Processing manifest file: %s" , base_rule_id , mf )
228+ socket_url = Messages .get_manifest_type_url (mf , pkg_name , pkg_version )
254229 line_number , line_content = Messages .find_line_in_file (pkg_name , pkg_version , mf )
255230 if line_number < 1 :
256231 line_number = 1
257- logging .debug ("Alert %s: Manifest %s, line %d: %s" , rule_id , mf , line_number , line_content )
258- locations .append ({
259- "physicalLocation" : {
260- "artifactLocation" : {"uri" : mf },
261- "region" : {
262- "startLine" : line_number ,
263- "snippet" : {"text" : line_content },
232+ logging .debug ("Alert %s: Manifest %s, line %d: %s" , base_rule_id , mf , line_number , line_content )
233+
234+ # Create a unique rule id and name by appending the file prefix.
235+ unique_rule_id = f"{ base_rule_id } ({ mf } )"
236+ rule_name = unique_rule_id
237+
238+ short_desc = (f"{ alert .props .get ('note' , '' )} <br/><br/>Suggested Action:<br/>{ alert .suggestion } "
239+ f"<br/><a href=\" { socket_url } \" >{ socket_url } </a>" )
240+ full_desc = "{} - {}" .format (alert .title , alert .description .replace ('\r \n ' , '<br/>' ))
241+
242+ # Add the rule if not already defined.
243+ if unique_rule_id not in rules_map :
244+ rules_map [unique_rule_id ] = {
245+ "id" : unique_rule_id ,
246+ "name" : rule_name ,
247+ "shortDescription" : {"text" : f"Alert generated for { unique_rule_id } by Socket Security" },
248+ "fullDescription" : {"text" : full_desc },
249+ "helpUri" : socket_url ,
250+ "defaultConfiguration" : {
251+ "level" : Messages .map_severity_to_sarif (severity )
264252 },
265253 }
266- })
267-
268- result_obj = {
269- "ruleId" : rule_id ,
270- "message" : {"text" : short_desc },
271- "locations" : locations ,
272- }
273- results_list .append (result_obj )
254+
255+ result_obj = {
256+ "ruleId" : unique_rule_id ,
257+ "message" : {"text" : short_desc },
258+ "locations" : [{
259+ "physicalLocation" : {
260+ "artifactLocation" : {"uri" : mf },
261+ "region" : {
262+ "startLine" : line_number ,
263+ "snippet" : {"text" : line_content },
264+ },
265+ }
266+ }]
267+ }
268+ results_list .append (result_obj )
274269
275270 sarif_data ["runs" ][0 ]["tool" ]["driver" ]["rules" ] = list (rules_map .values ())
276271 sarif_data ["runs" ][0 ]["results" ] = results_list
0 commit comments