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.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#patches ⇒ Object
readonly
Returns the value of attribute patches.
-
#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
- #codeberg? ⇒ Boolean
-
#cyclonedx_pedigree ⇒ Object
Maps
brew info --json=v2patch data to a CycloneDXpedigreehash (symbol-keyed for thesbomgem). - #github? ⇒ Boolean
- #gitlab? ⇒ Boolean
-
#initialize(data) ⇒ Formula
constructor
A new instance of Formula.
- #repo_url ⇒ Object
-
#resolved_vulnerability_ids ⇒ Object
CVE/GHSA identifiers declared as resolved by this formula's patches.
- #resolves?(vulnerability) ⇒ Boolean
- #supported_forge? ⇒ Boolean
- #tag ⇒ Object
- #to_osv_query ⇒ Object
Constructor Details
#initialize(data) ⇒ Formula
Returns a new instance of Formula.
11 12 13 14 15 16 17 18 |
# File 'lib/brew/vulns/formula.rb', line 11 def initialize(data) @name = data["name"] || data["full_name"] @version = data.dig("versions", "stable") || data["version"] @source_url = data.dig("urls", "stable", "url") @head_url = data.dig("urls", "head", "url") @dependencies = data["dependencies"] || [] @patches = data["patches"] || [] end |
Instance Attribute Details
#dependencies ⇒ Object (readonly)
Returns the value of attribute dependencies.
9 10 11 |
# File 'lib/brew/vulns/formula.rb', line 9 def dependencies @dependencies end |
#head_url ⇒ Object (readonly)
Returns the value of attribute head_url.
9 10 11 |
# File 'lib/brew/vulns/formula.rb', line 9 def head_url @head_url end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
9 10 11 |
# File 'lib/brew/vulns/formula.rb', line 9 def name @name end |
#patches ⇒ Object (readonly)
Returns the value of attribute patches.
9 10 11 |
# File 'lib/brew/vulns/formula.rb', line 9 def patches @patches end |
#source_url ⇒ Object (readonly)
Returns the value of attribute source_url.
9 10 11 |
# File 'lib/brew/vulns/formula.rb', line 9 def source_url @source_url end |
#version ⇒ Object (readonly)
Returns the value of attribute version.
9 10 11 |
# File 'lib/brew/vulns/formula.rb', line 9 def version @version end |
Class Method Details
.load_all ⇒ Object
103 104 105 106 107 108 109 |
# File 'lib/brew/vulns/formula.rb', line 103 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
149 150 151 152 153 154 |
# File 'lib/brew/vulns/formula.rb', line 149 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
111 112 113 114 115 116 117 |
# File 'lib/brew/vulns/formula.rb', line 111 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
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/brew/vulns/formula.rb', line 119 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
156 157 158 159 160 161 |
# File 'lib/brew/vulns/formula.rb', line 156 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
#codeberg? ⇒ Boolean
89 90 91 |
# File 'lib/brew/vulns/formula.rb', line 89 def codeberg? repo_url&.include?("codeberg.org") end |
#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.
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/brew/vulns/formula.rb', line 49 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 |
#github? ⇒ Boolean
81 82 83 |
# File 'lib/brew/vulns/formula.rb', line 81 def github? repo_url&.include?("github.com") end |
#gitlab? ⇒ Boolean
85 86 87 |
# File 'lib/brew/vulns/formula.rb', line 85 def gitlab? repo_url&.include?("gitlab.com") end |
#repo_url ⇒ Object
69 70 71 72 73 |
# File 'lib/brew/vulns/formula.rb', line 69 def repo_url return @repo_url if defined?(@repo_url) @repo_url = extract_repo_url(source_url) || extract_repo_url(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.
23 24 25 26 27 28 29 30 31 32 |
# File 'lib/brew/vulns/formula.rb', line 23 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
34 35 36 37 38 39 |
# File 'lib/brew/vulns/formula.rb', line 34 def resolves?(vulnerability) ids = resolved_vulnerability_ids return false if ids.empty? vulnerability.identifiers.any? { |id| ids.include?(id.to_s.upcase) } end |
#supported_forge? ⇒ Boolean
93 94 95 |
# File 'lib/brew/vulns/formula.rb', line 93 def supported_forge? github? || gitlab? || codeberg? end |
#tag ⇒ Object
75 76 77 78 79 |
# File 'lib/brew/vulns/formula.rb', line 75 def tag return @tag if defined?(@tag) @tag = extract_tag_from_url(source_url) end |
#to_osv_query ⇒ Object
97 98 99 100 101 |
# File 'lib/brew/vulns/formula.rb', line 97 def to_osv_query return nil unless repo_url && tag { repo_url: repo_url, version: tag, name: name } end |