Class: Homebrew::Vulns::Repology Private
- Inherits:
-
CachedFeed
- Object
- CachedFeed
- Homebrew::Vulns::Repology
- Defined in:
- vulns/repology.rb
Overview
This class is part of a private API. This class may only be used in the Homebrew/brew repository. Third parties should avoid using this class if possible, as it may be removed or changed without warning.
Reader for the Repology-derived formula → distro-package index published
by Homebrew/advisory-database (data/repology.json, built by that
repository's RepologyIndex via rake repology:build).
The index maps each formula name to its source-package names in OSV.dev-covered distro ecosystems so Match can query those ecosystems' advisories. Repology.lookup provides a live single-project API fallback for formulae the published index doesn't yet cover.
Constant Summary collapse
- DATA_URL =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
"https://raw.githubusercontent.com/Homebrew/advisory-database/" \ "main/data/repology.json"
- DistroMap =
This constant is part of a private API. This constant may only be used in the Homebrew/brew repository. Third parties should avoid using this constant if possible, as it may be removed or changed without warning.
T.type_alias { T::Hash[String, T::Array[String]] }
Instance Attribute Summary collapse
- #meta ⇒ Hash{String => T.untyped} readonly private
Class Method Summary collapse
- .base_name(name) ⇒ String private
- .cache_filename ⇒ String private
- .data_url ⇒ String private
- .default_max_age ⇒ Integer private
- .distil(entries) ⇒ DistroMap private
-
.fetch_project(project) ⇒ Array<Hash{String => T.untyped}>
private
Fetch one Repology project.
- .homebrew_entries(entries) ⇒ Hash{String => Boolean} private
-
.lookup(formula_name) ⇒ DistroMap
private
Live single-project fallback for a formula the published index does not cover: a new formula in a homebrew-core PR before the next nightly index build, or one the index put in
meta.ambiguous_projects. - .name_candidates(formula_name) ⇒ Array<String> private
- .resolve_contributions(contributions) ⇒ DistroMap? private
Instance Method Summary collapse
-
#distro_packages_for(formula_name) ⇒ DistroMap
private
Returns
{osv_ecosystem => [srcname, ...]}forformula_name, or an empty hash if the index has no entry. - #formulae ⇒ Array<String> private
- #initialize(data) ⇒ void constructor private
Methods inherited from CachedFeed
#as_hash, from_file, load, refresh
Methods included from Utils::Output::Mixin
#issue_reporting_message, #odebug, #odeprecated, #odie, #odisabled, #ofail, #oh1, #oh1_title, #ohai, #ohai_title, #onoe, #opoo, #opoo_outside_github_actions, #opoo_without_github_actions_annotation, #pretty_deprecated, #pretty_disabled, #pretty_duration, #pretty_install_status, #pretty_installed, #pretty_uninstalled, #pretty_unmarked, #pretty_upgradable, #pretty_warning
Constructor Details
#initialize(data) ⇒ void
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
36 37 38 39 40 41 42 43 |
# File 'vulns/repology.rb', line 36 def initialize(data) super raise Error, "Repology index is not a JSON object" unless (top = as_hash(data)) raise Error, "Repology index missing 'formulae' key" unless (formulae = as_hash(top["formulae"])) @formulae = T.let(formulae, T::Hash[String, T.untyped]) @meta = T.let(as_hash(top["meta"]) || {}, T::Hash[String, T.untyped]) end |
Instance Attribute Details
#meta ⇒ Hash{String => T.untyped} (readonly)
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
46 47 48 |
# File 'vulns/repology.rb', line 46 def @meta end |
Class Method Details
.base_name(name) ⇒ String
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
33 |
# File 'vulns/repology.rb', line 33 def self.base_name(name) = name.sub(/@.+\z/, "") |
.cache_filename ⇒ String
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
25 |
# File 'vulns/repology.rb', line 25 def self.cache_filename = "repology.json" |
.data_url ⇒ String
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
22 |
# File 'vulns/repology.rb', line 22 def self.data_url = DATA_URL |
.default_max_age ⇒ Integer
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
28 |
# File 'vulns/repology.rb', line 28 def self.default_max_age = 7 * 86_400 |
.distil(entries) ⇒ DistroMap
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'vulns/repology.rb', line 190 def self.distil(entries) result = Hash.new { |h, k| h[k] = [] } entries.each do |entry| repo = entry["repo"] next unless repo.is_a?(String) distro = OSV_DISTROS.find { |prefix, _| repo.start_with?(prefix) }&.last next unless distro next if entry["status"] == "legacy" name = entry[distro[:name_field] || "srcname"] || entry["binname"] next unless name.is_a?(String) result[distro.fetch(:ecosystem)] << name end result.transform_values! { |names| names.uniq.sort.freeze } result.default = nil result.sort.to_h.freeze end |
.fetch_project(project) ⇒ Array<Hash{String => T.untyped}>
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
Fetch one Repology project. A nonexistent project returns HTTP 200 with
[], so an empty array is the only "try next candidate" signal;
transport failures, HTTP errors, malformed JSON and unexpected shapes
all raise so callers don't mistake an outage for "no packages".
177 178 179 180 181 182 183 184 185 186 187 |
# File 'vulns/repology.rb', line 177 def self.fetch_project(project) result = ::Repology.single_package_query(project, repository: ::Repology::HOMEBREW_CORE) raise Error, "Repology API request for #{project.inspect} failed" if result.nil? entries = result.fetch(project) if !entries.is_a?(Array) || !entries.all?(Hash) raise Error, "Repology API returned unexpected shape for #{project.inspect}" end entries end |
.homebrew_entries(entries) ⇒ Hash{String => Boolean}
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'vulns/repology.rb', line 146 def self.homebrew_entries(entries) result = {} entries.each do |e| next if e["repo"] != "homebrew" name = (e["srcname"] || e["binname"]).to_s next if name.empty? result[name] ||= false result[name] = true if PREFERRED_STATUSES.include?(e["status"]) end result end |
.lookup(formula_name) ⇒ DistroMap
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
Live single-project fallback for a formula the published index does
not cover: a new formula in a homebrew-core PR before the next nightly
index build, or one the index put in meta.ambiguous_projects.
Fetches each project in name_candidates, keeps those whose Homebrew
entries include formula_name (or its @-stripped base), then applies
the same preferred-status resolution as RepologyIndex#resolve across
the survivors. Unlike the index builder, a project that also lists
sibling formulae with a different base (wget + wget2, sqlite +
sqlite-analyzer, ffmpeg + a third-party ffmpeg-full) is not
rejected: the distro srcnames for the sibling flow through as extra
low-confidence distro queries whose upstream-CVE range check will not
match this formula's identity, so the cost is uncomparable noise rather
than a wrong :affected/:fixed claim. This cannot detect collisions
with projects outside name_candidates (e.g. allegro4), which only
the full crawl sees.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'vulns/repology.rb', line 112 def self.lookup(formula_name) base = base_name(formula_name) exact = [] by_base = [] name_candidates(formula_name).each do |candidate| entries = fetch_project(candidate) next if entries.empty? brew = homebrew_entries(entries) distros = distil(entries) next if distros.empty? # A project listing both the exact and base names contributes to both # pools, matching the producer's per-key contributions. exact << { preferred: brew.fetch(formula_name), distros: } if brew.key?(formula_name) by_base << { preferred: brew.fetch(base), distros: } if base != formula_name && brew.key?(base) end # Resolve the exact-name pool first, mirroring # `#distro_packages_for`'s `@formulae[name] || @formulae[base]` # precedence over the producer's per-key resolved index. resolve_contributions(exact) || resolve_contributions(by_base) || {} end |
.name_candidates(formula_name) ⇒ Array<String>
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
161 162 163 164 165 166 167 168 169 170 |
# File 'vulns/repology.rb', line 161 def self.name_candidates(formula_name) base = base_name(formula_name) [ formula_name, base, base.delete_prefix("lib"), base.delete_prefix("gnu-"), base.delete_suffix("2"), ].uniq.reject(&:empty?) end |
.resolve_contributions(contributions) ⇒ DistroMap?
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
140 141 142 143 |
# File 'vulns/repology.rb', line 140 def self.resolve_contributions(contributions) chosen = contributions.one? ? contributions : contributions.select { |c| c.fetch(:preferred) } chosen.fetch(0).fetch(:distros) if chosen.one? end |
Instance Method Details
#distro_packages_for(formula_name) ⇒ DistroMap
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
Returns {osv_ecosystem => [srcname, ...]} for formula_name, or an
empty hash if the index has no entry. The index is keyed on the
Homebrew formula name as Repology records it, so @-versioned
variants (postgresql@16) are looked up under their base name too.
58 59 60 61 62 63 64 65 66 67 68 |
# File 'vulns/repology.rb', line 58 def distro_packages_for(formula_name) entry = @formulae[formula_name] || @formulae[self.class.base_name(formula_name)] return {} unless entry.is_a?(Hash) entry.filter_map do |eco, names| next unless eco.is_a?(String) list = Array(names).grep(String) [eco, list.freeze] if list.any? end.to_h.freeze end |
#formulae ⇒ Array<String>
This method is part of a private API. This method may only be used in the Homebrew/brew repository. Third parties should avoid using this method if possible, as it may be removed or changed without warning.
49 50 51 |
# File 'vulns/repology.rb', line 49 def formulae @formulae.keys end |