@@ -37,78 +37,58 @@ def find_line_in_file(packagename: str, packageversion: str, manifest_file: str)
37
37
"""
38
38
Finds the line number and snippet of code for the given package/version in a manifest file.
39
39
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
47
40
"""
48
- # Extract just the file name to detect manifest type
49
41
file_type = Path (manifest_file ).name
50
-
42
+ logging .debug ("Processing manifest file: %s" , manifest_file )
43
+
51
44
# ----------------------------------------------------
52
45
# 1) JSON-based manifest files
53
46
# ----------------------------------------------------
54
47
if file_type in ["package-lock.json" , "Pipfile.lock" , "composer.lock" ]:
55
48
try :
56
- # Read entire file so we can parse JSON and also do raw line checks
57
49
with open (manifest_file , "r" , encoding = "utf-8" ) as f :
58
50
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 )
59
57
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.
65
58
packages_dict = (
66
59
data .get ("packages" )
67
60
or data .get ("default" )
68
61
or data .get ("dependencies" )
69
62
or {}
70
63
)
71
-
64
+ logging . debug ( "Packages dict keys: %s" , list ( packages_dict . keys ()))
72
65
found_key = None
73
66
found_info = None
74
- # Locate a dictionary entry whose 'version' matches
75
67
for key , value in packages_dict .items ():
76
- # For NPM package-lock, keys might look like "node_modules/axios"
77
68
if key .endswith (packagename ) and "version" in value :
78
69
if value ["version" ] == packageversion :
79
70
found_key = key
80
71
found_info = value
81
72
break
82
73
83
74
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 } ":'
86
76
needle_version = f'"version": "{ packageversion } "'
87
77
lines = raw_text .splitlines ()
88
- best_line = 1
89
- snippet = None
90
-
78
+ logging .debug ("Total lines: %d" , len (lines ))
91
79
for i , line in enumerate (lines , start = 1 ):
92
80
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 } '
102
84
else :
103
85
return 1 , f"{ packagename } { packageversion } (not found in { manifest_file } )"
104
-
105
86
except (FileNotFoundError , json .JSONDecodeError ):
106
87
return 1 , f"Error reading { manifest_file } "
107
-
88
+
108
89
# ----------------------------------------------------
109
90
# 2) Text-based / line-based manifests
110
91
# ----------------------------------------------------
111
- # Define a dictionary of patterns for common manifest types
112
92
search_patterns = {
113
93
"package.json" : rf'"{ packagename } ":\s*"{ packageversion } "' ,
114
94
"yarn.lock" : rf'{ packagename } @{ packageversion } ' ,
@@ -134,18 +114,16 @@ def find_line_in_file(packagename: str, packageversion: str, manifest_file: str)
134
114
"conanfile.txt" : rf'{ re .escape (packagename )} /{ re .escape (packageversion )} ' ,
135
115
"vcpkg.json" : rf'"{ re .escape (packagename )} ":\s*"{ re .escape (packageversion )} "' ,
136
116
}
137
-
138
- # If no specific pattern is found for this file name, fallback to a naive approach
139
117
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 )
140
119
try :
141
- # Read file lines and search for a match
142
120
with open (manifest_file , 'r' , encoding = "utf-8" ) as file :
143
121
lines = [line .rstrip ("\n " ) for line in file ]
122
+ logging .debug ("Total lines in %s: %d" , manifest_file , len (lines ))
144
123
for line_number , line_content in enumerate (lines , start = 1 ):
145
- # For Python conditional dependencies, ignore everything after first ';'
146
124
line_main = line_content .split (";" , 1 )[0 ].strip ()
147
- # Use a case-insensitive regex search
148
125
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 ())
149
127
return line_number , line_content .strip ()
150
128
except FileNotFoundError :
151
129
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) -
180
158
"composer.json" : "composer" ,
181
159
"vcpkg.json" : "vcpkg" ,
182
160
}
183
-
184
161
file_type = Path (manifest_file ).name
185
162
url_prefix = manifest_to_url_prefix .get (file_type , "unknown" )
186
163
return f"https://socket.dev/{ url_prefix } /package/{ pkg_name } /alerts/{ pkg_version } "
187
164
188
165
@staticmethod
189
166
def create_security_comment_sarif (diff ) -> dict :
190
167
"""
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.
192
175
"""
193
176
if len (diff .new_alerts ) == 0 :
194
177
for alert in diff .new_alerts :
@@ -219,7 +202,7 @@ def create_security_comment_sarif(diff) -> dict:
219
202
rule_id = f"{ pkg_name } =={ pkg_version } "
220
203
severity = alert .severity
221
204
222
- # --- Extract manifest files ---
205
+ # --- Extract manifest files from alert data ---
223
206
manifest_files = []
224
207
if alert .introduced_by and isinstance (alert .introduced_by , list ):
225
208
for entry in alert .introduced_by :
@@ -230,16 +213,12 @@ def create_security_comment_sarif(diff) -> dict:
230
213
elif hasattr (alert , 'manifests' ) and alert .manifests :
231
214
manifest_files = [mf .strip () for mf in alert .manifests .split (";" ) if mf .strip ()]
232
215
233
- # Log the extracted manifest files
234
216
logging .debug ("Alert %s manifest_files before fallback: %s" , rule_id , manifest_files )
235
-
236
217
if not manifest_files :
237
218
manifest_files = ["requirements.txt" ]
238
219
logging .debug ("Alert %s: Falling back to manifest_files: %s" , rule_id , manifest_files )
239
220
240
- # Log the manifest file used for URL generation
241
221
logging .debug ("Alert %s: Using manifest_file for URL: %s" , rule_id , manifest_files [0 ])
242
-
243
222
socket_url = Messages .get_manifest_type_url (manifest_files [0 ], pkg_name , pkg_version )
244
223
short_desc = (f"{ alert .props .get ('note' , '' )} <br/><br/>Suggested Action:<br/>{ alert .suggestion } "
245
224
f"<br/><a href=\" { socket_url } \" >{ socket_url } </a>" )
@@ -257,13 +236,13 @@ def create_security_comment_sarif(diff) -> dict:
257
236
},
258
237
}
259
238
260
- # Create a SARIF location for each manifest file and log each result .
239
+ # Create a SARIF location for each manifest file.
261
240
locations = []
262
241
for mf in manifest_files :
263
242
line_number , line_content = Messages .find_line_in_file (pkg_name , pkg_version , mf )
264
243
if line_number < 1 :
265
244
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 )
267
246
locations .append ({
268
247
"physicalLocation" : {
269
248
"artifactLocation" : {"uri" : mf },
@@ -285,7 +264,7 @@ def create_security_comment_sarif(diff) -> dict:
285
264
sarif_data ["runs" ][0 ]["results" ] = results_list
286
265
287
266
return sarif_data
288
-
267
+
289
268
@staticmethod
290
269
def create_security_comment_json (diff : Diff ) -> dict :
291
270
scan_failed = False
0 commit comments