From 62c64a8e37b34a7d2fa41b5b83c0353bea0200ad Mon Sep 17 00:00:00 2001 From: Al Snow Date: Sun, 2 Jul 2023 12:15:49 -0400 Subject: [PATCH 1/8] Tiny step toward unaffected_versions field automation --- lib/github_advisory_sync.rb | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/github_advisory_sync.rb b/lib/github_advisory_sync.rb index 04af518a98..2eb5f8dab6 100644 --- a/lib/github_advisory_sync.rb +++ b/lib/github_advisory_sync.rb @@ -366,15 +366,37 @@ def patched_versions_for(package) return patched_versions end + def unaffected_versions_for(package) + # The unaffected_versions field is similarly not directly available. + # This optional field must be inferred from the vulnerableVersionRange. + + # EXAMPLE YAML OUTPUT: + #vulnerabilities: + #- package: + # name: redcloth + # ecosystem: RUBYGEMS + # vulnerableVersionRange: "< 4.3.0" + # firstPatchedVersion: + # identifier: 4.3.0 + + [""] + end + def create(package) filename_to_write = package.filename new_data = package.merge_data( "cvss_v3" => ("" unless cvss), - "cvss_v4" => "", - "unaffected_versions" => [""] + "cvss_v4" => "" ) + unaffected_versions = unaffected_versions_for(package) + + # NOTE: Do not add "unaffected_versions:" field if empty. + if !unaffected_versions.empty? + new_data['unaffected_versions'] = unaffected_versions + end + patched_versions = patched_versions_for(package) if !patched_versions.empty? From 8011167a84af5d521e7c8e0acc876d54d8debd4b Mon Sep 17 00:00:00 2001 From: Al Snow Date: Sun, 2 Jul 2023 12:33:47 -0400 Subject: [PATCH 2/8] Added use cases as comment for dev --- lib/github_advisory_sync.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/github_advisory_sync.rb b/lib/github_advisory_sync.rb index 2eb5f8dab6..3b8262debe 100644 --- a/lib/github_advisory_sync.rb +++ b/lib/github_advisory_sync.rb @@ -379,6 +379,19 @@ def unaffected_versions_for(package) # firstPatchedVersion: # identifier: 4.3.0 + #Use Case 1: gems/nokogiri/GHSA-fq42-c5rg-92c2.yml + # vulnerableVersionRange: "< 1.13.2" + # firstPatchedVersion: + # identifier: 1.13.2 + #Use Case 2: Example: gems/spree_auth_devise/GHSA-8xfaw-5q82-3652.yml + # vulnerableVersionRange:"= 4.1.0" + # firstPatchedVersion: + # identifier: 4.1.1 + #Use Case 3: Example: gems/webrick/CVE-2009-4492.yml + # vulnerableVersionRange: "<= 1.3.1" + # firstPatchedVersion: + # identifier: 1.4.0 + [""] end From 11907fcab52431cd2d72b774b71d97b6982fc878 Mon Sep 17 00:00:00 2001 From: Al Snow Date: Sun, 2 Jul 2023 13:00:41 -0400 Subject: [PATCH 3/8] Added expected unaffected_versions values --- lib/github_advisory_sync.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/github_advisory_sync.rb b/lib/github_advisory_sync.rb index 3b8262debe..4c5a49c22c 100644 --- a/lib/github_advisory_sync.rb +++ b/lib/github_advisory_sync.rb @@ -382,16 +382,20 @@ def unaffected_versions_for(package) #Use Case 1: gems/nokogiri/GHSA-fq42-c5rg-92c2.yml # vulnerableVersionRange: "< 1.13.2" # firstPatchedVersion: - # identifier: 1.13.2 + # identifier: 1.13.2 + # THEREFORE: Do nothing. + # #Use Case 2: Example: gems/spree_auth_devise/GHSA-8xfaw-5q82-3652.yml # vulnerableVersionRange:"= 4.1.0" # firstPatchedVersion: - # identifier: 4.1.1 - #Use Case 3: Example: gems/webrick/CVE-2009-4492.yml + # identifier: 4.1.1 + #THEREFORE: unaffected_versions: "< 4.1.0" + # + #Use Case 3: Example: TBD # vulnerableVersionRange: "<= 1.3.1" # firstPatchedVersion: - # identifier: 1.4.0 - + # identifier: 1.4.0 + #THEREFORE: unaffected_versions: "> 1.3.1, < 4.1.0" [""] end From f901421a109c21f39fcef82cc08f249fd3e66bce Mon Sep 17 00:00:00 2001 From: Al Snow Date: Sun, 2 Jul 2023 13:03:14 -0400 Subject: [PATCH 4/8] Added multi patched_versions ranges use case --- lib/github_advisory_sync.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/github_advisory_sync.rb b/lib/github_advisory_sync.rb index 4c5a49c22c..ff7e350194 100644 --- a/lib/github_advisory_sync.rb +++ b/lib/github_advisory_sync.rb @@ -396,6 +396,8 @@ def unaffected_versions_for(package) # firstPatchedVersion: # identifier: 1.4.0 #THEREFORE: unaffected_versions: "> 1.3.1, < 4.1.0" + # + #Use Case 4: Multiple patched_verions ranges: TBD [""] end From 1a77e3265b97c3bf5571b5d2431000c89354b4f9 Mon Sep 17 00:00:00 2001 From: Al Snow Date: Sun, 2 Jul 2023 17:42:08 -0400 Subject: [PATCH 5/8] Initial code to set unaffected_versions field --- lib/github_advisory_sync.rb | 47 ++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/github_advisory_sync.rb b/lib/github_advisory_sync.rb index ff7e350194..7dbd078757 100644 --- a/lib/github_advisory_sync.rb +++ b/lib/github_advisory_sync.rb @@ -366,6 +366,19 @@ def patched_versions_for(package) return patched_versions end + def first_unaffected_versions_for(package) + first_unaffected_versions = [] + + vulnerabilities.each do |v| + if v['package']['name'] == package.name && + v['vulnerableVersionRange'] + first_unaffected_versions << v['vulnerableVersionRange'] + end + end + + first_unaffected_versions.sort + end + def unaffected_versions_for(package) # The unaffected_versions field is similarly not directly available. # This optional field must be inferred from the vulnerableVersionRange. @@ -398,7 +411,39 @@ def unaffected_versions_for(package) #THEREFORE: unaffected_versions: "> 1.3.1, < 4.1.0" # #Use Case 4: Multiple patched_verions ranges: TBD - [""] + + first_unaffected_versions = first_unaffected_versions_for(package) + unaffected_versions = [] + + if !first_unaffected_versions.empty? + unaff_vers_range = + first_unaffected_versions.last[2..first_unaffected_versions.last.length] + + case first_unaffected_versions.last[0,1] + when "<" + if first_unaffected_versions.last[0,2] == "<=" + unaff_vers_range = + first_unaffected_versions.last[3..first_unaffected_versions.last.length] + unaffected_versions << "[<=]: [#{unaff_vers_range}]" + else + unaffected_versions << "[<]: [#{unaff_vers_range}]" + end + when "=" + unaffected_versions << "[=]: [#{unaff_vers_range}]" + when ">" + if first_unaffected_versions.last[0,2] == ">=" + unaff_vers_range = + first_unaffected_versions.last[4..first_unaffected_versions.last.length] + unaffected_versions << "[>=]: [#{unaff_vers_range}]" + else + unaffected_versions << "[>]: [#{unaff_vers_range}]" + end + else + unaffected_versions << "[UNK]: [#{unaff_vers_range}]" + end + end + + return unaffected_versions end def create(package) From f4775a8b85a8cd6f2a19d1963d1784fa3291ccb3 Mon Sep 17 00:00:00 2001 From: Al Snow Date: Sun, 2 Jul 2023 20:34:08 -0400 Subject: [PATCH 6/8] Add patched_versions code for use with unaffected_versions field --- lib/github_advisory_sync.rb | 64 ++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 23 deletions(-) diff --git a/lib/github_advisory_sync.rb b/lib/github_advisory_sync.rb index 7dbd078757..dab296a98b 100644 --- a/lib/github_advisory_sync.rb +++ b/lib/github_advisory_sync.rb @@ -382,7 +382,6 @@ def first_unaffected_versions_for(package) def unaffected_versions_for(package) # The unaffected_versions field is similarly not directly available. # This optional field must be inferred from the vulnerableVersionRange. - # EXAMPLE YAML OUTPUT: #vulnerabilities: #- package: @@ -392,25 +391,21 @@ def unaffected_versions_for(package) # firstPatchedVersion: # identifier: 4.3.0 - #Use Case 1: gems/nokogiri/GHSA-fq42-c5rg-92c2.yml - # vulnerableVersionRange: "< 1.13.2" - # firstPatchedVersion: - # identifier: 1.13.2 - # THEREFORE: Do nothing. - # - #Use Case 2: Example: gems/spree_auth_devise/GHSA-8xfaw-5q82-3652.yml - # vulnerableVersionRange:"= 4.1.0" - # firstPatchedVersion: - # identifier: 4.1.1 - #THEREFORE: unaffected_versions: "< 4.1.0" - # - #Use Case 3: Example: TBD - # vulnerableVersionRange: "<= 1.3.1" - # firstPatchedVersion: - # identifier: 1.4.0 - #THEREFORE: unaffected_versions: "> 1.3.1, < 4.1.0" - # - #Use Case 4: Multiple patched_verions ranges: TBD +#TODO: #Use Case 4: Multiple patched_verions ranges: TBD + + # Need this to compare vulnerableVersionRange and firstPatchedVersion[identifier] + first_patched_versions = patched_versions_for(package) + if first_patched_versions.count > 0 + fpv_operator = first_patched_versions.last[0,1] + fpv_value = first_patched_versions.last[3..first_patched_versions.last.length] + + #puts "FPV=#{first_patched_versions}; COUNT=[#{first_patched_versions.length}]" + #puts "FPV[1]=[#{fpv_operator}]; FPV[2]=[#{fpv_value}]" +#TODO: HEH Add "<=" and ">=" + else + fpv_operator = "EMPTY" + fpv_value = "EMPTY" + end first_unaffected_versions = first_unaffected_versions_for(package) unaffected_versions = [] @@ -422,14 +417,37 @@ def unaffected_versions_for(package) case first_unaffected_versions.last[0,1] when "<" if first_unaffected_versions.last[0,2] == "<=" + #Use Case 3: Example: TBD + # vulnerableVersionRange: "<= 1.3.1" + # firstPatchedVersion: + # identifier: 1.4.0 + #THEREFORE: unaffected_versions: "> 1.3.1, < 4.1.0" unaff_vers_range = first_unaffected_versions.last[3..first_unaffected_versions.last.length] - unaffected_versions << "[<=]: [#{unaff_vers_range}]" + unaffected_versions << "[<=]: [> #{unaff_vers_range}, < #{fpv_value} ]" else - unaffected_versions << "[<]: [#{unaff_vers_range}]" + if unaff_vers_range == fpv_value + #Use Case 1: gems/nokogiri/GHSA-fq42-c5rg-92c2.yml + # vulnerableVersionRange: "< 1.13.2" + # firstPatchedVersion: + # identifier: 1.13.2 + # THEREFORE: Do nothing. + unaffected_versions << "[< fvr == fpv_value], so do nothing]" + else + #Use Case 2: Example: gems/spree_auth_devise/GHSA-8xfaw-5q82-3652.yml + # vulnerableVersionRange:"= 4.1.0" + # firstPatchedVersion: + # identifier: 4.1.1 + #THEREFORE: unaffected_versions: "< 4.1.0" +#TODO: Add expr: (unaff_vers_range == fpv_operator + 0.0.1) + unaffected_versions << "[<]: [< #{unaff_vers_range}, #{fpv_operator} + 0.0.1]" + end end when "=" - unaffected_versions << "[=]: [#{unaff_vers_range}]" +#TODO: Double check next 3 lines of code. + if unaff_vers_range < fpv_value + unaffected_versions << "[=, PROBABLY]: [< #{unaff_vers_range}]" + end when ">" if first_unaffected_versions.last[0,2] == ">=" unaff_vers_range = From 4723db68e0367aff9681e098de5ebc1cca84d74e Mon Sep 17 00:00:00 2001 From: Al Snow Date: Mon, 3 Jul 2023 10:11:35 -0400 Subject: [PATCH 7/8] Finished beta 1 changes --- lib/github_advisory_sync.rb | 79 ++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/lib/github_advisory_sync.rb b/lib/github_advisory_sync.rb index dab296a98b..3e167bfe30 100644 --- a/lib/github_advisory_sync.rb +++ b/lib/github_advisory_sync.rb @@ -382,6 +382,7 @@ def first_unaffected_versions_for(package) def unaffected_versions_for(package) # The unaffected_versions field is similarly not directly available. # This optional field must be inferred from the vulnerableVersionRange. + # # EXAMPLE YAML OUTPUT: #vulnerabilities: #- package: @@ -391,20 +392,22 @@ def unaffected_versions_for(package) # firstPatchedVersion: # identifier: 4.3.0 -#TODO: #Use Case 4: Multiple patched_verions ranges: TBD - - # Need this to compare vulnerableVersionRange and firstPatchedVersion[identifier] + # Need this code to compare vulnerableVersionRange and firstPatchedVersion[identifier] first_patched_versions = patched_versions_for(package) if first_patched_versions.count > 0 - fpv_operator = first_patched_versions.last[0,1] - fpv_value = first_patched_versions.last[3..first_patched_versions.last.length] - - #puts "FPV=#{first_patched_versions}; COUNT=[#{first_patched_versions.length}]" - #puts "FPV[1]=[#{fpv_operator}]; FPV[2]=[#{fpv_value}]" -#TODO: HEH Add "<=" and ">=" - else - fpv_operator = "EMPTY" - fpv_value = "EMPTY" + if first_patched_versions.count == 1 + fpv_operator = first_patched_versions.last[0,1] + fpv_value = first_patched_versions.last[3..first_patched_versions.last.length] + #puts "FPV=#{first_patched_versions}; COUNT=[#{first_patched_versions.length}]" + #puts "FPV[1]=[#{fpv_operator}]; FPV[2]=[#{fpv_value}]" + else # > 1 + puts "TODO: #Use Case 4: Multiple patched_verions ranges: TBD" + fpv_operator = "MULTIPLE" + fpv_value = "MULTIPLE" + end + else # == 0 + #fpv_operator = "EMPTY" + #fpv_value = "EMPTY" end first_unaffected_versions = first_unaffected_versions_for(package) @@ -424,7 +427,8 @@ def unaffected_versions_for(package) #THEREFORE: unaffected_versions: "> 1.3.1, < 4.1.0" unaff_vers_range = first_unaffected_versions.last[3..first_unaffected_versions.last.length] - unaffected_versions << "[<=]: [> #{unaff_vers_range}, < #{fpv_value} ]" + puts "uv3: [<=]: > #{unaff_vers_range}, < #{fpv_value}" + unaffected_versions << "> #{unaff_vers_range}, < #{fpv_value}" else if unaff_vers_range == fpv_value #Use Case 1: gems/nokogiri/GHSA-fq42-c5rg-92c2.yml @@ -432,32 +436,43 @@ def unaffected_versions_for(package) # firstPatchedVersion: # identifier: 1.13.2 # THEREFORE: Do nothing. - unaffected_versions << "[< fvr == fpv_value], so do nothing]" - else - #Use Case 2: Example: gems/spree_auth_devise/GHSA-8xfaw-5q82-3652.yml - # vulnerableVersionRange:"= 4.1.0" - # firstPatchedVersion: - # identifier: 4.1.1 - #THEREFORE: unaffected_versions: "< 4.1.0" -#TODO: Add expr: (unaff_vers_range == fpv_operator + 0.0.1) - unaffected_versions << "[<]: [< #{unaff_vers_range}, #{fpv_operator} + 0.0.1]" + puts "uc1A: [< && fvr == fpv_value], so do nothing" + else # < && != + puts "uc1B: [< && fvr != fpv_value], so UNKNOWN" end end when "=" -#TODO: Double check next 3 lines of code. if unaff_vers_range < fpv_value - unaffected_versions << "[=, PROBABLY]: [< #{unaff_vers_range}]" + #Use Case 2: Example: gems/spree_auth_devise/GHSA-8xfaw-5q82-3652.yml + # vulnerableVersionRange:"= 4.1.0" + # firstPatchedVersion: + # identifier: 4.1.1 + #THEREFORE: unaffected_versions: "< 4.1.0" + puts "uc4A: [=, uvr < fpv_v]: < #{unaff_vers_range}" + unaffected_versions << "< #{unaff_vers_range}" + else # = & != + puts "uc4B: [=, uvr >= fpv_v]: UNKNOWN" + unaffected_verions << "[=, uvr >= fpv_v]: UNKNOWN" end when ">" if first_unaffected_versions.last[0,2] == ">=" unaff_vers_range = first_unaffected_versions.last[4..first_unaffected_versions.last.length] - unaffected_versions << "[>=]: [#{unaff_vers_range}]" + puts "[>=]: #{unaff_vers_range}" + unaffected_versions << "[>=]: #{unaff_vers_range}" else - unaffected_versions << "[>]: [#{unaff_vers_range}]" + puts "[>, !>=]: #{unaff_vers_range}" + unaffected_versions << "[>, !>=]: #{unaff_vers_range}" end + when "!" + puts "[!]: #{unaff_vers_range}" + unaffected_versions << "[! OPERATOR]: #{unaff_vers_range}" + when "~" + puts "[~]: #{unaff_vers_range}" + unaffected_versions << "[~ OPERATOR]: #{unaff_vers_range}" else - unaffected_versions << "[UNK]: [#{unaff_vers_range}]" + puts "[UNK OPERATOR]: #{unaff_vers_range}" + unaffected_versions << "[UNK OPERATOR]: #{unaff_vers_range}" end end @@ -474,13 +489,15 @@ def create(package) unaffected_versions = unaffected_versions_for(package) - # NOTE: Do not add "unaffected_versions:" field if empty. + patched_versions = patched_versions_for(package) + if !unaffected_versions.empty? - new_data['unaffected_versions'] = unaffected_versions + new_data['unaffected_versions'] = unaffected_versions if !patched_versions.empty? + else + # NOTE: Do not add "unaffected_versions:" field if empty. + puts "DEBUG: unaffected_versions: All Affected" end - patched_versions = patched_versions_for(package) - if !patched_versions.empty? new_data['patched_versions'] = patched_versions else From 119a93d82be9548ac862c7d8fb471ae1c1077c84 Mon Sep 17 00:00:00 2001 From: Al Snow Date: Tue, 4 Jul 2023 12:30:34 -0400 Subject: [PATCH 8/8] A little refactoring --- lib/github_advisory_sync.rb | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/github_advisory_sync.rb b/lib/github_advisory_sync.rb index 3e167bfe30..817ce1300d 100644 --- a/lib/github_advisory_sync.rb +++ b/lib/github_advisory_sync.rb @@ -379,21 +379,7 @@ def first_unaffected_versions_for(package) first_unaffected_versions.sort end - def unaffected_versions_for(package) - # The unaffected_versions field is similarly not directly available. - # This optional field must be inferred from the vulnerableVersionRange. - # - # EXAMPLE YAML OUTPUT: - #vulnerabilities: - #- package: - # name: redcloth - # ecosystem: RUBYGEMS - # vulnerableVersionRange: "< 4.3.0" - # firstPatchedVersion: - # identifier: 4.3.0 - - # Need this code to compare vulnerableVersionRange and firstPatchedVersion[identifier] - first_patched_versions = patched_versions_for(package) + def set_fpv_vars(first_patched_versions) if first_patched_versions.count > 0 if first_patched_versions.count == 1 fpv_operator = first_patched_versions.last[0,1] @@ -410,6 +396,27 @@ def unaffected_versions_for(package) #fpv_value = "EMPTY" end + return fpv_operator, fpv_value + end + + def unaffected_versions_for(package) + # The unaffected_versions field is similarly not directly available. + # This optional field must be inferred from the vulnerableVersionRange. + # + # EXAMPLE YAML OUTPUT: + #vulnerabilities: + #- package: + # name: redcloth + # ecosystem: RUBYGEMS + # vulnerableVersionRange: "< 4.3.0" + # firstPatchedVersion: + # identifier: 4.3.0 + + # Need this code to compare vulnerableVersionRange and firstPatchedVersion[identifier] + first_patched_versions = patched_versions_for(package) + + fpv_operator, fpv_value = set_fpv_vars(first_patched_versions) + first_unaffected_versions = first_unaffected_versions_for(package) unaffected_versions = []