Class: Brew::Vulns::Vulnerability
- Inherits:
-
Object
- Object
- Brew::Vulns::Vulnerability
- Defined in:
- lib/brew/vulns/vulnerability.rb
Instance Attribute Summary collapse
-
#affected ⇒ Object
readonly
Returns the value of attribute affected.
-
#aliases ⇒ Object
readonly
Returns the value of attribute aliases.
-
#details ⇒ Object
readonly
Returns the value of attribute details.
-
#id ⇒ Object
readonly
Returns the value of attribute id.
-
#references ⇒ Object
readonly
Returns the value of attribute references.
-
#severity ⇒ Object
readonly
Returns the value of attribute severity.
-
#summary ⇒ Object
readonly
Returns the value of attribute summary.
Class Method Summary collapse
Instance Method Summary collapse
- #advisory_url ⇒ Object
- #affects_version?(version, default_ecosystem = "gem") ⇒ Boolean
- #build_constraints(events) ⇒ Object
- #cve_ids ⇒ Object
- #extract_ecosystem(aff, default_ecosystem) ⇒ Object
- #extract_severity(data) ⇒ Object
- #fix_urls ⇒ Object
- #fixed_versions ⇒ Object
- #in_explicit_versions?(aff, version) ⇒ Boolean
- #in_semver_ranges?(aff, version, ecosystem) ⇒ Boolean
-
#initialize(data) ⇒ Vulnerability
constructor
A new instance of Vulnerability.
- #normalize_severity(severity) ⇒ Object
- #normalize_version(version) ⇒ Object
- #parse_cvss_metrics(vector) ⇒ Object
- #severity_display ⇒ Object
- #severity_from_cvss(vector) ⇒ Object
- #severity_level ⇒ Object
- #version_in_range?(version, events, ecosystem) ⇒ Boolean
Constructor Details
#initialize(data) ⇒ Vulnerability
Returns a new instance of Vulnerability.
11 12 13 14 15 16 17 18 19 |
# File 'lib/brew/vulns/vulnerability.rb', line 11 def initialize(data) @id = data["id"] @summary = data["summary"] @details = data["details"] @aliases = data["aliases"] || [] @references = data["references"] || [] @affected = data["affected"] || [] @severity = extract_severity(data) end |
Instance Attribute Details
#affected ⇒ Object (readonly)
Returns the value of attribute affected.
9 10 11 |
# File 'lib/brew/vulns/vulnerability.rb', line 9 def affected @affected end |
#aliases ⇒ Object (readonly)
Returns the value of attribute aliases.
9 10 11 |
# File 'lib/brew/vulns/vulnerability.rb', line 9 def aliases @aliases end |
#details ⇒ Object (readonly)
Returns the value of attribute details.
9 10 11 |
# File 'lib/brew/vulns/vulnerability.rb', line 9 def details @details end |
#id ⇒ Object (readonly)
Returns the value of attribute id.
9 10 11 |
# File 'lib/brew/vulns/vulnerability.rb', line 9 def id @id end |
#references ⇒ Object (readonly)
Returns the value of attribute references.
9 10 11 |
# File 'lib/brew/vulns/vulnerability.rb', line 9 def references @references end |
#severity ⇒ Object (readonly)
Returns the value of attribute severity.
9 10 11 |
# File 'lib/brew/vulns/vulnerability.rb', line 9 def severity @severity end |
#summary ⇒ Object (readonly)
Returns the value of attribute summary.
9 10 11 |
# File 'lib/brew/vulns/vulnerability.rb', line 9 def summary @summary end |
Class Method Details
.from_osv_list(vulns_data) ⇒ Object
73 74 75 |
# File 'lib/brew/vulns/vulnerability.rb', line 73 def self.from_osv_list(vulns_data) vulns_data.map { |data| new(data) } end |
Instance Method Details
#advisory_url ⇒ Object
39 40 41 42 |
# File 'lib/brew/vulns/vulnerability.rb', line 39 def advisory_url ref = references.find { |r| r["type"] == "ADVISORY" } ref&.dig("url") end |
#affects_version?(version, default_ecosystem = "gem") ⇒ Boolean
60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/brew/vulns/vulnerability.rb', line 60 def affects_version?(version, default_ecosystem = "gem") return true if affected.empty? normalized_version = normalize_version(version) affected.any? do |aff| ecosystem = extract_ecosystem(aff, default_ecosystem) in_explicit_versions?(aff, normalized_version) || in_semver_ranges?(aff, normalized_version, ecosystem) end end |
#build_constraints(events) ⇒ Object
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/brew/vulns/vulnerability.rb', line 180 def build_constraints(events) constraints = [] events.each do |event| if event["introduced"] intro = normalize_version(event["introduced"]) constraints << ">=#{intro}" unless intro == "0" end if event["fixed"] constraints << "<#{normalize_version(event["fixed"])}" end if event["last_affected"] constraints << "<=#{normalize_version(event["last_affected"])}" end end constraints end |
#cve_ids ⇒ Object
35 36 37 |
# File 'lib/brew/vulns/vulnerability.rb', line 35 def cve_ids ([id] + aliases).select { |a| a.start_with?("CVE-") } end |
#extract_ecosystem(aff, default_ecosystem) ⇒ Object
143 144 145 146 147 148 149 150 151 152 |
# File 'lib/brew/vulns/vulnerability.rb', line 143 def extract_ecosystem(aff, default_ecosystem) purl_str = aff.dig("package", "purl") return default_ecosystem unless purl_str purl = Purl.parse(purl_str) purl.type rescue StandardError => e warn "Warning: Failed to parse purl '#{purl_str}': #{e.}" default_ecosystem end |
#extract_severity(data) ⇒ Object
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/brew/vulns/vulnerability.rb', line 77 def extract_severity(data) if data["severity"]&.any? sev = data["severity"].first if sev["score"]&.include?("CVSS") return severity_from_cvss(sev["score"]) end end if data.dig("database_specific", "severity") return normalize_severity(data.dig("database_specific", "severity")) end data["affected"]&.each do |aff| db_sev = aff.dig("database_specific", "severity") return normalize_severity(db_sev) if db_sev end nil end |
#fix_urls ⇒ Object
44 45 46 |
# File 'lib/brew/vulns/vulnerability.rb', line 44 def fix_urls references.select { |r| r["type"] == "FIX" }.map { |r| r["url"] } end |
#fixed_versions ⇒ Object
48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/brew/vulns/vulnerability.rb', line 48 def fixed_versions versions = [] affected.each do |aff| (aff["ranges"] || []).each do |range| (range["events"] || []).each do |event| versions << event["fixed"] if event["fixed"] end end end versions.uniq end |
#in_explicit_versions?(aff, version) ⇒ Boolean
154 155 156 157 |
# File 'lib/brew/vulns/vulnerability.rb', line 154 def in_explicit_versions?(aff, version) versions = aff["versions"] || [] versions.any? { |v| normalize_version(v) == version } end |
#in_semver_ranges?(aff, version, ecosystem) ⇒ Boolean
159 160 161 162 163 164 165 166 |
# File 'lib/brew/vulns/vulnerability.rb', line 159 def in_semver_ranges?(aff, version, ecosystem) ranges = aff["ranges"] || [] semver_ranges = ranges.select { |r| r["type"] == "SEMVER" } semver_ranges.any? do |range| version_in_range?(version, range["events"], ecosystem) end end |
#normalize_severity(severity) ⇒ Object
97 98 99 100 101 102 103 104 105 106 |
# File 'lib/brew/vulns/vulnerability.rb', line 97 def normalize_severity(severity) return nil unless severity case severity.downcase when "critical" then "critical" when "high" then "high" when "moderate", "medium" then "medium" when "low" then "low" end end |
#normalize_version(version) ⇒ Object
139 140 141 |
# File 'lib/brew/vulns/vulnerability.rb', line 139 def normalize_version(version) version.sub(/^v/, "") end |
#parse_cvss_metrics(vector) ⇒ Object
131 132 133 134 135 136 137 |
# File 'lib/brew/vulns/vulnerability.rb', line 131 def parse_cvss_metrics(vector) metrics = {} vector.scan(%r{([A-Z]+):([A-Z])}).each do |key, value| metrics[key] = value end metrics end |
#severity_display ⇒ Object
21 22 23 |
# File 'lib/brew/vulns/vulnerability.rb', line 21 def severity_display severity&.upcase || "UNKNOWN" end |
#severity_from_cvss(vector) ⇒ Object
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/brew/vulns/vulnerability.rb', line 108 def severity_from_cvss(vector) return nil unless vector return nil unless vector.include?("CVSS:3") metrics = parse_cvss_metrics(vector) return nil if metrics.empty? impact_high = %w[C I A].count { |m| metrics[m] == "H" } network_attack = metrics["AV"] == "N" no_privs = metrics["PR"] == "N" no_interaction = metrics["UI"] == "N" if impact_high >= 2 && network_attack && no_privs "critical" elsif impact_high >= 1 && network_attack "high" elsif impact_high >= 1 || (network_attack && no_privs && no_interaction) "medium" else "low" end end |
#severity_level ⇒ Object
25 26 27 28 29 30 31 32 33 |
# File 'lib/brew/vulns/vulnerability.rb', line 25 def severity_level case severity&.downcase when "critical" then 4 when "high" then 3 when "medium" then 2 when "low" then 1 else 0 end end |
#version_in_range?(version, events, ecosystem) ⇒ Boolean
168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/brew/vulns/vulnerability.rb', line 168 def version_in_range?(version, events, ecosystem) return false if events.nil? || events.empty? constraints = build_constraints(events) return false if constraints.empty? Vers.satisfies?(version, constraints.join(","), ecosystem) rescue StandardError => e warn "Warning: Failed to check version '#{version}' against constraints: #{e.}" false end |