Module: Homebrew::Vulns::OsvExport Private

Defined in:
vulns/osv_export.rb

Overview

This module is part of a private API. This module may only be used in the Homebrew/brew repository. Third parties should avoid using this module if possible, as it may be removed or changed without warning.

Emits OSV-schema records for the Homebrew ecosystem describing CVEs that homebrew-core formulae resolve via shipped patches.

One record is written per (formula, vulnerability id) pair found in serialized_patches[].resolves. The record states that the formula was affected up to (but not including) the currently shipped version+revision; this is a "fixed at or before what we ship today" approximation, since the precise fix boundary requires homebrew-core git archaeology.

Record shape follows the OSV 1.7 schema and mirrors the Debian DSA layout (upstream listing the source CVE, affected[].ranges of type ECOSYSTEM, ecosystem_specific carrying the resolving patch detail).

See DevCmd::GenerateVulnsAdvisories for the entry point and https://github.com/Homebrew/homebrew-advisory-database for the published feed.

Constant Summary collapse

SCHEMA_VERSION =

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://ossf.github.io/osv-schema/ — value of the emitted schema_version field, pinning the OSV schema release these records target. Homebrew and BREW were registered in that schema in ossf/osv-schema#576.

"1.7.3"
ECOSYSTEM =

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.

"Homebrew"
ID_PREFIX =

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.

"BREW"
PatchRef =

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[Symbol, T.any(String, T::Array[String])] }

Class Method Summary collapse

Class Method Details

.affected_entry(formula, vuln_id, patches) ⇒ Hash{Symbol => 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.

Parameters:

Returns:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'vulns/osv_export.rb', line 93

def self.affected_entry(formula, vuln_id, patches)
  {
    package:            {
      ecosystem: ECOSYSTEM,
      name:      formula.name,
      purl:      purl(formula.name),
    },
    ranges:             [
      {
        type:   "ECOSYSTEM",
        events: [{ introduced: "0" }, { fixed: formula.pkg_version.to_s }],
      },
    ],
    ecosystem_specific: {
      fix:     "patch",
      patches: patches_resolving(patches, vuln_id).filter_map { |p| patch_ref(p) },
    },
  }
end

.fetch_upstream(vuln_id) ⇒ 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.

Parameters:

Returns:



149
150
151
152
153
# File 'vulns/osv_export.rb', line 149

def self.fetch_upstream(vuln_id)
  OSV.vulnerability(vuln_id)
rescue OSV::Error
  nil
end

.patch_ref(patch) ⇒ PatchRef?

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:



139
140
141
142
143
144
145
146
# File 'vulns/osv_export.rb', line 139

def self.patch_ref(patch)
  ref = T.let({}, PatchRef)
  ref[:type] = patch["type"] if patch["type"]
  ref[:url] = patch["url"] if patch["url"]
  ref[:file] = patch["file"] if patch["file"]
  ref[:apply] = patch["apply"] if patch["apply"]
  ref.presence
end

.patches_resolving(serialized_patches, vuln_id) ⇒ 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.

Parameters:

Returns:



129
130
131
132
133
134
# File 'vulns/osv_export.rb', line 129

def self.patches_resolving(serialized_patches, vuln_id)
  target = vuln_id.upcase
  serialized_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(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:



121
122
123
# File 'vulns/osv_export.rb', line 121

def self.purl(name)
  "pkg:brew/#{name.gsub(/[@+]/, PURL_NAME_ENCODE)}"
end

.record_for(formula, vuln_id, patches: formula.serialized_patches, upstream: nil, now: Time.now.utc) ⇒ Hash{Symbol => 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.

Parameters:

  • formula (Formula)
  • vuln_id (String)
  • patches (Array<Hash{String => T.untyped}>) (defaults to: formula.serialized_patches)
  • upstream (Hash{String => T.untyped}, nil) (defaults to: nil)
  • now (Time) (defaults to: Time.now.utc)

Returns:



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'vulns/osv_export.rb', line 69

def self.record_for(formula, vuln_id, patches: formula.serialized_patches, upstream: nil, now: Time.now.utc)
  record = T.let({
    schema_version: SCHEMA_VERSION,
    id:             "#{ID_PREFIX}-#{formula.name}-#{vuln_id}",
    modified:       now.strftime("%Y-%m-%dT%H:%M:%SZ"),
    upstream:       [vuln_id],
    affected:       [affected_entry(formula, vuln_id, patches)],
  }, T::Hash[Symbol, T.untyped])

  if upstream
    record[:summary] = upstream["summary"] if upstream["summary"]
    record[:details] = upstream["details"] if upstream["details"]
    record[:severity] = upstream["severity"] if upstream["severity"]
    record[:upstream] = ([vuln_id] + Array(upstream["aliases"])).uniq
    record[:references] = upstream["references"] if upstream["references"]
  end

  record
end

.run(annotated, dir, now: Time.now.utc) ⇒ 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.

annotated is a list of [formula, serialized_patches] pairs. The patches are passed in rather than read from the formula so callers can supply the union across OS/architecture variations (a patch inside an on_linux/on_intel block only appears in Formula#serialized_patches under the matching SimulateSystem).

Parameters:

Returns:



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'vulns/osv_export.rb', line 46

def self.run(annotated, dir, now: Time.now.utc)
  FileUtils.mkdir_p(dir)
  written = []
  upstream_cache = T.let({}, T::Hash[String, T.nilable(T::Hash[String, T.untyped])])

  annotated.each do |formula, patches|
    Scanner.resolved_ids(patches).each do |vuln_id|
      upstream = upstream_cache.fetch(vuln_id) { upstream_cache[vuln_id] = fetch_upstream(vuln_id) }
      record = record_for(formula, vuln_id, patches:, upstream:, now:)
      path = File.join(dir, "#{record.fetch(:id)}.json")
      File.write(path, "#{JSON.pretty_generate(record)}\n")
      written << path
    end
  end

  written
end