@@ -18,7 +18,7 @@ def self.sync(min_year: 2015)
18
18
# Filter out advisories with a CVE year that is before the min_year
19
19
gh_advisories . select! { |v | v . cve_after_year? ( min_year ) }
20
20
21
- files_written = gh_advisories . filter_map ( &:write_files ) . flatten
21
+ files_written = gh_advisories . filter_map ( &:sync ) . flatten
22
22
23
23
puts "\n Sync completed"
24
24
if files_written . empty?
@@ -179,6 +179,58 @@ def github_api_token
179
179
end
180
180
181
181
class GitHubAdvisory
182
+ class Package
183
+ attr_reader :name
184
+
185
+ def initialize ( advisory , name )
186
+ @advisory = advisory
187
+ @name = name
188
+ end
189
+
190
+ def updating?
191
+ File . exist? filename
192
+ end
193
+
194
+ def filename
195
+ File . join ( "gems" , name , "#{ @advisory . primary_id } .yml" )
196
+ end
197
+
198
+ def framework
199
+ case name
200
+ when %w[
201
+ actioncable actionmailbox actionmailer actionpack actiontext
202
+ actionview activejob activemodel activerecord activestorage
203
+ activesupport railties
204
+ ]
205
+ "rails"
206
+ end
207
+ end
208
+
209
+ def to_h
210
+ {
211
+ "gem" => name ,
212
+ "framework" => framework ,
213
+ } . merge ( @advisory . to_h )
214
+ end
215
+
216
+ def merge_data ( saved_data )
217
+ data = { }
218
+
219
+ # Creating the hash like this makes the key insert order consistent so
220
+ # the output should always be the same for the same data
221
+ KEYS . each do |key |
222
+ data [ key ] = saved_data [ key ] || to_h [ key ]
223
+ end
224
+
225
+ data . compact!
226
+ end
227
+
228
+ KEYS = %w[
229
+ gem framework platform cve osvdb ghsa url title date description
230
+ cvss_v2 cvss_v3 unaffected_versions patched_versions related
231
+ ] . freeze
232
+ end
233
+
182
234
attr_reader :advisory , :vulnerabilities
183
235
184
236
def initialize ( advisory )
@@ -226,7 +278,7 @@ def withdrawn?
226
278
end
227
279
228
280
def cvss
229
- return "<FILL IN IF AVAILABLE>" if advisory [ "cvss" ] [ "vectorString" ] . nil?
281
+ return if advisory [ "cvss" ] [ "vectorString" ] . nil?
230
282
231
283
advisory [ "cvss" ] [ "score" ] . to_f
232
284
end
@@ -237,64 +289,86 @@ def external_reference
237
289
end
238
290
end
239
291
240
- def package_names
241
- vulnerabilities . map { |v | v [ "package" ] [ "name" ] } . uniq
292
+ def packages
293
+ vulnerabilities . map { |v | v [ "package" ] [ "name" ] } . uniq . map do |name |
294
+ Package . new ( self , name )
295
+ end
242
296
end
243
297
244
- def filename_for ( package_name )
245
- File . join ( "gems" , package_name , "#{ primary_id } .yml" )
298
+ def to_h
299
+ {
300
+ "cve" => ( cve_id [ 4 ..20 ] if cve_id ) ,
301
+ "date" => published_day ,
302
+ "ghsa" => ghsa_id [ 5 ..] ,
303
+ "url" => external_reference ,
304
+ "title" => advisory [ "summary" ] ,
305
+ "description" => advisory [ "description" ] ,
306
+ "cvss_v3" => cvss ,
307
+ } . compact
246
308
end
247
309
248
- def write_files
249
- packages_to_write = package_names . filter { |name | !File . exist? ( filename_for ( name ) ) }
310
+ def sync
311
+ packages . map do |package |
312
+ if package . updating?
313
+ update ( package )
314
+ else
315
+ create ( package )
316
+ end
317
+ end
318
+ end
250
319
251
- return if packages_to_write . empty?
320
+ def update ( package )
321
+ saved_data = YAML . load_file ( package . filename )
322
+ new_data = package . merge_data ( saved_data )
252
323
253
- packages_to_write . map do |package_name |
254
- filename_to_write = filename_for ( package_name )
324
+ return if saved_data == new_data
255
325
256
- data = {
257
- "gem" => package_name ,
258
- "ghsa" => ghsa_id [ 5 ..] ,
259
- "url" => external_reference ,
260
- "date" => published_day ,
261
- "title" => advisory [ "summary" ] ,
262
- "description" => advisory [ "description" ] ,
263
- "cvss_v3" => cvss ,
264
- "patched_versions" => [ "<FILL IN SEE BELOW>" ] ,
265
- "unaffected_versions" => [ "<OPTIONAL: FILL IN SEE BELOW>" ]
266
- }
267
- data [ "cve" ] = cve_id [ 4 ..20 ] if cve_id
268
-
269
- dir_to_write = File . dirname ( filename_to_write )
270
- Dir . mkdir dir_to_write unless Dir . exist? ( dir_to_write )
271
- File . open ( filename_to_write , "w" ) do |file |
272
- # create an automatically generated advisory yaml file
273
- file . write data . to_yaml
274
-
275
- # The data we just wrote is incomplete,
276
- # and therefore should not be committed as is
277
- # We can not directly translate from GitHub to rubysec advisory format
278
- #
279
- # The patched_versions field is not exactly available.
280
- # - GitHub has a first_patched_version field,
281
- # but rubysec advisory needs a ruby version spec
282
- #
283
- # The unaffected_versions field is similarly not directly available
284
- # This optional field must be inferred from the vulnerableVersionRange
285
- #
286
- # To help write those fields, we put all the github data below.
287
- #
288
- # The second block of yaml in a .yaml file is ignored (after the second "---" line)
289
- # This effectively makes this data a large comment
290
- # Still it should be removed before the data goes into rubysec
291
- file . write "\n \n # GitHub advisory data below - **Remove this data before committing**\n "
292
- file . write "# Use this data to write patched_versions (and potentially unaffected_versions) above\n "
293
- file . write advisory . merge ( { "vulnerabilities" => vulnerabilities } ) . to_yaml
294
- end
295
- puts "Wrote: #{ filename_to_write } "
296
- filename_to_write
326
+ File . open ( package . filename , 'w' ) do |file |
327
+ file . write YAML . dump ( new_data )
328
+ end
329
+
330
+ puts "Updated: #{ package . filename } "
331
+
332
+ package . filename
333
+ end
334
+
335
+ def create ( package )
336
+ filename_to_write = package . filename
337
+
338
+ new_data = package . merge_data ( {
339
+ "cvss_v3" : ( "<FILL IN IF AVAILABLE>" unless cvss ) ,
340
+ "patched_versions" => [ "<FILL IN SEE BELOW>" ] ,
341
+ "unaffected_versions" => [ "<OPTIONAL: FILL IN SEE BELOW>" ]
342
+ } )
343
+
344
+ dir_to_write = File . dirname ( filename_to_write )
345
+ Dir . mkdir dir_to_write unless Dir . exist? ( dir_to_write )
346
+ File . open ( filename_to_write , "w" ) do |file |
347
+ # create an automatically generated advisory yaml file
348
+ file . write new_data . to_yaml
349
+
350
+ # The data we just wrote is incomplete,
351
+ # and therefore should not be committed as is
352
+ # We can not directly translate from GitHub to rubysec advisory format
353
+ #
354
+ # The patched_versions field is not exactly available.
355
+ # - GitHub has a first_patched_version field,
356
+ # but rubysec advisory needs a ruby version spec
357
+ #
358
+ # The unaffected_versions field is similarly not directly available
359
+ # This optional field must be inferred from the vulnerableVersionRange
360
+ #
361
+ # To help write those fields, we put all the github data below.
362
+ #
363
+ # The second block of yaml in a .yaml file is ignored (after the second "---" line)
364
+ # This effectively makes this data a large comment
365
+ # Still it should be removed before the data goes into rubysec
366
+ file . write "\n \n # GitHub advisory data below - **Remove this data before committing**\n "
367
+ file . write "# Use this data to write patched_versions (and potentially unaffected_versions) above\n "
368
+ file . write advisory . merge ( { "vulnerabilities" => vulnerabilities } ) . to_yaml
297
369
end
370
+ puts "Wrote: #{ filename_to_write } "
371
+ filename_to_write
298
372
end
299
373
300
374
def cve_after_year? ( year )
0 commit comments