package-audit 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/exe/package-audit +10 -0
- data/lib/package/audit/cli.rb +134 -0
- data/lib/package/audit/const.rb +5 -0
- data/lib/package/audit/dependency.rb +57 -0
- data/lib/package/audit/dependency_printer.rb +128 -0
- data/lib/package/audit/enum/environment.rb +15 -0
- data/lib/package/audit/enum/risk_explanation.rb +14 -0
- data/lib/package/audit/enum/risk_type.rb +12 -0
- data/lib/package/audit/enum/vulnerability_type.rb +14 -0
- data/lib/package/audit/formatter/base.rb +11 -0
- data/lib/package/audit/formatter/risk.rb +28 -0
- data/lib/package/audit/formatter/version.rb +33 -0
- data/lib/package/audit/formatter/version_date.rb +28 -0
- data/lib/package/audit/formatter/vulnerability.rb +37 -0
- data/lib/package/audit/risk.rb +27 -0
- data/lib/package/audit/risk_calculator.rb +65 -0
- data/lib/package/audit/ruby/bundler_specs.rb +28 -0
- data/lib/package/audit/ruby/gem_collection.rb +43 -0
- data/lib/package/audit/ruby/gem_meta_data.rb +58 -0
- data/lib/package/audit/ruby/vulnerability_finder.rb +24 -0
- data/lib/package/audit/util/bash_color.rb +35 -0
- data/lib/package/audit/util/summary_printer.rb +75 -0
- data/lib/package/audit/version.rb +5 -0
- data/sig/const.rbs +5 -0
- data/sig/package/audit/cli.rbs +31 -0
- data/sig/package/audit/dependency.rbs +35 -0
- data/sig/package/audit/dependency_printer.rbs +24 -0
- data/sig/package/audit/enum/environment.rbs +13 -0
- data/sig/package/audit/enum/risk_explanation.rbs +12 -0
- data/sig/package/audit/enum/risk_type.rbs +12 -0
- data/sig/package/audit/enum/vulnerability_type.rbs +14 -0
- data/sig/package/audit/formatter/base.rbs +9 -0
- data/sig/package/audit/formatter/risk_printer.rbs +13 -0
- data/sig/package/audit/formatter/version_date.rbs +13 -0
- data/sig/package/audit/formatter/version_printer.rbs +14 -0
- data/sig/package/audit/formatter/vulnerability.rbs +13 -0
- data/sig/package/audit/risk.rbs +12 -0
- data/sig/package/audit/risk_calculator.rbs +21 -0
- data/sig/package/audit/ruby/bundler_specs.rbs +11 -0
- data/sig/package/audit/ruby/gem_collection.rbs +15 -0
- data/sig/package/audit/ruby/gem_meta_data.rbs +23 -0
- data/sig/package/audit/ruby/vulnerability_finder.rbs +9 -0
- data/sig/package/audit/util/bash_color.rbs +21 -0
- data/sig/package/audit/util/summary_printer.rbs +21 -0
- data/sig/package/audit/version.rbs +5 -0
- 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,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,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,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,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
|