Class: Homebrew::Vulns::CPANSec Private

Inherits:
CachedFeed show all
Defined in:
vulns/cpan_sec.rb

Overview

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

Loader for the CPAN Security Advisory database. Source: https://github.com/briandfoy/cpan-security-advisory

The upstream repository ships a compiled cpan-security-advisory.json keyed on CPAN distribution name. This class fetches and caches that file and exposes advisories per distribution. Evaluating affected_versions range strings against a formula version is left to Match.

Defined Under Namespace

Classes: Advisory

Constant Summary collapse

DATA_URL =

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://raw.githubusercontent.com/briandfoy/cpan-security-advisory/" \
"master/cpan-security-advisory.json"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from CachedFeed

#as_hash, default_max_age, from_file, load, refresh

Methods included from Utils::Output::Mixin

#issue_reporting_message, #odebug, #odeprecated, #odie, #odisabled, #ofail, #oh1, #oh1_title, #ohai, #ohai_title, #onoe, #opoo, #opoo_outside_github_actions, #opoo_without_github_actions_annotation, #pretty_deprecated, #pretty_disabled, #pretty_duration, #pretty_install_status, #pretty_installed, #pretty_uninstalled, #pretty_unmarked, #pretty_upgradable, #pretty_warning

Constructor Details

#initialize(data) ⇒ void

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:

  • data (T.anything)

Raises:



33
34
35
36
37
38
39
40
# File 'vulns/cpan_sec.rb', line 33

def initialize(data)
  super
  raise Error, "CPANSA data is not a JSON object" unless (top = as_hash(data))
  raise Error, "CPANSA data missing 'dists' key" unless (dists = as_hash(top["dists"]))

  @dists = T.let(dists, T::Hash[String, T.untyped])
  @meta = T.let(as_hash(top["meta"]) || {}, T::Hash[String, T.untyped])
end

Instance Attribute Details

#metaHash{String => T.untyped} (readonly)

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.

Returns:



43
44
45
# File 'vulns/cpan_sec.rb', line 43

def meta
  @meta
end

Class Method Details

.cache_filenameString

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.

Returns:



24
# File 'vulns/cpan_sec.rb', line 24

def self.cache_filename = "cpansa.json"

.data_urlString

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.

Returns:



21
# File 'vulns/cpan_sec.rb', line 21

def self.data_url = DATA_URL

.lower_bounds(conjunction) ⇒ Array<Version>

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:



105
106
107
108
109
110
# File 'vulns/cpan_sec.rb', line 105

def self.lower_bounds(conjunction)
  conjunction.split(",").filter_map do |term|
    match = term.match(CONSTRAINT)
    Version.new(T.must(match[2])) if match && LOWER_BOUND_OPS.include?(match[1])
  end
end

.range_status(advisory, version) ⇒ Vulnerability::RangeStatus

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.

CPANSA constraints: each affected_versions array entry is a comma-joined AND of </<=/>/>=/==/=/bare-version terms; the array is an OR of those. fixed_versions uses the same grammar. Compared with Version; Perl's decimal-vs-dotted equivalence (1.002003 == v1.2.3) is not modelled since homebrew-core CPAN formulae uniformly use the decimal form.

Parameters:

Returns:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'vulns/cpan_sec.rb', line 65

def self.range_status(advisory, version)
  target = Version.new(version.sub(/\Av/i, ""))
  affected = advisory.affected_versions.empty? ||
             advisory.affected_versions.any? { |c| satisfies?(target, c) }
  bounds = advisory.fixed_versions.flat_map { |c| lower_bounds(c) }
  if affected
    fixed_in = bounds.select { |v| target < v }.min&.to_s
    Vulnerability::RangeStatus.new(state: :affected, fixed_in:).freeze
  elsif advisory.fixed_versions.any? { |c| satisfies?(target, c) }
    fixed_in = bounds.select { |v| target >= v }.max&.to_s
    Vulnerability::RangeStatus.new(state: :fixed, fixed_in:).freeze
  else
    Vulnerability::RangeStatus.new(state: :not_applicable, fixed_in: nil).freeze
  end
end

.satisfies?(target, conjunction) ⇒ Boolean

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:

  • (Boolean)


88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'vulns/cpan_sec.rb', line 88

def self.satisfies?(target, conjunction)
  conjunction.split(",").all? do |term|
    match = term.match(CONSTRAINT)
    next false unless match

    bound = Version.new(T.must(match[2]))
    case match[1]
    when "<"  then target < bound
    when "<=" then target <= bound
    when ">"  then target > bound
    when ">=" then target >= bound
    else target == bound
    end
  end
end

Instance Method Details

#advisories_for(distribution) ⇒ Array<Advisory>

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:



51
52
53
54
55
56
# File 'vulns/cpan_sec.rb', line 51

def advisories_for(distribution)
  entry = @dists[distribution]
  return [] unless entry.is_a?(Hash)

  Array(entry["advisories"]).filter_map { |a| build_advisory(a) if a.is_a?(Hash) }
end

#build_advisory(raw) ⇒ Advisory?

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:



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'vulns/cpan_sec.rb', line 113

def build_advisory(raw)
  id = raw["id"]
  return if id.nil?

  Advisory.new(
    id:,
    cves:              Array(raw["cves"]).map(&:to_s),
    affected_versions: Array(raw["affected_versions"]).map(&:to_s),
    fixed_versions:    Array(raw["fixed_versions"]).map(&:to_s),
    severity:          raw["severity"],
    description:       raw["description"],
    references:        Array(raw["references"]).map(&:to_s),
    reported:          raw["reported"],
  ).freeze
end

#distributionsArray<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.

Returns:



46
47
48
# File 'vulns/cpan_sec.rb', line 46

def distributions
  @dists.keys
end