Class: Homebrew::Vulns::Repology Private

Inherits:
CachedFeed show all
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

Class Method Summary collapse

Instance Method Summary collapse

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.

Parameters:

  • data (T.anything)

Raises:



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

#metaHash{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.

Returns:



46
47
48
# File 'vulns/repology.rb', line 46

def meta
  @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.

Parameters:

Returns:



33
# File 'vulns/repology.rb', line 33

def self.base_name(name) = name.sub(/@.+\z/, "")

.cache_filenameString

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:



25
# File 'vulns/repology.rb', line 25

def self.cache_filename = "repology.json"

.data_urlString

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:



22
# File 'vulns/repology.rb', line 22

def self.data_url = DATA_URL

.default_max_ageInteger

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:



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.

Parameters:

Returns:



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".

Parameters:

Returns:

Raises:



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.

Parameters:

Returns:



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.

Parameters:

Returns:



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.

Parameters:

Returns:



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.

Parameters:

Returns:



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.

Parameters:

Returns:



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

#formulaeArray<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.

Returns:



49
50
51
# File 'vulns/repology.rb', line 49

def formulae
  @formulae.keys
end