Class: Brew::Vulns::Formula
- Inherits:
-
Object
- Object
- Brew::Vulns::Formula
- Defined in:
- lib/brew/vulns/formula.rb
Constant Summary collapse
- CYCLONEDX_PATCH_TYPES =
{ "backport" => "backport", "cherry_pick" => "cherry-pick", "unofficial" => "unofficial", }.freeze
Instance Attribute Summary collapse
-
#dependencies ⇒ Object
readonly
Returns the value of attribute dependencies.
-
#head_url ⇒ Object
readonly
Returns the value of attribute head_url.
-
#homepage ⇒ Object
readonly
Returns the value of attribute homepage.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#patches ⇒ Object
readonly
Returns the value of attribute patches.
-
#revision ⇒ Object
readonly
Returns the value of attribute revision.
-
#source_url ⇒ Object
readonly
Returns the value of attribute source_url.
-
#version ⇒ Object
readonly
Returns the value of attribute version.
Class Method Summary collapse
- .load_all ⇒ Object
- .load_from_brewfile(brewfile_path, include_deps: false) ⇒ Object
- .load_installed ⇒ Object
- .load_named(formula_names, include_deps: false) ⇒ Object
- .parse_brewfile(brewfile_path) ⇒ Object
Instance Method Summary collapse
-
#cyclonedx_pedigree ⇒ Object
Maps
brew info --json=v2patch data to a CycloneDXpedigreehash (symbol-keyed for thesbomgem). -
#full_version ⇒ Object
The Homebrew package version including the revision suffix, e.g.
-
#initialize(data) ⇒ Formula
constructor
A new instance of Formula.
-
#patches_resolving(vuln_id) ⇒ Object
Patches whose
resolveslist containsvuln_id(case-insensitive). -
#purl(with_version: true) ⇒ Object
Package URL for this formula.
-
#repo_url ⇒ Object
The git repository URL to use as the OSV
GITecosystem package name. -
#resolved_vulnerability_ids ⇒ Object
CVE/GHSA identifiers declared as resolved by this formula's patches.
- #resolves?(vulnerability) ⇒ Boolean
-
#tag ⇒ Object
The version identifier to send as the OSV query
version. - #to_osv_query ⇒ Object
Constructor Details
#initialize(data) ⇒ Formula
Returns a new instance of Formula.
12 13 14 15 16 17 18 19 20 21 22 |
# File 'lib/brew/vulns/formula.rb', line 12 def initialize(data) @name = data["name"] || data["full_name"] @version = data.dig("versions", "stable") || data["version"] @revision = data["revision"].to_i @source_url = data.dig("urls", "stable", "url") @stable_tag = data.dig("urls", "stable", "tag") @head_url = data.dig("urls", "head", "url") @homepage = data["homepage"] @dependencies = data["dependencies"] || [] @patches = data["patches"] || [] end |
Instance Attribute Details
#dependencies ⇒ Object (readonly)
Returns the value of attribute dependencies.
10 11 12 |
# File 'lib/brew/vulns/formula.rb', line 10 def dependencies @dependencies end |
#head_url ⇒ Object (readonly)
Returns the value of attribute head_url.
10 11 12 |
# File 'lib/brew/vulns/formula.rb', line 10 def head_url @head_url end |
#homepage ⇒ Object (readonly)
Returns the value of attribute homepage.
10 11 12 |
# File 'lib/brew/vulns/formula.rb', line 10 def homepage @homepage end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
10 11 12 |
# File 'lib/brew/vulns/formula.rb', line 10 def name @name end |
#patches ⇒ Object (readonly)
Returns the value of attribute patches.
10 11 12 |
# File 'lib/brew/vulns/formula.rb', line 10 def patches @patches end |
#revision ⇒ Object (readonly)
Returns the value of attribute revision.
10 11 12 |
# File 'lib/brew/vulns/formula.rb', line 10 def revision @revision end |
#source_url ⇒ Object (readonly)
Returns the value of attribute source_url.
10 11 12 |
# File 'lib/brew/vulns/formula.rb', line 10 def source_url @source_url end |
#version ⇒ Object (readonly)
Returns the value of attribute version.
10 11 12 |
# File 'lib/brew/vulns/formula.rb', line 10 def version @version end |
Class Method Details
.load_all ⇒ Object
129 130 131 132 133 134 135 |
# File 'lib/brew/vulns/formula.rb', line 129 def self.load_all json, status = Open3.capture2("brew", "info", "--json=v2", "--eval-all") raise Error, "brew info failed with status #{status.exitstatus}" unless status.success? data = JSON.parse(json) data["formulae"].map { |f| new(f) } end |
.load_from_brewfile(brewfile_path, include_deps: false) ⇒ Object
175 176 177 178 179 180 |
# File 'lib/brew/vulns/formula.rb', line 175 def self.load_from_brewfile(brewfile_path, include_deps: false) raise Error, "Brewfile not found: #{brewfile_path}" unless File.exist?(brewfile_path) formula_names = parse_brewfile(brewfile_path) load_named(formula_names, include_deps: include_deps) end |
.load_installed ⇒ Object
137 138 139 140 141 142 143 |
# File 'lib/brew/vulns/formula.rb', line 137 def self.load_installed json, status = Open3.capture2("brew", "info", "--json=v2", "--installed") raise Error, "brew info failed with status #{status.exitstatus}" unless status.success? data = JSON.parse(json) data["formulae"].map { |f| new(f) } end |
.load_named(formula_names, include_deps: false) ⇒ Object
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/brew/vulns/formula.rb', line 145 def self.load_named(formula_names, include_deps: false) return [] if formula_names.empty? json, status = Open3.capture2("brew", "info", "--json=v2", *formula_names) raise Error, "brew info failed with status #{status.exitstatus}" unless status.success? data = JSON.parse(json) formulae = data["formulae"].map { |f| new(f) } if include_deps dep_names = [] formula_names.each do |name| deps_output, = Open3.capture2("brew", "deps", name) dep_names.concat(deps_output.split("\n").map(&:strip)) end dep_names.uniq! dep_names -= formula_names if dep_names.any? deps_json, deps_status = Open3.capture2("brew", "info", "--json=v2", *dep_names) if deps_status.success? deps_data = JSON.parse(deps_json) formulae.concat(deps_data["formulae"].map { |f| new(f) }) end end end formulae.uniq { |f| f.name } end |
.parse_brewfile(brewfile_path) ⇒ Object
182 183 184 185 186 187 |
# File 'lib/brew/vulns/formula.rb', line 182 def self.parse_brewfile(brewfile_path) output, status = Open3.capture2("brew", "bundle", "list", "--file=#{brewfile_path}", "--formula") raise Error, "brew bundle list failed with status #{status.exitstatus}" unless status.success? output.split("\n").map(&:strip).reject(&:empty?) end |
Instance Method Details
#cyclonedx_pedigree ⇒ Object
Maps brew info --json=v2 patch data to a CycloneDX pedigree hash
(symbol-keyed for the sbom gem). Returns nil when there are no patches.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/brew/vulns/formula.rb', line 75 def cyclonedx_pedigree return nil if patches.empty? cdx_patches = patches.map do |p| patch = { type: CYCLONEDX_PATCH_TYPES.fetch(p["type"].to_s, "unofficial") } patch[:diff] = { url: p["url"] } if p["url"] resolves = Array(p["resolves"]).filter_map do |r| next unless r.is_a?(Hash) && r["type"] && r["id"] { type: r["type"], id: r["id"] } end patch[:resolves] = resolves if resolves.any? patch end { patches: cdx_patches } end |
#full_version ⇒ Object
The Homebrew package version including the revision suffix, e.g. "1.81.6_6".
This is the version string as it appears in pkg:brew/ purls and bottle paths.
26 27 28 |
# File 'lib/brew/vulns/formula.rb', line 26 def full_version revision.zero? ? version : "#{version}_#{revision}" end |
#patches_resolving(vuln_id) ⇒ Object
Patches whose resolves list contains vuln_id (case-insensitive).
39 40 41 42 43 44 |
# File 'lib/brew/vulns/formula.rb', line 39 def patches_resolving(vuln_id) target = vuln_id.to_s.upcase patches.select do |p| Array(p["resolves"]).any? { |r| r.is_a?(Hash) && r["type"] == "security" && r["id"].to_s.upcase == target } end end |
#purl(with_version: true) ⇒ Object
Package URL for this formula. Uses the purl gem so that special
characters in the formula name (notably @ in versioned formulae like
glibc@2.13) are percent-encoded; otherwise the @ would be parsed as
the version separator.
34 35 36 |
# File 'lib/brew/vulns/formula.rb', line 34 def purl(with_version: true) Purl::PackageURL.new(type: "brew", name: name, version: with_version ? version : nil).to_s end |
#repo_url ⇒ Object
The git repository URL to use as the OSV GIT ecosystem package name.
In order of preference:
1. owner/repo extracted from a recognised forge URL on stable, head,
or homepage. Known forges are preferred even over a canonical
non-forge git URL, because OSV's GIT coverage is much better there.
2. the stable URL itself when stable is a git checkout (`url ..., tag: ...`)
3. the head URL verbatim (head specs are always git URLs)
Returns nil when none of the above yield a URL.
103 104 105 106 107 108 109 110 111 |
# File 'lib/brew/vulns/formula.rb', line 103 def repo_url return @repo_url if defined?(@repo_url) @repo_url = extract_repo_url(source_url) || extract_repo_url(head_url) || extract_repo_url(homepage) || (source_url if @stable_tag) || head_url end |
#resolved_vulnerability_ids ⇒ Object
CVE/GHSA identifiers declared as resolved by this formula's patches.
Populated from brew info --json=v2 patches[].resolves[] (Homebrew >= 6.0.4);
empty on older Homebrew versions or for formulae with no annotated patches.
49 50 51 52 53 54 55 56 57 58 |
# File 'lib/brew/vulns/formula.rb', line 49 def resolved_vulnerability_ids return @resolved_vulnerability_ids if defined?(@resolved_vulnerability_ids) @resolved_vulnerability_ids = patches .flat_map { |p| Array(p["resolves"]) } .select { |r| r.is_a?(Hash) && r["type"] == "security" } .map { |r| r["id"].to_s.upcase } .reject(&:empty?) .uniq end |
#resolves?(vulnerability) ⇒ Boolean
60 61 62 63 64 65 |
# File 'lib/brew/vulns/formula.rb', line 60 def resolves?(vulnerability) ids = resolved_vulnerability_ids return false if ids.empty? vulnerability.identifiers.any? { |id| ids.include?(id.to_s.upcase) } end |
#tag ⇒ Object
The version identifier to send as the OSV query version. Prefers an
explicit git tag (from a tag: stable spec or parsed from a forge
tarball URL); falls back to the bare formula version, which OSV's GIT
ecosystem accepts for at least some upstreams.
117 118 119 120 121 |
# File 'lib/brew/vulns/formula.rb', line 117 def tag return @tag if defined?(@tag) @tag = @stable_tag || extract_tag_from_url(source_url) || version end |
#to_osv_query ⇒ Object
123 124 125 126 127 |
# File 'lib/brew/vulns/formula.rb', line 123 def to_osv_query return nil unless repo_url && tag { repo_url: repo_url, version: tag, name: name } end |