package-audit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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