package-audit 0.1.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/exe/package-audit +10 -0
  3. data/lib/package/audit/cli.rb +134 -0
  4. data/lib/package/audit/const.rb +5 -0
  5. data/lib/package/audit/dependency.rb +57 -0
  6. data/lib/package/audit/dependency_printer.rb +128 -0
  7. data/lib/package/audit/enum/environment.rb +15 -0
  8. data/lib/package/audit/enum/risk_explanation.rb +14 -0
  9. data/lib/package/audit/enum/risk_type.rb +12 -0
  10. data/lib/package/audit/enum/vulnerability_type.rb +14 -0
  11. data/lib/package/audit/formatter/base.rb +11 -0
  12. data/lib/package/audit/formatter/risk.rb +28 -0
  13. data/lib/package/audit/formatter/version.rb +33 -0
  14. data/lib/package/audit/formatter/version_date.rb +28 -0
  15. data/lib/package/audit/formatter/vulnerability.rb +37 -0
  16. data/lib/package/audit/risk.rb +27 -0
  17. data/lib/package/audit/risk_calculator.rb +65 -0
  18. data/lib/package/audit/ruby/bundler_specs.rb +28 -0
  19. data/lib/package/audit/ruby/gem_collection.rb +43 -0
  20. data/lib/package/audit/ruby/gem_meta_data.rb +58 -0
  21. data/lib/package/audit/ruby/vulnerability_finder.rb +24 -0
  22. data/lib/package/audit/util/bash_color.rb +35 -0
  23. data/lib/package/audit/util/summary_printer.rb +75 -0
  24. data/lib/package/audit/version.rb +5 -0
  25. data/sig/const.rbs +5 -0
  26. data/sig/package/audit/cli.rbs +31 -0
  27. data/sig/package/audit/dependency.rbs +35 -0
  28. data/sig/package/audit/dependency_printer.rbs +24 -0
  29. data/sig/package/audit/enum/environment.rbs +13 -0
  30. data/sig/package/audit/enum/risk_explanation.rbs +12 -0
  31. data/sig/package/audit/enum/risk_type.rbs +12 -0
  32. data/sig/package/audit/enum/vulnerability_type.rbs +14 -0
  33. data/sig/package/audit/formatter/base.rbs +9 -0
  34. data/sig/package/audit/formatter/risk_printer.rbs +13 -0
  35. data/sig/package/audit/formatter/version_date.rbs +13 -0
  36. data/sig/package/audit/formatter/version_printer.rbs +14 -0
  37. data/sig/package/audit/formatter/vulnerability.rbs +13 -0
  38. data/sig/package/audit/risk.rbs +12 -0
  39. data/sig/package/audit/risk_calculator.rbs +21 -0
  40. data/sig/package/audit/ruby/bundler_specs.rbs +11 -0
  41. data/sig/package/audit/ruby/gem_collection.rbs +15 -0
  42. data/sig/package/audit/ruby/gem_meta_data.rbs +23 -0
  43. data/sig/package/audit/ruby/vulnerability_finder.rbs +9 -0
  44. data/sig/package/audit/util/bash_color.rbs +21 -0
  45. data/sig/package/audit/util/summary_printer.rbs +21 -0
  46. data/sig/package/audit/version.rbs +5 -0
  47. metadata +121 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 40db656091a8559c095240ab373f96c905cc6b78898717c1932dff93428d8b6e
4
+ data.tar.gz: a3220c0f098a071c3cd429914b40bda20826e3157035e778513410c01bd1a566
5
+ SHA512:
6
+ metadata.gz: 348811b4789ffb9dd446a15c6a73819adcf196737a3949689a18457f2b368daa7c2830f10063208e7073833c7b512feb1ddb36bf60e8376f2d84a2d40bd7d9e3
7
+ data.tar.gz: b3a779536d324ea28160defac771a242020168f4668ac8e7540d89e6c5db7f833c0c9caa435b8bb50b2cb807f8e211e2257d33e64aa80c61affd271e1a3fd24e
data/exe/package-audit ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+
5
+ lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
7
+
8
+ require 'package/audit/cli'
9
+
10
+ Package::Audit::CLI.start
@@ -0,0 +1,134 @@
1
+ require_relative './const'
2
+ require_relative './version'
3
+ require_relative './util/summary_printer'
4
+ require_relative './ruby/bundler_specs'
5
+ require_relative './dependency_printer'
6
+ require_relative './ruby/gem_collection'
7
+
8
+ require 'json'
9
+ require 'thor'
10
+
11
+ module Package
12
+ module Audit
13
+ class CLI < Thor
14
+ default_task :report
15
+
16
+ map '--version' => :version
17
+
18
+ desc 'report', 'Show a report of potentially deprecated, outdated or vulnerable gems'
19
+ method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
20
+ method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
21
+
22
+ def report
23
+ within_rescue_block do
24
+ gems = Ruby::GemCollection.all
25
+ DependencyPrinter.new(gems, options).print
26
+
27
+ if gems.any?
28
+ Util::SummaryPrinter.total(gems.length) unless options[:csv]
29
+ Util::SummaryPrinter.report unless options[:csv]
30
+ exit 1
31
+ else
32
+ exit_with_success 'There are no deprecated, outdated or vulnerable gems!'
33
+ end
34
+ end
35
+ end
36
+
37
+ desc 'deprecated', "Show gems with no updates by author for at least #{Const::YEARS_ELAPSED_TO_BE_OUTDATED} years"
38
+ method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
39
+ method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
40
+
41
+ def deprecated
42
+ within_rescue_block do
43
+ gems = Ruby::GemCollection.deprecated
44
+ DependencyPrinter.new(gems, options).print(%i[name version latest_version latest_version_date groups])
45
+
46
+ if gems.any?
47
+ Util::SummaryPrinter.total(gems.length) unless options[:csv]
48
+ Util::SummaryPrinter.deprecated unless options[:csv]
49
+ exit 1
50
+ else
51
+ exit_with_success 'No potential deprecated have been found!'
52
+ end
53
+ end
54
+ end
55
+
56
+ desc 'outdated', 'Show gems, and optionally their dependencies, that are out of date'
57
+ method_option :'include-implicit', type: :boolean, default: false,
58
+ desc: 'Only both gems specified in Gemfile and their dependencies'
59
+ method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
60
+ method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
61
+
62
+ def outdated # rubocop:disable Metrics/AbcSize
63
+ within_rescue_block do
64
+ gems = Ruby::GemCollection.outdated(include_implicit: options[:'include-implicit'])
65
+ DependencyPrinter.new(gems, options).print(%i[name version latest_version latest_version_date groups])
66
+
67
+ if gems.any?
68
+ Util::SummaryPrinter.total(gems.length) unless options[:csv]
69
+ Util::SummaryPrinter.outdated unless options[:'include-implicit'] || options[:csv]
70
+ exit 1
71
+ else
72
+ exit_with_success 'All gems are at latest versions!'
73
+ end
74
+ end
75
+ end
76
+
77
+ desc 'vulnerable', 'Show gems and their dependencies that have security vulnerabilities'
78
+ method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
79
+ method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
80
+
81
+ def vulnerable
82
+ within_rescue_block do
83
+ gems = Ruby::GemCollection.vulnerable
84
+ DependencyPrinter.new(gems, options).print(%i[name version latest_version groups vulnerabilities])
85
+
86
+ if gems.any?
87
+ Util::SummaryPrinter.total(gems.length) unless options[:csv]
88
+ Util::SummaryPrinter.vulnerable unless options[:csv]
89
+ exit 1
90
+ else
91
+ exit_with_success 'No vulnerabilities found!'
92
+ end
93
+ end
94
+ end
95
+
96
+ desc 'risk', 'Print information on how risk is calculated'
97
+
98
+ def risk
99
+ Util::SummaryPrinter.risk
100
+ end
101
+
102
+ desc 'version', 'Print the currently installed version of the package-audit gem'
103
+
104
+ def version
105
+ puts "package-audit #{VERSION}"
106
+ end
107
+
108
+ def self.exit_on_failure?
109
+ true
110
+ end
111
+
112
+ private
113
+
114
+ def within_rescue_block
115
+ raise "Gemfile was not found in #{Dir.pwd}/Gemfile" unless File.exist?("#{Dir.pwd}/Gemfile")
116
+ raise "Gemfile.lock was not found in #{Dir.pwd}/Gemfile.lock" unless File.exist?("#{Dir.pwd}/Gemfile.lock")
117
+
118
+ yield
119
+ rescue StandardError => e
120
+ exit_with_error "#{e.class}: #{e.message}"
121
+ end
122
+
123
+ def exit_with_error(msg)
124
+ puts Util::BashColor.red msg
125
+ exit 1
126
+ end
127
+
128
+ def exit_with_success(msg)
129
+ puts Util::BashColor.green msg
130
+ exit 0
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,5 @@
1
+ module Const
2
+ SECONDS_PER_YEAR = 31_556_952 # length of a gregorian year (365.2425 days)
3
+ YEARS_ELAPSED_TO_BE_OUTDATED = 2
4
+ SECONDS_ELAPSED_TO_BE_OUTDATED = SECONDS_PER_YEAR * YEARS_ELAPSED_TO_BE_OUTDATED
5
+ end
@@ -0,0 +1,57 @@
1
+ require_relative './risk'
2
+ require_relative './risk_calculator'
3
+ require_relative './enum/environment'
4
+ require_relative './enum/risk_type'
5
+ require_relative './enum/risk_explanation'
6
+
7
+ module Package
8
+ module Audit
9
+ class Dependency
10
+ attr_reader :name, :version
11
+ attr_accessor :groups, :version_date, :latest_version, :latest_version_date, :vulnerabilities
12
+
13
+ def initialize(name, version)
14
+ @name = name.to_s
15
+ @version = version.to_s
16
+ @groups = []
17
+ @vulnerabilities = []
18
+ end
19
+
20
+ def update(**attr)
21
+ attr.each { |key, value| instance_variable_set("@#{key}", value) }
22
+ end
23
+
24
+ def risk
25
+ @risk ||= RiskCalculator.new(self).find || Risk.new(Enum::RiskType::NONE)
26
+ end
27
+
28
+ def risk?
29
+ risk.type != Enum::RiskType::NONE
30
+ end
31
+
32
+ def group_list
33
+ @groups.join('|')
34
+ end
35
+
36
+ def vulnerabilities_grouped
37
+ @vulnerabilities.group_by(&:itself).map { |k, v| "#{k}(#{v.length})" }.join('|')
38
+ end
39
+
40
+ def risk_type
41
+ risk.type
42
+ end
43
+
44
+ def risk_explanation
45
+ risk.explanation
46
+ end
47
+
48
+ def to_csv(fields)
49
+ fields.map { |field| send(field) }.join(',')
50
+ end
51
+
52
+ def to_s
53
+ "#{@name} #{@version} - [#{@groups.sort.join(', ')}]"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,128 @@
1
+ require_relative './formatter/risk'
2
+ require_relative './formatter/version'
3
+ require_relative './formatter/version_date'
4
+ require_relative './formatter/vulnerability'
5
+
6
+ module Package
7
+ module Audit
8
+ class DependencyPrinter
9
+ BASH_FORMATTING_REGEX = /\e\[\d+(?:;\d+)*m/
10
+
11
+ COLUMN_GAP = 2
12
+
13
+ # the names of these fields must match the instance variables in the Dependency class
14
+ FIELDS = %i[
15
+ name
16
+ version
17
+ latest_version
18
+ latest_version_date
19
+ groups
20
+ vulnerabilities
21
+ risk_type
22
+ risk_explanation
23
+ ]
24
+
25
+ HEADERS = {
26
+ name: 'Package',
27
+ version: 'Version',
28
+ latest_version: 'Latest',
29
+ latest_version_date: 'Latest Date',
30
+ groups: 'Groups',
31
+ vulnerabilities: 'Vulnerabilities',
32
+ risk_type: 'Risk',
33
+ risk_explanation: 'Risk Explanation'
34
+ }
35
+
36
+ def initialize(dependencies, options)
37
+ @dependencies = dependencies
38
+ @options = options
39
+ end
40
+
41
+ def print(fields = FIELDS)
42
+ if (fields - FIELDS).any?
43
+ raise ArgumentError, "#{fields - FIELDS} are not valid field names. Available fields names are: #{FIELDS}."
44
+ end
45
+
46
+ if @options[:csv]
47
+ csv(fields, exclude_headers: @options[:'exclude-headers'])
48
+ else
49
+ pretty(fields)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def pretty(fields) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
56
+ return if @dependencies.empty?
57
+
58
+ # find the maximum length of each field across all the dependencies so we know how many
59
+ # characters of horizontal space to allocate for each field when printing
60
+ fields.each do |key|
61
+ instance_variable_set "@max_#{key}", HEADERS[key].length
62
+ @dependencies.each do |gem|
63
+ curr_field_length = case key
64
+ when :vulnerabilities
65
+ gem.vulnerabilities_grouped.length
66
+ when :groups
67
+ gem.group_list.length
68
+ else
69
+ gem.send(key)&.gsub(BASH_FORMATTING_REGEX, '')&.length || 0
70
+ end
71
+ max_field_length = instance_variable_get "@max_#{key}"
72
+ instance_variable_set "@max_#{key}", [curr_field_length, max_field_length].max
73
+ end
74
+ end
75
+
76
+ line_length = fields.sum { |key| instance_variable_get "@max_#{key}" } +
77
+ (COLUMN_GAP * (fields.length - 1))
78
+
79
+ puts '=' * line_length
80
+ puts fields.map { |key|
81
+ HEADERS[key].gsub(BASH_FORMATTING_REGEX, '').ljust(instance_variable_get("@max_#{key}"))
82
+ }.join(' ' * COLUMN_GAP)
83
+ puts '=' * line_length
84
+
85
+ @dependencies.each do |dep|
86
+ puts fields.map { |key|
87
+ val = dep.send(key) || ''
88
+ val = case key
89
+ when :groups
90
+ dep.group_list
91
+ when :risk_type
92
+ Formatter::Risk.new(dep.risk_type).format
93
+ when :version
94
+ Formatter::Version.new(dep.version, dep.latest_version).format
95
+ when :vulnerabilities
96
+ Formatter::Vulnerability.new(dep.vulnerabilities).format
97
+ when :latest_version_date
98
+ Formatter::VersionDate.new(dep.latest_version_date).format
99
+ else
100
+ val
101
+ end
102
+
103
+ formatting_length = val.length - val.gsub(BASH_FORMATTING_REGEX, '').length
104
+ val.ljust(instance_variable_get("@max_#{key}") + formatting_length)
105
+ }.join(' ' * COLUMN_GAP)
106
+ end
107
+ end
108
+
109
+ def csv(fields, exclude_headers: false) # rubocop:disable Metrics/MethodLength
110
+ return if @dependencies.empty?
111
+
112
+ value_fields = fields.map do |field|
113
+ case field
114
+ when :groups
115
+ :group_list
116
+ when :vulnerabilities
117
+ :vulnerabilities_grouped
118
+ else
119
+ field
120
+ end
121
+ end
122
+
123
+ puts fields.join(',') unless exclude_headers
124
+ @dependencies.map { |gem| puts gem.to_csv(value_fields) }
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,15 @@
1
+ require_relative '../const'
2
+
3
+ module Package
4
+ module Audit
5
+ module Enum
6
+ module Environment
7
+ DEV = :development
8
+ TEST = :test
9
+ STAGING = :staging
10
+ PRODUCTION = :production
11
+ DEFAULT = :default
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ require_relative '../const'
2
+
3
+ module Package
4
+ module Audit
5
+ module Enum
6
+ module RiskExplanation
7
+ POTENTIAL_DEPRECATION = "no updates by author in over #{Const::YEARS_ELAPSED_TO_BE_OUTDATED} years"
8
+ OUTDATED_BY_MAJOR_VERSION = 'behind by a major version'
9
+ OUTDATED = 'not at latest version'
10
+ VULNERABILITY = 'security vulnerability'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ module Package
2
+ module Audit
3
+ module Enum
4
+ module RiskType
5
+ NONE = 'none'
6
+ LOW = 'low'
7
+ MEDIUM = 'medium'
8
+ HIGH = 'high'
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module Package
2
+ module Audit
3
+ module Enum
4
+ module VulnerabilityType
5
+ NONE = 'none'
6
+ LOW = 'low'
7
+ MEDIUM = 'medium'
8
+ HIGH = 'high'
9
+ CRITICAL = 'critical'
10
+ UNKNOWN = 'unknown'
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module Package
2
+ module Audit
3
+ module Formatter
4
+ class Base
5
+ def format
6
+ raise NotImplementedError, 'Subclass must implement abstract method'
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ require_relative './base'
2
+ require_relative '../util/bash_color'
3
+
4
+ module Package
5
+ module Audit
6
+ module Formatter
7
+ class Risk < Formatter::Base
8
+ def initialize(risk_type)
9
+ super()
10
+ @risk_type = risk_type
11
+ end
12
+
13
+ def format
14
+ case @risk_type
15
+ when Enum::RiskType::HIGH
16
+ Util::BashColor.red(Enum::RiskType::HIGH)
17
+ when Enum::RiskType::MEDIUM
18
+ Util::BashColor.orange(Enum::RiskType::MEDIUM)
19
+ when Enum::RiskType::LOW
20
+ Util::BashColor.yellow(Enum::RiskType::LOW)
21
+ else
22
+ @risk_type.to_s
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,33 @@
1
+ require_relative './base'
2
+ require_relative '../util/bash_color'
3
+
4
+ module Package
5
+ module Audit
6
+ module Formatter
7
+ class Version < Formatter::Base
8
+ def initialize(curr, target)
9
+ super()
10
+ @curr = curr
11
+ @target = target
12
+ end
13
+
14
+ def format # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
15
+ curr_tokens = @curr.split('.')
16
+ target_tokens = @target.split('.')
17
+
18
+ if curr_tokens[0] && curr_tokens[0] < target_tokens[0]
19
+ Util::BashColor.orange(@curr)
20
+ elsif curr_tokens[1] && curr_tokens[1] < target_tokens[1]
21
+ "#{curr_tokens[0]}.#{Util::BashColor.yellow(curr_tokens[1..]&.join('.'))}"
22
+ elsif curr_tokens[2] && curr_tokens[2] < target_tokens[2]
23
+ "#{curr_tokens[0..1]&.join('.')}.#{Util::BashColor.green(curr_tokens[2..]&.join('.'))}"
24
+ elsif curr_tokens[3] && curr_tokens[3] < target_tokens[3]
25
+ "#{curr_tokens[0..2]&.join('.')}.#{Util::BashColor.green(curr_tokens[3])}"
26
+ else
27
+ @curr
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ require_relative '../const'
2
+ require_relative './base'
3
+ require_relative '../util/bash_color'
4
+
5
+ require 'time'
6
+
7
+ module Package
8
+ module Audit
9
+ module Formatter
10
+ class VersionDate < Formatter::Base
11
+ def initialize(date)
12
+ super()
13
+ @date = date
14
+ end
15
+
16
+ def format
17
+ seconds_since_date = (Time.now - Time.parse(@date)).to_i
18
+
19
+ if seconds_since_date >= Const::SECONDS_ELAPSED_TO_BE_OUTDATED
20
+ Util::BashColor.yellow(@date)
21
+ else
22
+ @date
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ require_relative './base'
2
+ require_relative '../enum/vulnerability_type'
3
+ require_relative '../util/bash_color'
4
+
5
+ module Package
6
+ module Audit
7
+ module Formatter
8
+ class Vulnerability < Formatter::Base
9
+ def initialize(vulnerabilities)
10
+ super()
11
+ @vulnerabilities = vulnerabilities
12
+ end
13
+
14
+ def format # rubocop:disable Metrics/MethodLength
15
+ formatted = @vulnerabilities.map do |vulnerability|
16
+ case vulnerability
17
+ when Enum::VulnerabilityType::UNKNOWN, Enum::VulnerabilityType::CRITICAL, Enum::VulnerabilityType::HIGH
18
+ Util::BashColor.red(vulnerability)
19
+ when Enum::VulnerabilityType::MEDIUM
20
+ Util::BashColor.orange(vulnerability)
21
+ when Enum::VulnerabilityType::LOW
22
+ Util::BashColor.yellow(vulnerability)
23
+ else
24
+ vulnerability
25
+ end
26
+ end
27
+
28
+ formatted.group_by(&:itself).map { |k, v| "#{k}(#{v.length})" }.join(' ')
29
+ end
30
+
31
+ private
32
+
33
+ def group; end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,27 @@
1
+ module Package
2
+ module Audit
3
+ class Risk
4
+ include Comparable
5
+
6
+ attr_reader :type, :explanation
7
+
8
+ def initialize(type, explanation = nil)
9
+ @type = type
10
+ @explanation = explanation
11
+ end
12
+
13
+ def <=>(other) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
14
+ if @type == other.type
15
+ 0
16
+ elsif (@type == Enum::RiskType::HIGH && [Enum::RiskType::MEDIUM, Enum::RiskType::LOW,
17
+ Enum::RiskType::NONE].include?(other.type)) ||
18
+ (@type == Enum::RiskType::MEDIUM && [Enum::RiskType::LOW, Enum::RiskType::NONE].include?(other.type)) ||
19
+ (@type == Enum::RiskType::LOW && [Enum::RiskType::NONE].include?(other.type))
20
+ 1
21
+ else
22
+ -1
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,65 @@
1
+ require_relative './const'
2
+
3
+ module Package
4
+ module Audit
5
+ class RiskCalculator
6
+ def initialize(dependency)
7
+ @dependency = dependency
8
+ end
9
+
10
+ def find
11
+ vulnerability_risk = assess_vulnerability_risk
12
+ deprecation_risk = assess_vulnerability_risk
13
+ version_risk = assess_version_risk
14
+
15
+ risk = [vulnerability_risk, deprecation_risk, version_risk].max
16
+ risk = [risk, Risk.new(Enum::RiskType::MEDIUM, risk.explanation)].min unless risk.nil? || production_dependency?
17
+ risk
18
+ end
19
+
20
+ private
21
+
22
+ def assess_vulnerability_risk # rubocop:disable Metrics/MethodLength
23
+ if (@dependency.vulnerabilities & [
24
+ Enum::VulnerabilityType::UNKNOWN,
25
+ Enum::VulnerabilityType::CRITICAL,
26
+ Enum::VulnerabilityType::HIGH
27
+ ]).any?
28
+ Risk.new(Enum::RiskType::HIGH, Enum::RiskExplanation::VULNERABILITY)
29
+ elsif @dependency.vulnerabilities.include? Enum::VulnerabilityType::MEDIUM
30
+ Risk.new(Enum::RiskType::MEDIUM, Enum::RiskExplanation::VULNERABILITY)
31
+ elsif @dependency.vulnerabilities.include? Enum::VulnerabilityType::LOW
32
+ Risk.new(Enum::RiskType::LOW, Enum::RiskExplanation::VULNERABILITY)
33
+ else
34
+ Risk.new(Enum::RiskType::NONE)
35
+ end
36
+ end
37
+
38
+ def assess_version_risk
39
+ if (@dependency.version.split('.').first || '') < (@dependency.latest_version.split('.').first || '')
40
+ Risk.new(Enum::RiskType::MEDIUM, Enum::RiskExplanation::OUTDATED_BY_MAJOR_VERSION)
41
+ elsif @dependency.version < @dependency.latest_version
42
+ Risk.new(Enum::RiskType::LOW, Enum::RiskExplanation::OUTDATED)
43
+ else
44
+ Risk.new(Enum::RiskType::NONE)
45
+ end
46
+ end
47
+
48
+ def assess_deprecation_risk
49
+ seconds_since_date = (Time.now - Time.parse(@dependency.latest_version_date)).to_i
50
+
51
+ if @dependency.version == @dependency.latest_version &&
52
+ seconds_since_date >= Const::SECONDS_ELAPSED_TO_BE_OUTDATED
53
+ Risk.new(Enum::RiskType::MEDIUM, Enum::RiskExplanation::POTENTIAL_DEPRECATION)
54
+ else
55
+ Risk.new(Enum::RiskType::NONE)
56
+ end
57
+ end
58
+
59
+ def production_dependency?
60
+ @dependency.groups.none? || (@dependency.groups & [Enum::Environment::DEFAULT,
61
+ Enum::Environment::PRODUCTION]).any?
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,28 @@
1
+ require_relative '../dependency'
2
+ require_relative './gem_meta_data'
3
+ require_relative './vulnerability_finder'
4
+
5
+ require 'bundler'
6
+
7
+ module Package
8
+ module Audit
9
+ module Ruby
10
+ class BundlerSpecs
11
+ def self.all
12
+ Bundler.ui.silence { Bundler.definition.resolve }
13
+ end
14
+
15
+ def self.gemfile
16
+ current_dependencies = Bundler.ui.silence do
17
+ Bundler.load.dependencies.to_h { |dep| [dep.name, dep] }
18
+ end
19
+
20
+ gemfile_specs, = all.partition do |spec|
21
+ current_dependencies.key? spec.name
22
+ end
23
+ gemfile_specs
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end