diff --git a/lib/github_advisory_sync.rb b/lib/github_advisory_sync.rb index 04af518a98..817ce1300d 100644 --- a/lib/github_advisory_sync.rb +++ b/lib/github_advisory_sync.rb @@ -366,17 +366,145 @@ 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 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] + 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 + + 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 = [] + + 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] == "<=" + #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] + 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 + # vulnerableVersionRange: "< 1.13.2" + # firstPatchedVersion: + # identifier: 1.13.2 + # THEREFORE: Do nothing. + puts "uc1A: [< && fvr == fpv_value], so do nothing" + else # < && != + puts "uc1B: [< && fvr != fpv_value], so UNKNOWN" + end + end + when "=" + if unaff_vers_range < fpv_value + #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] + puts "[>=]: #{unaff_vers_range}" + unaffected_versions << "[>=]: #{unaff_vers_range}" + else + 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 + puts "[UNK OPERATOR]: #{unaff_vers_range}" + unaffected_versions << "[UNK OPERATOR]: #{unaff_vers_range}" + end + end + + return unaffected_versions + 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) + patched_versions = patched_versions_for(package) + if !unaffected_versions.empty? + 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 + if !patched_versions.empty? new_data['patched_versions'] = patched_versions else