brew-vulns 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 80d99be04bbb5f94ac9b6ce0572699c06291a38d3cad5643f40fcc5991a11bcf
4
- data.tar.gz: 50af431e418e7af72b006acbdbc3d88dcf177726f0796eb2ead936406c5242ba
3
+ metadata.gz: a4db816ba6a05913a6c043982fa279cd27c20637ee999737323480519c504ce5
4
+ data.tar.gz: 47a3baef2449740c693d43f09e90dfe87d5f9b1ab3b596cffeb5f2afaa0c0371
5
5
  SHA512:
6
- metadata.gz: c45915f201f9b804298836a2ccf16f815eeb48899253499c315ae74a7684e52da207b344682fe8b48af14afe4003bbcdeffb233a11a984cca5821554e687626d
7
- data.tar.gz: 4aea92e8df8fe2143d8e1f4268d0173c98d11f721ce1195b79e68c5c67e41357a2f9b96ded95d5b57d62baf821bf20e5cd43beebf18995fc039d8da6cb2b746c
6
+ metadata.gz: f11cd24893e2a5609b5a499932c9aa3d11d00e7579aeec82ad381945df16a2508a1b90b022f787a28959414f165d7e14e02c639ebcc593942151b14af6c6877b
7
+ data.tar.gz: 137ad2269080760fd54751bf662ec26283b4bc1054b0ed4ec683bb2a2aba88c194402a3e74b5f8a9911f6a47fe8ccb297bc1299d8c3ef6afa81ebd4178654b9a
data/CHANGELOG.md CHANGED
@@ -1,4 +1,10 @@
1
- ## [Unreleased]
1
+ ## [0.5.0] - 2026-07-14
2
+
3
+ - Deprecate in favour of the built-in `brew vulns` command shipped in Homebrew 6.0.11 (see [Homebrew/brew#23080](https://github.com/Homebrew/brew/pull/23080)). A deprecation notice is now printed to stderr on every run and after `gem install`. The formula is marked deprecated. This tap and gem will be archived after one release cycle.
4
+ - Query OSV for formulae whose source is not on GitHub/GitLab/Codeberg by falling back to the formula's `head` git URL, a `tag:`-style stable git URL, or a forge URL in `homepage`, with the bare formula version. Removes the forge whitelist; any formula with a derivable git repository URL is now sent in the batch query
5
+ - CycloneDX output: percent-encode `@` in `pkg:brew/` purls so versioned formula names like `glibc@2.13` are not misparsed as `name@version`
6
+ - Add `--osv-export DIR` (experimental) to write OSV-schema records under a `Homebrew` ecosystem for every CVE listed in a formula's `patches[].resolves`. Each record marks the formula as fixed at the currently shipped version+revision and carries the resolving patch detail in `ecosystem_specific`. Intended as the seed for a published Homebrew advisory feed.
7
+ - Retry the final OSV API attempt over IPv4 (via `Net::HTTP#ipaddr=`) when earlier attempts time out or fail to connect, to work around IPv6 paths that complete the TCP/TLS handshake but then drop data. Add `--ipv4` to force IPv4 from the first attempt.
2
8
 
3
9
  ## [0.4.0] - 2026-06-28
4
10
 
@@ -1,10 +1,12 @@
1
1
  class BrewVulns < Formula
2
2
  desc "Check Homebrew packages for known vulnerabilities via osv.dev"
3
3
  homepage "https://github.com/Homebrew/homebrew-brew-vulns"
4
- url "https://github.com/Homebrew/homebrew-brew-vulns/archive/refs/tags/v0.3.0.tar.gz"
5
- sha256 "1b9fbb03d46192350ad7dcac4279b382debb917d40688a4b816319214a2570d4"
4
+ url "https://github.com/Homebrew/homebrew-brew-vulns/archive/refs/tags/v0.5.0.tar.gz"
5
+ sha256 "f20b9dadb01d3ec8c4e97fe5e3225aac58ca609f935ce17928b23deabd415ba7"
6
6
  license "MIT"
7
7
 
8
+ deprecate! date: "2026-07-14", because: "is now built into Homebrew as `brew vulns`"
9
+
8
10
  depends_on "ruby"
9
11
 
10
12
  def install
@@ -19,6 +21,14 @@ class BrewVulns < Formula
19
21
  bin.env_script_all_files(libexec/"bin", GEM_HOME: ENV.fetch("GEM_HOME", nil))
20
22
  end
21
23
 
24
+ def caveats
25
+ <<~EOS
26
+ `brew vulns` is now a built-in Homebrew command. Run `brew update` and
27
+ use `brew vulns` directly, then `brew uninstall brew-vulns` and
28
+ `brew untap homebrew/brew-vulns`.
29
+ EOS
30
+ end
31
+
22
32
  test do
23
33
  system bin/"brew-vulns", "--help"
24
34
  end
data/README.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # brew-vulns
2
2
 
3
+ > [!WARNING]
4
+ > **Deprecated.** `brew vulns` is now a built-in Homebrew command as of Homebrew 6.0.11. Run `brew update` and use `brew vulns` directly; this tap and gem are no longer needed and will be archived. See [Homebrew/brew#23080](https://github.com/Homebrew/brew/pull/23080).
5
+ >
6
+ > Interface changes in the built-in command:
7
+ >
8
+ > - `--all` is dropped; with no arguments the built-in command scans every trusted formula (or just installed formulae if tap trust is disabled)
9
+ > - `-b` short flag is dropped; use `--brewfile`
10
+ > - `--sarif`, `--cyclonedx` and `--ipv4` are not available (SARIF and CycloneDX are planned as follow-ups)
11
+ > - `--osv-export` moved to `brew generate-vulns-advisories` ([Homebrew/brew#23106](https://github.com/Homebrew/brew/pull/23106)); its output lives at [Homebrew/advisory-database](https://github.com/Homebrew/advisory-database)
12
+ > - Errors no longer exit with code `2`; brew's standard error handling applies. Exit `0` (clean) and `1` (vulnerabilities found) are unchanged.
13
+ >
14
+ > To migrate: `brew uninstall brew-vulns && brew untap homebrew/brew-vulns` (or `gem uninstall brew-vulns`).
15
+
3
16
  A Homebrew subcommand that checks installed packages for known vulnerabilities using the [OSV.dev](https://osv.dev) database.
4
17
 
5
18
  ## Installation
@@ -91,11 +104,11 @@ brew vulns --help
91
104
  ## How it works
92
105
 
93
106
  1. Reads Homebrew formulae via `brew info --json=v2` (installed packages by default, or any named formulae passed as arguments)
94
- 2. Extracts the repository URL and version tag from each formula's source URL
107
+ 2. Derives a git repository URL for each formula from its stable source URL, `head` URL or homepage, and a version from the release tag or formula version
95
108
  3. Queries the OSV API using the GIT ecosystem to find known vulnerabilities
96
109
  4. Reports any vulnerabilities found with their severity and CVE identifiers
97
110
 
98
- Packages with GitHub, GitLab, or Codeberg source URLs are checked. Packages from other sources are skipped.
111
+ A formula is queried when a repository URL can be derived. GitHub, GitLab and Codeberg URLs are recognised in any of the stable, `head` or homepage fields; for other hosts, the `head` URL or a `tag:`-style stable git URL is used as-is. Formulae with none of these are skipped.
99
112
 
100
113
  ### Patched vulnerabilities
101
114
 
@@ -106,8 +119,8 @@ This relies on `patches[].resolves` data in `brew info --json=v2`, available fro
106
119
  ## Example output
107
120
 
108
121
  ```
109
- Checking 104 packages for vulnerabilities...
110
- (119 packages skipped - no supported source URL)
122
+ Checking 181 packages for vulnerabilities...
123
+ (42 packages skipped - no source repository URL or version)
111
124
 
112
125
  expat (2.7.3)
113
126
  CVE-2025-66382 (HIGH) - XML parsing vulnerability...
@@ -197,7 +210,7 @@ See [examples/](examples/) for workflows that check changed formulae on tap pull
197
210
 
198
211
  ```bash
199
212
  git clone https://github.com/Homebrew/homebrew-brew-vulns
200
- cd brew-vulns
213
+ cd homebrew-brew-vulns
201
214
  bin/setup
202
215
  rake test
203
216
  ```
@@ -18,7 +18,7 @@ jobs:
18
18
  steps:
19
19
  - name: Set up Homebrew
20
20
  id: set-up-homebrew
21
- uses: Homebrew/actions/setup-homebrew@main
21
+ uses: Homebrew/actions/setup-homebrew@1f8e202ffddf94def7f42f6fa3a482e821489f9c # 2026.07.10.1
22
22
 
23
23
  - name: Install brew-vulns
24
24
  run: brew install homebrew/brew-vulns/brew-vulns
@@ -16,7 +16,7 @@ jobs:
16
16
  contents: write
17
17
  steps:
18
18
  - name: Set up Homebrew
19
- uses: Homebrew/actions/setup-homebrew@main
19
+ uses: Homebrew/actions/setup-homebrew@1f8e202ffddf94def7f42f6fa3a482e821489f9c # 2026.07.10.1
20
20
 
21
21
  - name: Install brew-vulns
22
22
  run: brew install homebrew/brew-vulns/brew-vulns
data/exe/brew-vulns CHANGED
@@ -3,4 +3,6 @@
3
3
 
4
4
  require "brew/vulns"
5
5
 
6
+ warn Brew::Vulns::DEPRECATION_MESSAGE
7
+
6
8
  exit Brew::Vulns::CLI.run(ARGV)
@@ -5,12 +5,15 @@ module Brew
5
5
  class CLI
6
6
  def self.run(args)
7
7
  new(args).run
8
+ rescue Error => e
9
+ $stderr.puts "Error: #{e.message}"
10
+ 2
8
11
  end
9
12
 
10
13
  DEFAULT_MAX_SUMMARY = 60
11
14
  SEVERITY_LEVELS = { "low" => 1, "medium" => 2, "high" => 3, "critical" => 4 }.freeze
12
15
  MAX_VULN_FETCH_THREADS = 15
13
- FLAGS_WITH_VALUE = %w[-b --brewfile -m --max-summary -s --severity].freeze
16
+ FLAGS_WITH_VALUE = %w[-b --brewfile -m --max-summary -s --severity --osv-export].freeze
14
17
 
15
18
  def initialize(args)
16
19
  @args = args
@@ -25,6 +28,8 @@ module Brew
25
28
  @max_summary = parse_max_summary(args)
26
29
  @min_severity = parse_severity(args)
27
30
  @brewfile = parse_brewfile_path(args)
31
+ @osv_export_dir = parse_osv_export_dir(args)
32
+ @force_ipv4 = args.include?("--ipv4")
28
33
  end
29
34
 
30
35
  def parse_formula_names(args)
@@ -71,6 +76,23 @@ module Brew
71
76
  0
72
77
  end
73
78
 
79
+ def parse_osv_export_dir(args)
80
+ args.each_with_index do |arg, idx|
81
+ if arg == "--osv-export"
82
+ value = args[idx + 1]
83
+ return value if value && !value.empty? && !value.start_with?("-")
84
+
85
+ raise Error, "--osv-export requires a directory argument"
86
+ elsif arg.start_with?("--osv-export=")
87
+ value = arg.split("=", 2).last
88
+ raise Error, "--osv-export requires a directory argument" if value.empty?
89
+
90
+ return value
91
+ end
92
+ end
93
+ nil
94
+ end
95
+
74
96
  def parse_brewfile_path(args)
75
97
  args.each_with_index do |arg, idx|
76
98
  if arg == "--brewfile" || arg == "-b"
@@ -90,18 +112,20 @@ module Brew
90
112
  return 0
91
113
  end
92
114
 
115
+ return run_osv_export if @osv_export_dir
116
+
93
117
  formulae = load_formulae
94
118
  if formulae.empty?
95
119
  puts "No formulae found."
96
120
  return 0
97
121
  end
98
122
 
99
- queryable = formulae.select(&:supported_forge?).select(&:tag)
123
+ queryable = formulae.select(&:to_osv_query)
100
124
  skipped = formulae.size - queryable.size
101
125
 
102
126
  unless @json_output || @sarif_output || @cyclonedx_output
103
127
  puts "Checking #{queryable.size} packages for vulnerabilities..."
104
- puts "(#{skipped} packages skipped - no supported source URL)" if skipped > 0
128
+ puts "(#{skipped} packages skipped - no source repository URL or version)" if skipped > 0
105
129
  puts
106
130
  end
107
131
 
@@ -118,6 +142,28 @@ module Brew
118
142
  2
119
143
  end
120
144
 
145
+ # OSV export always operates on the full homebrew-core set (it generates
146
+ # an ecosystem-wide advisory feed), or on explicitly named formulae for
147
+ # testing. --brewfile / installed selection are intentionally ignored.
148
+ def run_osv_export
149
+ formulae = if @formula_names.any?
150
+ Formula.load_named(@formula_names)
151
+ else
152
+ Formula.load_all
153
+ end
154
+
155
+ annotated = formulae.select { |f| f.resolved_vulnerability_ids.any? }
156
+ $stderr.puts "#{annotated.size} formulae with security `resolves` annotations"
157
+
158
+ written = OsvExport.run(annotated, @osv_export_dir)
159
+ written.each { |p| $stderr.puts " wrote #{p}" }
160
+ $stderr.puts "#{written.size} records written to #{@osv_export_dir}"
161
+ 0
162
+ rescue Error => e
163
+ $stderr.puts "Error: #{e.message}"
164
+ 2
165
+ end
166
+
121
167
  private
122
168
 
123
169
  def load_formulae
@@ -133,7 +179,7 @@ module Brew
133
179
  end
134
180
 
135
181
  def scan_vulnerabilities(formulae)
136
- client = OsvClient.new
182
+ client = OsvClient.new(force_ipv4: @force_ipv4)
137
183
  queries = formulae.map(&:to_osv_query).compact
138
184
 
139
185
  vuln_results = client.query_batch(queries)
@@ -213,7 +259,7 @@ module Brew
213
259
  type: "library",
214
260
  name: formula.name,
215
261
  version: formula.version,
216
- purl: "pkg:brew/#{formula.name}@#{formula.version}",
262
+ purl: formula.purl,
217
263
  }
218
264
  pedigree = formula.cyclonedx_pedigree
219
265
  component[:pedigree] = pedigree if pedigree
@@ -255,7 +301,7 @@ module Brew
255
301
  source: { name: "OSV", url: "https://osv.dev" },
256
302
  ratings: [{ severity: vuln.severity_display&.downcase }],
257
303
  description: vuln.summary,
258
- affects: [{ ref: "pkg:brew/#{formula.name}@#{formula.version}" }],
304
+ affects: [{ ref: formula.purl }],
259
305
  }
260
306
  end
261
307
 
@@ -421,8 +467,10 @@ module Brew
421
467
  -j, --json Output results as JSON
422
468
  --cyclonedx Output results as CycloneDX SBOM with vulnerabilities
423
469
  --sarif Output results as SARIF for GitHub code scanning
470
+ --osv-export DIR Write OSV-schema records for patch-resolved CVEs to DIR (experimental)
424
471
  -m, --max-summary N Truncate summaries to N characters (default: 60, 0 for no limit)
425
472
  -s, --severity LEVEL Only show vulnerabilities at or above LEVEL (low, medium, high, critical)
473
+ --ipv4 Connect to api.osv.dev over IPv4 only
426
474
  -h, --help Show this help message
427
475
 
428
476
  Examples:
@@ -438,6 +486,7 @@ module Brew
438
486
  brew vulns --cyclonedx Output as CycloneDX SBOM
439
487
  brew vulns --sarif Output as SARIF for GitHub Actions
440
488
  brew vulns --severity high Only show HIGH and CRITICAL vulnerabilities
489
+ brew vulns --osv-export ./advisories Generate Homebrew-ecosystem OSV records
441
490
  HELP
442
491
  end
443
492
  end
@@ -2,21 +2,47 @@
2
2
 
3
3
  require "json"
4
4
  require "open3"
5
+ require "purl"
5
6
 
6
7
  module Brew
7
8
  module Vulns
8
9
  class Formula
9
- attr_reader :name, :version, :source_url, :head_url, :dependencies, :patches
10
+ attr_reader :name, :version, :revision, :source_url, :head_url, :homepage, :dependencies, :patches
10
11
 
11
12
  def initialize(data)
12
13
  @name = data["name"] || data["full_name"]
13
14
  @version = data.dig("versions", "stable") || data["version"]
15
+ @revision = data["revision"].to_i
14
16
  @source_url = data.dig("urls", "stable", "url")
17
+ @stable_tag = data.dig("urls", "stable", "tag")
15
18
  @head_url = data.dig("urls", "head", "url")
19
+ @homepage = data["homepage"]
16
20
  @dependencies = data["dependencies"] || []
17
21
  @patches = data["patches"] || []
18
22
  end
19
23
 
24
+ # The Homebrew package version including the revision suffix, e.g. "1.81.6_6".
25
+ # This is the version string as it appears in `pkg:brew/` purls and bottle paths.
26
+ def full_version
27
+ revision.zero? ? version : "#{version}_#{revision}"
28
+ end
29
+
30
+ # Package URL for this formula. Uses the `purl` gem so that special
31
+ # characters in the formula name (notably `@` in versioned formulae like
32
+ # `glibc@2.13`) are percent-encoded; otherwise the `@` would be parsed as
33
+ # the version separator.
34
+ def purl(with_version: true)
35
+ Purl::PackageURL.new(type: "brew", name: name, version: with_version ? version : nil).to_s
36
+ end
37
+
38
+ # Patches whose `resolves` list contains `vuln_id` (case-insensitive).
39
+ def patches_resolving(vuln_id)
40
+ target = vuln_id.to_s.upcase
41
+ patches.select do |p|
42
+ Array(p["resolves"]).any? { |r| r.is_a?(Hash) && r["type"] == "security" && r["id"].to_s.upcase == target }
43
+ end
44
+ end
45
+
20
46
  # CVE/GHSA identifiers declared as resolved by this formula's patches.
21
47
  # Populated from `brew info --json=v2` `patches[].resolves[]` (Homebrew >= 6.0.4);
22
48
  # empty on older Homebrew versions or for formulae with no annotated patches.
@@ -66,32 +92,32 @@ module Brew
66
92
  { patches: cdx_patches }
67
93
  end
68
94
 
95
+ # The git repository URL to use as the OSV `GIT` ecosystem package name.
96
+ # In order of preference:
97
+ # 1. owner/repo extracted from a recognised forge URL on stable, head,
98
+ # or homepage. Known forges are preferred even over a canonical
99
+ # non-forge git URL, because OSV's GIT coverage is much better there.
100
+ # 2. the stable URL itself when stable is a git checkout (`url ..., tag: ...`)
101
+ # 3. the head URL verbatim (head specs are always git URLs)
102
+ # Returns nil when none of the above yield a URL.
69
103
  def repo_url
70
104
  return @repo_url if defined?(@repo_url)
71
105
 
72
- @repo_url = extract_repo_url(source_url) || extract_repo_url(head_url)
106
+ @repo_url = extract_repo_url(source_url) ||
107
+ extract_repo_url(head_url) ||
108
+ extract_repo_url(homepage) ||
109
+ (source_url if @stable_tag) ||
110
+ head_url
73
111
  end
74
112
 
113
+ # The version identifier to send as the OSV query `version`. Prefers an
114
+ # explicit git tag (from a `tag:` stable spec or parsed from a forge
115
+ # tarball URL); falls back to the bare formula version, which OSV's GIT
116
+ # ecosystem accepts for at least some upstreams.
75
117
  def tag
76
118
  return @tag if defined?(@tag)
77
119
 
78
- @tag = extract_tag_from_url(source_url)
79
- end
80
-
81
- def github?
82
- repo_url&.include?("github.com")
83
- end
84
-
85
- def gitlab?
86
- repo_url&.include?("gitlab.com")
87
- end
88
-
89
- def codeberg?
90
- repo_url&.include?("codeberg.org")
91
- end
92
-
93
- def supported_forge?
94
- github? || gitlab? || codeberg?
120
+ @tag = @stable_tag || extract_tag_from_url(source_url) || version
95
121
  end
96
122
 
97
123
  def to_osv_query
@@ -3,6 +3,7 @@
3
3
  require "net/http"
4
4
  require "json"
5
5
  require "uri"
6
+ require "socket"
6
7
  require "brew/vulns/version"
7
8
 
8
9
  module Brew
@@ -20,6 +21,10 @@ module Brew
20
21
 
21
22
  USER_AGENT = "brew-vulns/#{Brew::Vulns::VERSION} (+https://github.com/Homebrew/homebrew-brew-vulns)"
22
23
 
24
+ def initialize(force_ipv4: false)
25
+ @force_ipv4 = force_ipv4
26
+ end
27
+
23
28
  def query(repo_url:, version:)
24
29
  payload = {
25
30
  package: {
@@ -86,6 +91,7 @@ module Brew
86
91
 
87
92
  def execute_request(uri, request)
88
93
  attempts = 0
94
+ ipv4_addr = resolve_ipv4(uri.host) if @force_ipv4
89
95
 
90
96
  begin
91
97
  attempts += 1
@@ -94,6 +100,7 @@ module Brew
94
100
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
95
101
  http.open_timeout = OPEN_TIMEOUT
96
102
  http.read_timeout = READ_TIMEOUT
103
+ http.ipaddr = ipv4_addr if ipv4_addr
97
104
 
98
105
  response = http.request(request)
99
106
 
@@ -107,12 +114,14 @@ module Brew
107
114
  raise ApiError, "Invalid JSON response from OSV API: #{e.message}"
108
115
  rescue Net::OpenTimeout, Net::ReadTimeout => e
109
116
  if attempts < MAX_RETRIES
117
+ ipv4_addr ||= resolve_ipv4(uri.host) if attempts == MAX_RETRIES - 1
110
118
  sleep RETRY_DELAY
111
119
  retry
112
120
  end
113
121
  raise ApiError, "OSV API timeout after #{attempts} attempts: #{e.message}"
114
122
  rescue SocketError, Errno::ECONNREFUSED => e
115
123
  if attempts < MAX_RETRIES
124
+ ipv4_addr ||= resolve_ipv4(uri.host) if attempts == MAX_RETRIES - 1
116
125
  sleep RETRY_DELAY
117
126
  retry
118
127
  end
@@ -122,6 +131,15 @@ module Brew
122
131
  end
123
132
  end
124
133
 
134
+ # Resolve the first IPv4 address for `host`, for use with Net::HTTP#ipaddr=
135
+ # to bypass a misbehaving IPv6 path. Returns nil (so the caller falls back
136
+ # to default resolution) if no A record is found or DNS fails.
137
+ def resolve_ipv4(host)
138
+ Addrinfo.getaddrinfo(host, nil, Socket::AF_INET, Socket::SOCK_STREAM).first&.ip_address
139
+ rescue SocketError
140
+ nil
141
+ end
142
+
125
143
  MAX_PAGES = 100
126
144
 
127
145
  def fetch_all_pages(response, original_payload)
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "time"
5
+ require "fileutils"
6
+
7
+ module Brew
8
+ module Vulns
9
+ # Emits OSV-schema records for a `Homebrew` ecosystem describing CVEs that
10
+ # homebrew-core formulae resolve via shipped patches.
11
+ #
12
+ # One record is written per (formula, vulnerability id) pair found in
13
+ # `patches[].resolves`. The record states that the formula was affected up
14
+ # to (but not including) the currently shipped version+revision; this is the
15
+ # "fixed at or before what we ship today" approximation, since the precise
16
+ # fix boundary requires homebrew-core git archaeology.
17
+ #
18
+ # Record shape follows the OSV 1.7 schema and mirrors the Debian DSA layout
19
+ # (`upstream` listing the source CVE, `affected[].ranges` of type
20
+ # `ECOSYSTEM`, `ecosystem_specific` carrying the patch detail).
21
+ module OsvExport
22
+ SCHEMA_VERSION = "1.7.3"
23
+ ECOSYSTEM = "Homebrew"
24
+ ID_PREFIX = "BREW"
25
+
26
+ module_function
27
+
28
+ def run(formulae, dir, client: OsvClient.new, now: Time.now.utc)
29
+ FileUtils.mkdir_p(dir)
30
+ written = []
31
+
32
+ formulae.each do |formula|
33
+ formula.resolved_vulnerability_ids.each do |vuln_id|
34
+ upstream = fetch_upstream(client, vuln_id)
35
+ record = record_for(formula, vuln_id, upstream: upstream, now: now)
36
+ path = File.join(dir, "#{record[:id]}.json")
37
+ File.write(path, JSON.pretty_generate(record))
38
+ written << path
39
+ end
40
+ end
41
+
42
+ written
43
+ end
44
+
45
+ def record_for(formula, vuln_id, upstream: nil, now: Time.now.utc)
46
+ record = {
47
+ schema_version: SCHEMA_VERSION,
48
+ id: record_id(formula, vuln_id),
49
+ modified: now.strftime("%Y-%m-%dT%H:%M:%SZ"),
50
+ upstream: [vuln_id],
51
+ affected: [affected_entry(formula, vuln_id)],
52
+ }
53
+
54
+ if upstream
55
+ record[:summary] = upstream["summary"] if upstream["summary"]
56
+ record[:details] = upstream["details"] if upstream["details"]
57
+ record[:severity] = upstream["severity"] if upstream["severity"]
58
+ record[:upstream] = ([vuln_id] + Array(upstream["aliases"])).uniq
59
+ record[:references] = upstream["references"] if upstream["references"]
60
+ end
61
+
62
+ record
63
+ end
64
+
65
+ def record_id(formula, vuln_id)
66
+ "#{ID_PREFIX}-#{formula.name}-#{vuln_id}"
67
+ end
68
+
69
+ def affected_entry(formula, vuln_id)
70
+ {
71
+ package: {
72
+ ecosystem: ECOSYSTEM,
73
+ name: formula.name,
74
+ purl: formula.purl(with_version: false),
75
+ },
76
+ ranges: [
77
+ {
78
+ type: "ECOSYSTEM",
79
+ events: [
80
+ { introduced: "0" },
81
+ { fixed: formula.full_version },
82
+ ],
83
+ },
84
+ ],
85
+ ecosystem_specific: {
86
+ fix: "patch",
87
+ patches: formula.patches_resolving(vuln_id).filter_map { |p| patch_ref(p) },
88
+ },
89
+ }
90
+ end
91
+
92
+ def patch_ref(patch)
93
+ ref = {}
94
+ ref[:type] = patch["type"] if patch["type"]
95
+ ref[:url] = patch["url"] if patch["url"]
96
+ ref[:file] = patch["file"] if patch["file"]
97
+ ref[:apply] = patch["apply"] if patch["apply"]
98
+ ref[:data] = true if patch["data"]
99
+ ref.empty? ? nil : ref
100
+ end
101
+
102
+ def fetch_upstream(client, vuln_id)
103
+ client.get_vulnerability(vuln_id)
104
+ rescue OsvClient::Error
105
+ nil
106
+ end
107
+ end
108
+ end
109
+ end
@@ -2,6 +2,13 @@
2
2
 
3
3
  module Brew
4
4
  module Vulns
5
- VERSION = "0.4.0"
5
+ VERSION = "0.5.0"
6
+
7
+ DEPRECATION_MESSAGE = <<~MSG.freeze
8
+ brew-vulns is deprecated: `brew vulns` is now built into Homebrew.
9
+ Run `brew update` and use `brew vulns` directly, then remove this
10
+ package with `brew uninstall brew-vulns` or `gem uninstall brew-vulns`.
11
+ See https://github.com/Homebrew/brew/pull/23080 for details.
12
+ MSG
6
13
  end
7
14
  end
data/lib/brew/vulns.rb CHANGED
@@ -7,6 +7,7 @@ require_relative "vulns/version"
7
7
  require_relative "vulns/osv_client"
8
8
  require_relative "vulns/formula"
9
9
  require_relative "vulns/vulnerability"
10
+ require_relative "vulns/osv_export"
10
11
  require_relative "vulns/cli"
11
12
 
12
13
  module Brew
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brew-vulns
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Nesbitt
@@ -110,6 +110,7 @@ files:
110
110
  - lib/brew/vulns/cli.rb
111
111
  - lib/brew/vulns/formula.rb
112
112
  - lib/brew/vulns/osv_client.rb
113
+ - lib/brew/vulns/osv_export.rb
113
114
  - lib/brew/vulns/version.rb
114
115
  - lib/brew/vulns/vulnerability.rb
115
116
  - sig/brew/vulns.rbs
@@ -120,6 +121,11 @@ metadata:
120
121
  homepage_uri: https://github.com/Homebrew/homebrew-brew-vulns
121
122
  source_code_uri: https://github.com/Homebrew/homebrew-brew-vulns
122
123
  changelog_uri: https://github.com/Homebrew/homebrew-brew-vulns/blob/main/CHANGELOG.md
124
+ post_install_message: |
125
+ brew-vulns is deprecated: `brew vulns` is now built into Homebrew.
126
+ Run `brew update` and use `brew vulns` directly, then remove this
127
+ package with `brew uninstall brew-vulns` or `gem uninstall brew-vulns`.
128
+ See https://github.com/Homebrew/brew/pull/23080 for details.
123
129
  rdoc_options: []
124
130
  require_paths:
125
131
  - lib
@@ -134,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
140
  - !ruby/object:Gem::Version
135
141
  version: '0'
136
142
  requirements: []
137
- rubygems_version: 4.0.10
143
+ rubygems_version: 4.0.12
138
144
  specification_version: 4
139
145
  summary: Check Homebrew packages for known vulnerabilities
140
146
  test_files: []