Module: Homebrew::Vulns::Identify Private

Defined in:
vulns/identify.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.

Derives OSV.dev query keys (forge repo URL, release tag) from formula source URLs. Shared between Scanner and the advisory-matching pipeline.

Defined Under Namespace

Classes: RegistryPackage

Class Method Summary collapse

Class Method Details

.decode(component) ⇒ 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.

Percent-decode a URL path segment. Unlike decode_www_form_component this leaves + alone and unlike decode_uri_component (missing from Sorbet's stdlib RBI) it never raises on malformed input.

Parameters:

Returns:



280
281
282
283
284
285
# File 'vulns/identify.rb', line 280

def self.decode(component)
  return component unless component.include?("%")

  component.b.gsub(/%[0-9A-Fa-f]{2}/) { |m| Integer(m[1, 2], 16).chr }
           .force_encoding(component.encoding)
end

.gem_name_version(basename) ⇒ Array<([String, nil], [String, nil])>

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.

Split a .gem basename into name and version, discarding any trailing Gem::Platform suffix (e.g. nokogiri-1.16.0-arm64-darwin-22).

Parameters:

Returns:



299
300
301
302
303
304
305
# File 'vulns/identify.rb', line 299

def self.gem_name_version(basename)
  deplatformed = basename.sub(GEM_PLATFORM_SUFFIX, "")
  name, sep, version = deplatformed.rpartition("-")
  return [nil, nil] if sep.empty? || !version.match?(/\A\d[\w.]*\z/)

  [name, version]
end

.registry_package(url) ⇒ RegistryPackage?

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:



135
136
137
138
139
140
141
142
143
# File 'vulns/identify.rb', line 135

def self.registry_package(url)
  return if url.nil?

  result = registry_purl(url)
  return if result.nil?

  ecosystem, purl = result
  registry_package_from_purl(ecosystem, purl)
end

.registry_package_for(ecosystem:, name:, version: nil) ⇒ RegistryPackage?

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.

Build a canonical registry package from reviewed OSV package fields. Returns nil for unsupported ecosystems or non-canonical package names.

Parameters:

Returns:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'vulns/identify.rb', line 150

def self.registry_package_for(ecosystem:, name:, version: nil)
  return unless REGISTRY_PURL_TYPES.key?(ecosystem)
  return if name.empty? || name.match?(/\s/)

  namespace = T.let(nil, T.nilable(String))
  purl_name = T.let(name, String)
  case ecosystem
  when "npm"
    if name.start_with?("@")
      namespace, separator, purl_name = name.partition("/")
      return if namespace == "@" || separator.empty? || purl_name.empty? || purl_name.include?("/")
    elsif name.include?("/")
      return
    end
  when "Maven"
    namespace, separator, purl_name = name.rpartition(":")
    return if separator.empty? || namespace.empty? || purl_name.empty? || namespace.include?(":")
  end

  package = registry_package_from_purl(ecosystem, purl_for(ecosystem, namespace:, name: purl_name, version:))
  package if package.name == name
end

.registry_purl(url) ⇒ Array<(String, Purl)>?

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:



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'vulns/identify.rb', line 197

def self.registry_purl(url)
  basename = decode(File.basename(url)).sub(ARCHIVE_EXTENSIONS, "")

  case url
  when %r{\Ahttps://files\.pythonhosted\.org/packages/(?:[^/]+/){3}(?![^/]+\.whl\z)}
    # PEP 440 canonical versions contain no hyphen, so the last one delimits.
    name, _, version = basename.rpartition("-")
    return if name.empty?

    ["PyPI", purl_for("PyPI", name:, version:)]
  when %r{\Ahttps://registry\.npmjs\.org/(?:((?:@|%40)[^/]+)/)?([^/@%][^/]*)/-/}
    namespace = Regexp.last_match(1)
    name = Regexp.last_match(2)
    return if name.nil?

    namespace &&= "@#{decode(namespace).delete_prefix("@")}"
    name = decode(name)
    return unless (version = version_after_prefix(basename, name))

    ["npm", purl_for("npm", namespace:, name:, version:)]
  when %r{\Ahttps://static\.crates\.io/crates/([^/]+)/}
    name = Regexp.last_match(1)
    return if name.nil?

    name = decode(name)
    return unless (version = version_after_prefix(basename, name))

    ["crates.io", purl_for("crates.io", name:, version:)]
  when %r{\Ahttps://rubygems\.org/(?:downloads|gems)/}
    name, version = gem_name_version(basename)
    return if name.nil?

    ["RubyGems", purl_for("RubyGems", name:, version:)]
  when %r{\Ahttps://hackage\.haskell\.org/package/([^/]+)}
    match = Regexp.last_match(1)&.match(HACKAGE_PKGID)
    return if match.nil?

    name, version = match.captures
    return if name.nil?

    ["Hackage", purl_for("Hackage", name:, version:)]
  when %r{\Ahttps://repo\.hex\.pm/tarballs/}
    # Hex package names are `[a-z][a-z0-9_]*` so the first hyphen delimits.
    name, sep, version = basename.partition("-")
    return if sep.empty?

    ["Hex", purl_for("Hex", name:, version:)]
  when %r{/authors/id/[A-Z]/[A-Z]{2}/([A-Z][A-Z0-9-]+)/}
    author = Regexp.last_match(1)
    match = basename.match(CPAN_DISTNAME)
    return if author.nil? || match.nil?

    name, version = match.captures
    return if name.nil?

    ["CPAN", purl_for("CPAN", namespace: author, name:, version:)]
  # Maven Central only: OSV's bare `Maven` ecosystem is Central-scoped,
  # so third-party repositories (Google, fabricmc, jfrog, ...) are skipped.
  when %r{\Ahttps://repo1?\.maven\.(?:apache\.)?org/maven2/(.+)/([^/]+)/([^/]+)/\2-\3[.-][^/]+\z},
       %r{\Ahttps://search\.maven\.org/remotecontent\?filepath=(.+)/([^/]+)/([^/]+)/\2-\3[.-][^/]+\z}
    group_id = Regexp.last_match(1)
    artifact_id = Regexp.last_match(2)
    version = Regexp.last_match(3)
    return if group_id.nil? || artifact_id.nil?

    ["Maven", purl_for("Maven", namespace: group_id.tr("/", "."), name: artifact_id, version:)]
  when %r{\Ahttps://(?:cran|cloud)\.r-project\.org/src/contrib/(?:Archive/[^/]+/)?([^/_]+)_([^/]+)\.tar\.gz\z}
    name = Regexp.last_match(1)
    return if name.nil?

    ["CRAN", purl_for("CRAN", name:, version: Regexp.last_match(2))]
  when %r{\Ahttps://(?:api|www)\.nuget\.org/(?:v3-flatcontainer|api/v2/package)/([^/]+)/([^/]+)(?:/|\z)}
    name = Regexp.last_match(1)
    return if name.nil?

    ["NuGet", purl_for("NuGet", name:, version: Regexp.last_match(2))]
  end
end

.repo_url(*urls) ⇒ 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:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'vulns/identify.rb', line 58

def self.repo_url(*urls)
  urls.each do |url|
    next if url.nil?

    url = url.sub(WAYBACK_PREFIX, "")
    FORGES.each do |host, path_pattern|
      repo_path = url[%r{\Ahttps?://#{Regexp.escape(host)}#{path_pattern}}, 1]
      next if repo_path.nil?

      repo_path = repo_path.sub(/\.git$/, "")
      repo_path = repo_path.downcase if LOWERCASE_PATH_HOSTS.include?(host)
      return "https://#{host}/#{repo_path}"
    end
  end
  nil
end

.tag(url) ⇒ 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:



76
77
78
79
80
81
82
83
84
# File 'vulns/identify.rb', line 76

def self.tag(url)
  return if url.nil?

  TAG_PATTERNS.each do |pattern|
    match = url.match(pattern)
    return match[1] if match
  end
  nil
end

.version_after_prefix(basename, 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:



288
289
290
291
292
293
294
# File 'vulns/identify.rb', line 288

def self.version_after_prefix(basename, name)
  prefix = "#{name}-"
  return unless basename.start_with?(prefix)

  version = basename[prefix.length..]
  version.presence
end