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