Class: Homebrew::Vulns::AdvisoryDatabase Private

Inherits:
CachedFeed show all
Defined in:
vulns/advisory_database.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 concatenated BREW-* OSV corpus published by Homebrew/advisory-database at data/advisories.json (built by that repository's AdvisoryIndex via rake advisories:concat).

Consumed by brew generate-formula-api to attach a vulnerabilities field to each formula's API JSON, and by brew vulns (Phase 4) as the local ecosystem: Homebrew range source until osv.dev ingests the feed.

Defined Under Namespace

Classes: Entry

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/advisories.json"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from CachedFeed

#as_hash, default_max_age, 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:



27
28
29
30
31
32
33
34
35
36
37
# File 'vulns/advisory_database.rb', line 27

def initialize(data)
  super
  raise Error, "advisory index is not a JSON object" unless (top = as_hash(data))
  raise Error, "advisory index has no 'advisories' key" unless top.key?("advisories")
  unless (advisories = as_hash(top["advisories"]))
    raise Error, "advisory index 'advisories' is not a JSON object"
  end

  @advisories = T.let(advisories, 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:



40
41
42
# File 'vulns/advisory_database.rb', line 40

def meta
  @meta
end

Class Method Details

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



24
# File 'vulns/advisory_database.rb', line 24

def self.cache_filename = "advisories.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:



21
# File 'vulns/advisory_database.rb', line 21

def self.data_url = DATA_URL

Instance Method Details

#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:



43
44
45
# File 'vulns/advisory_database.rb', line 43

def formulae
  @advisories.keys
end

#records_for(formula_name) ⇒ Array<Vulnerability>

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.

Vulnerability wrappers for every BREW-* record whose affected[0].package.name is formula_name.

Parameters:

Returns:



50
51
52
53
54
# File 'vulns/advisory_database.rb', line 50

def records_for(formula_name)
  Array(@advisories[formula_name]).filter_map do |record|
    Vulnerability.new(record) if record.is_a?(Hash)
  end
end

#status_for(formula_name, pkg_version) ⇒ 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.

Evaluate every record for formula_name against pkg_version and return the {open:, patched:} shape used by the formula API JSON and brew info. open are records whose ECOSYSTEM range still contains pkg_version; patched are records where ecosystem_specific.fix is "patch" (Homebrew ships a resolves-annotated patch); fixed_count counts bump-fixed records that no longer apply. Returns nil when the corpus has no records for the formula so callers can distinguish "checked, clean" from "not covered".

Parameters:

Returns:



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'vulns/advisory_database.rb', line 75

def status_for(formula_name, pkg_version)
  records = records_for(formula_name)
  return if records.empty?

  version = pkg_version.to_s
  open = T.let([], T::Array[Entry])
  patched = T.let([], T::Array[Entry])
  fixed_count = 0

  records.each do |vuln|
    eco = vuln.affected.first&.dig("ecosystem_specific") || {}
    status = vuln.range_status("Homebrew", formula_name, version)
    entry = Entry.new(
      id:       vuln.id,
      upstream: vuln.upstream.presence || vuln.aliases.presence,
      summary:  vuln.summary,
      severity: vuln.severity&.to_s,
      fix:      eco["fix"],
      fixed_in: status&.fixed_in,
    ).freeze
    case status&.state
    when nil, :affected then open << entry
    when :fixed
      if eco["fix"] == "patch"
        patched << entry
      else
        fixed_count += 1
      end
    when :not_applicable then next
    end
  end

  {
    "open"        => open.sort_by(&:id).map(&:to_api_hash),
    "patched"     => patched.sort_by(&:id).map(&:to_api_hash),
    "fixed_count" => fixed_count,
  }
end