package-audit 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/package/audit/cli.rb +14 -57
- data/lib/package/audit/command_service.rb +187 -0
- data/lib/package/audit/const/cmd.rb +16 -0
- data/lib/package/audit/const/fields.rb +36 -0
- data/lib/package/audit/const/file.rb +13 -0
- data/lib/package/audit/const/time.rb +11 -0
- data/lib/package/audit/duplicate_package_merger.rb +26 -0
- data/lib/package/audit/enum/environment.rb +0 -2
- data/lib/package/audit/enum/risk_explanation.rb +2 -2
- data/lib/package/audit/enum/vulnerability_type.rb +1 -0
- data/lib/package/audit/formatter/version.rb +6 -5
- data/lib/package/audit/formatter/version_date.rb +2 -2
- data/lib/package/audit/formatter/vulnerability.rb +1 -1
- data/lib/package/audit/npm/node_collection.rb +64 -0
- data/lib/package/audit/npm/npm_meta_data.rb +41 -0
- data/lib/package/audit/npm/vulnerability_finder.rb +43 -0
- data/lib/package/audit/npm/yarn_lock_parser.rb +42 -0
- data/lib/package/audit/{dependency.rb → package.rb} +38 -4
- data/lib/package/audit/{dependency_printer.rb → printer.rb} +29 -47
- data/lib/package/audit/risk_calculator.rb +49 -34
- data/lib/package/audit/ruby/bundler_specs.rb +1 -1
- data/lib/package/audit/ruby/gem_collection.rb +14 -18
- data/lib/package/audit/ruby/gem_meta_data.rb +11 -9
- data/lib/package/audit/ruby/vulnerability_finder.rb +22 -12
- data/lib/package/audit/util/summary_printer.rb +26 -19
- data/lib/package/audit/version.rb +1 -1
- data/sig/package/audit/command_service.rbs +29 -0
- data/sig/package/audit/const/cmd.rbs +14 -0
- data/sig/package/audit/const/fields.rbs +13 -0
- data/sig/package/audit/const/file.rbs +13 -0
- data/sig/package/audit/const/time.rbs +11 -0
- data/sig/package/audit/duplicate_package_merger.rbs +11 -0
- data/sig/package/audit/enum/vulnerability_type.rbs +1 -0
- data/sig/package/audit/npm/node_collection.rbs +29 -0
- data/sig/package/audit/npm/npm_meta_data.rbs +19 -0
- data/sig/package/audit/npm/vulnerability_finder.rbs +20 -0
- data/sig/package/audit/npm/yarn_lock_parser.rbs +19 -0
- data/sig/package/audit/{dependency.rbs → package.rbs} +14 -4
- data/sig/package/audit/printer.rbs +24 -0
- data/sig/package/audit/risk_calculator.rbs +6 -6
- data/sig/package/audit/ruby/gem_collection.rbs +4 -4
- data/sig/package/audit/ruby/gem_meta_data.rbs +7 -8
- data/sig/package/audit/ruby/vulnerability_finder.rbs +7 -1
- data/sig/package/audit/util/summary_printer.rbs +3 -5
- metadata +27 -9
- data/lib/package/audit/const.rb +0 -5
- data/sig/const.rbs +0 -5
- data/sig/package/audit/dependency_printer.rbs +0 -24
@@ -0,0 +1,42 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
module Npm
|
4
|
+
class YarnLockParser
|
5
|
+
def initialize(yarn_lock_path)
|
6
|
+
@yarn_lock_path = File.read(yarn_lock_path)
|
7
|
+
end
|
8
|
+
|
9
|
+
def fetch(default_deps, dev_deps)
|
10
|
+
pkgs = []
|
11
|
+
default_deps.merge(dev_deps).each do |dep_name, expected_version|
|
12
|
+
pkg_block = fetch_package_block(dep_name, expected_version)
|
13
|
+
version = fetch_package_version(dep_name, pkg_block)
|
14
|
+
pks = Package.new(dep_name.to_s, version)
|
15
|
+
pks.update groups: dev_deps.key?(dep_name) ? %i[development] : %i[default development]
|
16
|
+
pkgs << pks
|
17
|
+
end
|
18
|
+
pkgs
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def fetch_package_block(dep_name, expected_version)
|
24
|
+
regex = /#{Regexp.escape(dep_name)}@#{Regexp.escape(expected_version)}.*?:.*?(\n\n|\z)/m
|
25
|
+
blocks = @yarn_lock_path.match(regex)
|
26
|
+
if blocks.nil? || blocks[0].nil?
|
27
|
+
raise NoMatchingPatternError, "Unable to find #{dep_name} in #{@yarn_lock_path}"
|
28
|
+
end
|
29
|
+
|
30
|
+
blocks[0] || ''
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch_package_version(dep_name, pkg_block)
|
34
|
+
version = pkg_block.match(/version "(.*?)"/)&.[](1)
|
35
|
+
raise NoMatchingPatternError, "Unable to find #{dep_name} version in #{@yarn_lock_path}" if version.nil?
|
36
|
+
|
37
|
+
version || '0.0.0.0'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -6,15 +6,21 @@ require_relative './enum/risk_explanation'
|
|
6
6
|
|
7
7
|
module Package
|
8
8
|
module Audit
|
9
|
-
class
|
9
|
+
class Package
|
10
10
|
attr_reader :name, :version
|
11
11
|
attr_accessor :groups, :version_date, :latest_version, :latest_version_date, :vulnerabilities
|
12
12
|
|
13
|
-
def initialize(name, version)
|
13
|
+
def initialize(name, version, **attr)
|
14
14
|
@name = name.to_s
|
15
15
|
@version = version.to_s
|
16
16
|
@groups = []
|
17
17
|
@vulnerabilities = []
|
18
|
+
@risks = []
|
19
|
+
update(**attr)
|
20
|
+
end
|
21
|
+
|
22
|
+
def full_name
|
23
|
+
"#{name}@#{version}"
|
18
24
|
end
|
19
25
|
|
20
26
|
def update(**attr)
|
@@ -22,11 +28,15 @@ module Package
|
|
22
28
|
end
|
23
29
|
|
24
30
|
def risk
|
25
|
-
|
31
|
+
risks.max || Risk.new(Enum::RiskType::NONE)
|
32
|
+
end
|
33
|
+
|
34
|
+
def risks
|
35
|
+
RiskCalculator.new(self).find
|
26
36
|
end
|
27
37
|
|
28
38
|
def risk?
|
29
|
-
|
39
|
+
risks.any?
|
30
40
|
end
|
31
41
|
|
32
42
|
def group_list
|
@@ -45,6 +55,30 @@ module Package
|
|
45
55
|
risk.explanation
|
46
56
|
end
|
47
57
|
|
58
|
+
def deprecated?
|
59
|
+
risks.each do |risk|
|
60
|
+
return true if risk.explanation == Enum::RiskExplanation::POTENTIAL_DEPRECATION
|
61
|
+
end
|
62
|
+
false
|
63
|
+
end
|
64
|
+
|
65
|
+
def outdated?
|
66
|
+
risks.each do |risk|
|
67
|
+
return true if [
|
68
|
+
Enum::RiskExplanation::OUTDATED,
|
69
|
+
Enum::RiskExplanation::OUTDATED_BY_MAJOR_VERSION
|
70
|
+
].include?(risk.explanation || '')
|
71
|
+
end
|
72
|
+
false
|
73
|
+
end
|
74
|
+
|
75
|
+
def vulnerable?
|
76
|
+
risks.each do |risk|
|
77
|
+
return true if risk.explanation == Enum::RiskExplanation::VULNERABILITY
|
78
|
+
end
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
48
82
|
def to_csv(fields)
|
49
83
|
fields.map { |field| send(field) }.join(',')
|
50
84
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require_relative './const/fields'
|
1
2
|
require_relative './formatter/risk'
|
2
3
|
require_relative './formatter/version'
|
3
4
|
require_relative './formatter/version_date'
|
@@ -5,61 +6,44 @@ require_relative './formatter/vulnerability'
|
|
5
6
|
|
6
7
|
module Package
|
7
8
|
module Audit
|
8
|
-
class
|
9
|
+
class Printer
|
9
10
|
BASH_FORMATTING_REGEX = /\e\[\d+(?:;\d+)*m/
|
10
11
|
|
11
12
|
COLUMN_GAP = 2
|
12
13
|
|
13
|
-
|
14
|
-
|
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
|
14
|
+
def initialize(pkgs, options)
|
15
|
+
@pkgs = pkgs
|
38
16
|
@options = options
|
39
17
|
end
|
40
18
|
|
41
|
-
def print(fields
|
42
|
-
|
43
|
-
|
44
|
-
end
|
19
|
+
def print(fields)
|
20
|
+
check_fields(fields)
|
21
|
+
return if @pkgs.empty?
|
45
22
|
|
46
23
|
if @options[:csv]
|
47
24
|
csv(fields, exclude_headers: @options[:'exclude-headers'])
|
48
25
|
else
|
49
26
|
pretty(fields)
|
50
27
|
end
|
28
|
+
puts
|
51
29
|
end
|
52
30
|
|
53
31
|
private
|
54
32
|
|
55
|
-
def
|
56
|
-
return
|
33
|
+
def check_fields(fields)
|
34
|
+
return unless (fields - Const::Fields::ALL).any?
|
57
35
|
|
58
|
-
|
36
|
+
raise ArgumentError,
|
37
|
+
"#{fields - Const::Fields::ALL} are not valid field names. " \
|
38
|
+
"Available fields names are: #{Const::Fields::ALL}."
|
39
|
+
end
|
40
|
+
|
41
|
+
def pretty(fields = Const::Fields::REPORT) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
42
|
+
# find the maximum length of each field across all the packages so we know how many
|
59
43
|
# characters of horizontal space to allocate for each field when printing
|
60
44
|
fields.each do |key|
|
61
|
-
instance_variable_set "@max_#{key}", HEADERS[key].length
|
62
|
-
@
|
45
|
+
instance_variable_set "@max_#{key}", Const::Fields::HEADERS[key].length
|
46
|
+
@pkgs.each do |gem|
|
63
47
|
curr_field_length = case key
|
64
48
|
when :vulnerabilities
|
65
49
|
gem.vulnerabilities_grouped.length
|
@@ -78,24 +62,24 @@ module Package
|
|
78
62
|
|
79
63
|
puts '=' * line_length
|
80
64
|
puts fields.map { |key|
|
81
|
-
HEADERS[key].gsub(BASH_FORMATTING_REGEX, '').ljust(instance_variable_get("@max_#{key}"))
|
65
|
+
Const::Fields::HEADERS[key].gsub(BASH_FORMATTING_REGEX, '').ljust(instance_variable_get("@max_#{key}"))
|
82
66
|
}.join(' ' * COLUMN_GAP)
|
83
67
|
puts '=' * line_length
|
84
68
|
|
85
|
-
@
|
69
|
+
@pkgs.each do |pkg|
|
86
70
|
puts fields.map { |key|
|
87
|
-
val =
|
71
|
+
val = pkg.send(key) || ''
|
88
72
|
val = case key
|
89
73
|
when :groups
|
90
|
-
|
74
|
+
pkg.group_list
|
91
75
|
when :risk_type
|
92
|
-
Formatter::Risk.new(
|
76
|
+
Formatter::Risk.new(pkg.risk_type).format
|
93
77
|
when :version
|
94
|
-
Formatter::Version.new(
|
78
|
+
Formatter::Version.new(pkg.version, pkg.latest_version).format
|
95
79
|
when :vulnerabilities
|
96
|
-
Formatter::Vulnerability.new(
|
80
|
+
Formatter::Vulnerability.new(pkg.vulnerabilities).format
|
97
81
|
when :latest_version_date
|
98
|
-
Formatter::VersionDate.new(
|
82
|
+
Formatter::VersionDate.new(pkg.latest_version_date).format
|
99
83
|
else
|
100
84
|
val
|
101
85
|
end
|
@@ -106,9 +90,7 @@ module Package
|
|
106
90
|
end
|
107
91
|
end
|
108
92
|
|
109
|
-
def csv(fields, exclude_headers: false)
|
110
|
-
return if @dependencies.empty?
|
111
|
-
|
93
|
+
def csv(fields, exclude_headers: false)
|
112
94
|
value_fields = fields.map do |field|
|
113
95
|
case field
|
114
96
|
when :groups
|
@@ -121,7 +103,7 @@ module Package
|
|
121
103
|
end
|
122
104
|
|
123
105
|
puts fields.join(',') unless exclude_headers
|
124
|
-
@
|
106
|
+
@pkgs.map { |gem| puts gem.to_csv(value_fields) }
|
125
107
|
end
|
126
108
|
end
|
127
109
|
end
|
@@ -1,64 +1,79 @@
|
|
1
|
-
require_relative './const'
|
1
|
+
require_relative './const/time'
|
2
2
|
|
3
3
|
module Package
|
4
4
|
module Audit
|
5
5
|
class RiskCalculator
|
6
|
-
def initialize(
|
7
|
-
@
|
6
|
+
def initialize(pkg)
|
7
|
+
@pkg = pkg
|
8
8
|
end
|
9
9
|
|
10
10
|
def find
|
11
|
-
|
12
|
-
deprecation_risk = assess_vulnerability_risk
|
13
|
-
version_risk = assess_version_risk
|
11
|
+
risks = assess_vulnerability_risks + assess_deprecation_risks + assess_version_risks
|
14
12
|
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
unless production_dependency?
|
14
|
+
risks.each_with_index do |risk, index|
|
15
|
+
risks[index] =
|
16
|
+
[risk, Risk.new(Enum::RiskType::MEDIUM, risk.explanation)].min || Risk.new(Enum::RiskType::NONE)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
risks
|
18
20
|
end
|
19
21
|
|
20
22
|
private
|
21
23
|
|
22
|
-
def
|
23
|
-
|
24
|
+
def assess_vulnerability_risks # rubocop:disable Metrics/MethodLength
|
25
|
+
risks = []
|
26
|
+
|
27
|
+
if (@pkg.vulnerabilities & [
|
24
28
|
Enum::VulnerabilityType::UNKNOWN,
|
25
29
|
Enum::VulnerabilityType::CRITICAL,
|
26
30
|
Enum::VulnerabilityType::HIGH
|
27
31
|
]).any?
|
28
|
-
Risk.new(Enum::RiskType::HIGH, Enum::RiskExplanation::VULNERABILITY)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
Risk.new(Enum::RiskType::
|
32
|
+
risks << Risk.new(Enum::RiskType::HIGH, Enum::RiskExplanation::VULNERABILITY)
|
33
|
+
end
|
34
|
+
if (@pkg.vulnerabilities & [
|
35
|
+
Enum::VulnerabilityType::MEDIUM,
|
36
|
+
Enum::VulnerabilityType::MODERATE
|
37
|
+
]).any?
|
38
|
+
risks << Risk.new(Enum::RiskType::MEDIUM, Enum::RiskExplanation::VULNERABILITY)
|
39
|
+
end
|
40
|
+
if @pkg.vulnerabilities.include? Enum::VulnerabilityType::LOW
|
41
|
+
risks << Risk.new(Enum::RiskType::LOW, Enum::RiskExplanation::VULNERABILITY)
|
35
42
|
end
|
43
|
+
risks
|
36
44
|
end
|
37
45
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
46
|
+
def assess_version_risks # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
47
|
+
risks = []
|
48
|
+
|
49
|
+
return risks if @pkg.latest_version.nil?
|
50
|
+
|
51
|
+
version_parts = @pkg.version.split('.').map(&:to_i)
|
52
|
+
latest_version_parts = @pkg.latest_version.split('.').map(&:to_i)
|
53
|
+
|
54
|
+
if (version_parts.first || 0) < (latest_version_parts.first || 0)
|
55
|
+
risks << Risk.new(Enum::RiskType::MEDIUM, Enum::RiskExplanation::OUTDATED_BY_MAJOR_VERSION)
|
56
|
+
end
|
57
|
+
if (version_parts.first || 0) == (latest_version_parts.first || 0) &&
|
58
|
+
(version_parts[1..] <=> latest_version_parts[1..]) == -1
|
59
|
+
risks << Risk.new(Enum::RiskType::LOW, Enum::RiskExplanation::OUTDATED)
|
45
60
|
end
|
61
|
+
risks
|
46
62
|
end
|
47
63
|
|
48
|
-
def
|
49
|
-
|
64
|
+
def assess_deprecation_risks
|
65
|
+
risks = []
|
66
|
+
seconds_since_date = (Time.now - Time.parse(@pkg.latest_version_date)).to_i
|
50
67
|
|
51
|
-
if
|
52
|
-
|
53
|
-
Risk.new(Enum::RiskType::MEDIUM, Enum::RiskExplanation::POTENTIAL_DEPRECATION)
|
54
|
-
else
|
55
|
-
Risk.new(Enum::RiskType::NONE)
|
68
|
+
if seconds_since_date >= Const::Time::SECONDS_ELAPSED_TO_BE_OUTDATED
|
69
|
+
risks << Risk.new(Enum::RiskType::MEDIUM, Enum::RiskExplanation::POTENTIAL_DEPRECATION)
|
56
70
|
end
|
71
|
+
risks
|
57
72
|
end
|
58
73
|
|
59
74
|
def production_dependency?
|
60
|
-
@
|
61
|
-
|
75
|
+
@pkg.groups.none? || (@pkg.groups & [Enum::Environment::DEFAULT,
|
76
|
+
Enum::Environment::PRODUCTION]).any?
|
62
77
|
end
|
63
78
|
end
|
64
79
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require_relative './bundler_specs'
|
2
2
|
require_relative './../enum/risk_type'
|
3
|
+
require_relative '../duplicate_package_merger'
|
3
4
|
|
4
5
|
module Package
|
5
6
|
module Audit
|
@@ -7,35 +8,30 @@ module Package
|
|
7
8
|
class GemCollection
|
8
9
|
def self.all
|
9
10
|
specs = BundlerSpecs.gemfile
|
10
|
-
|
11
|
-
|
12
|
-
GemMetaData.new(
|
11
|
+
pkgs = specs.map { |spec| Package.new(spec.name, spec.version) }
|
12
|
+
vulnerable_pkgs = VulnerabilityFinder.new.run
|
13
|
+
pkgs = GemMetaData.new(pkgs + vulnerable_pkgs).fetch.filter(&:risk?)
|
14
|
+
DuplicatePackageMerger.new(pkgs).run
|
13
15
|
end
|
14
16
|
|
15
17
|
def self.deprecated
|
16
18
|
specs = BundlerSpecs.gemfile
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
dep.risk.explanation == Enum::RiskExplanation::POTENTIAL_DEPRECATION
|
21
|
-
end.sort_by(&:name).uniq(&:name)
|
19
|
+
pkgs = specs.map { |spec| Package.new(spec.name, spec.version) }
|
20
|
+
pkgs = GemMetaData.new(pkgs).fetch.filter(&:deprecated?)
|
21
|
+
DuplicatePackageMerger.new(pkgs).run
|
22
22
|
end
|
23
23
|
|
24
24
|
def self.outdated(include_implicit: false)
|
25
25
|
specs = include_implicit ? BundlerSpecs.all : BundlerSpecs.gemfile
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
dep.version < dep.latest_version
|
30
|
-
end.sort_by(&:name).uniq(&:name)
|
26
|
+
pkgs = specs.map { |spec| Package.new(spec.name, spec.version) }
|
27
|
+
pkgs = GemMetaData.new(pkgs).fetch.filter(&:outdated?)
|
28
|
+
DuplicatePackageMerger.new(pkgs).run
|
31
29
|
end
|
32
30
|
|
33
31
|
def self.vulnerable
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
dep.risk.explanation == Enum::RiskExplanation::VULNERABILITY
|
38
|
-
end.sort_by(&:name).uniq(&:name)
|
32
|
+
pkgs = VulnerabilityFinder.new.run
|
33
|
+
pkgs = GemMetaData.new(pkgs).fetch.filter(&:vulnerable?)
|
34
|
+
DuplicatePackageMerger.new(pkgs).run
|
39
35
|
end
|
40
36
|
end
|
41
37
|
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
require_relative '../
|
1
|
+
require_relative '../package'
|
2
2
|
|
3
3
|
module Package
|
4
4
|
module Audit
|
5
5
|
module Ruby
|
6
6
|
class GemMetaData
|
7
|
-
def initialize(
|
8
|
-
@
|
7
|
+
def initialize(pkgs)
|
8
|
+
@pkgs = pkgs
|
9
9
|
@gem_hash = {}
|
10
10
|
end
|
11
11
|
|
@@ -20,23 +20,25 @@ module Package
|
|
20
20
|
def find_rubygems_metadata # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
21
21
|
fetcher = Gem::SpecFetcher.fetcher
|
22
22
|
|
23
|
-
@
|
24
|
-
gem_dependency = Gem::Dependency.new
|
23
|
+
@pkgs.each do |pkg|
|
24
|
+
gem_dependency = Gem::Dependency.new pkg.name, ">= #{pkg.version}"
|
25
25
|
local_version_date = Time.new(0)
|
26
26
|
latest_version_date = Time.new(0)
|
27
|
-
local_version = Gem::Version.new(
|
28
|
-
latest_version = Gem::Version.new('0.0.0')
|
27
|
+
local_version = Gem::Version.new(pkg.version)
|
28
|
+
latest_version = Gem::Version.new('0.0.0.0')
|
29
29
|
|
30
30
|
remote_dependencies, = fetcher.spec_for_dependency gem_dependency
|
31
31
|
|
32
|
+
next unless remote_dependencies.any?
|
33
|
+
|
32
34
|
remote_dependencies.each do |remote_spec, _|
|
33
35
|
latest_version = remote_spec.version if latest_version < remote_spec.version
|
34
36
|
latest_version_date = remote_spec.date if latest_version_date < remote_spec.date
|
35
37
|
local_version_date = remote_spec.date if local_version == remote_spec.version
|
36
38
|
end
|
37
39
|
|
38
|
-
@gem_hash[
|
39
|
-
|
40
|
+
@gem_hash[pkg.name] = pkg
|
41
|
+
pkg.update latest_version: latest_version.to_s,
|
40
42
|
version_date: local_version_date.strftime('%Y-%m-%d'),
|
41
43
|
latest_version_date: latest_version_date.strftime('%Y-%m-%d')
|
42
44
|
end
|
@@ -1,22 +1,32 @@
|
|
1
|
+
require_relative '../const/cmd'
|
1
2
|
require_relative '../enum/vulnerability_type'
|
2
3
|
|
3
4
|
module Package
|
4
5
|
module Audit
|
5
6
|
module Ruby
|
6
7
|
class VulnerabilityFinder
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
gem_hash[gem_name].update vulnerabilities: gem_hash[gem_name].vulnerabilities + [vulnerability]
|
8
|
+
def initialize
|
9
|
+
@vuln_hash = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
json_result = `#{Const::Cmd::BUNDLE_AUDIT_JSON}`
|
14
|
+
vulnerability_json_array = JSON.parse(json_result, symbolize_names: true)[:results]
|
15
|
+
vulnerability_json_array.each do |vulnerability_json|
|
16
|
+
update_meta_data(vulnerability_json)
|
18
17
|
end
|
19
|
-
|
18
|
+
@vuln_hash.values
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def update_meta_data(json)
|
24
|
+
name = json[:gem][:name]
|
25
|
+
version = json[:gem][:version]
|
26
|
+
full_name = "#{name}@#{version}"
|
27
|
+
vulnerability = json[:advisory][:criticality] || Enum::VulnerabilityType::UNKNOWN
|
28
|
+
@vuln_hash[full_name] = Package.new(name, version) unless @vuln_hash.key? full_name
|
29
|
+
@vuln_hash[full_name].update vulnerabilities: @vuln_hash[full_name].vulnerabilities + [vulnerability]
|
20
30
|
end
|
21
31
|
end
|
22
32
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require_relative '../const'
|
1
|
+
require_relative '../const/time'
|
2
2
|
require_relative './bash_color'
|
3
3
|
|
4
4
|
module Package
|
@@ -12,44 +12,51 @@ module Package
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def self.deprecated
|
15
|
-
puts Util::BashColor.blue(
|
16
|
-
puts Util::BashColor.blue("Please contact the
|
15
|
+
puts Util::BashColor.blue('Although the packages above have no recent updates, they may not be deprecated.')
|
16
|
+
puts Util::BashColor.blue("Please contact the package author for more information about its status.\n")
|
17
17
|
end
|
18
18
|
|
19
|
-
def self.
|
20
|
-
printf("
|
21
|
-
info: Util::BashColor.blue(
|
22
|
-
cmd: Util::BashColor.magenta(
|
19
|
+
def self.vulnerable(package_type, cmd)
|
20
|
+
printf("%<info>s\n%<cmd>s\n\n",
|
21
|
+
info: Util::BashColor.blue("To get more information about the #{package_type} vulnerabilities run:"),
|
22
|
+
cmd: Util::BashColor.magenta(" > #{cmd}"))
|
23
23
|
end
|
24
24
|
|
25
|
-
def self.
|
26
|
-
|
27
|
-
info: Util::BashColor.blue('To get more information about the vulnerabilities run:'),
|
28
|
-
cmd: Util::BashColor.magenta(' > bundle exec bundle-audit check --update'))
|
25
|
+
def self.total(package_type, pkgs)
|
26
|
+
puts Util::BashColor.cyan("Found a total of #{pkgs.length} #{package_type}s.\n")
|
29
27
|
end
|
30
28
|
|
31
|
-
def self.
|
32
|
-
|
29
|
+
def self.statistics(package_type, pkgs)
|
30
|
+
outdated = pkgs.count(&:outdated?)
|
31
|
+
deprecated = pkgs.count(&:deprecated?)
|
32
|
+
vulnerable = pkgs.count(&:vulnerable?)
|
33
|
+
|
34
|
+
vulnerabilities = pkgs.sum { |pkg| pkg.vulnerabilities.length }
|
35
|
+
|
36
|
+
puts Util::BashColor.cyan("Found a total of #{pkgs.length} #{package_type}s.\n" \
|
37
|
+
"#{vulnerable} vulnerable (#{vulnerabilities} vulnerabilities), " \
|
38
|
+
"#{outdated} outdated, #{deprecated} deprecated.\n")
|
33
39
|
end
|
34
40
|
|
35
41
|
def self.risk # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
36
|
-
puts Util::BashColor.blue('1. Check if the
|
42
|
+
puts Util::BashColor.blue('1. Check if the package has a security vulnerability.')
|
37
43
|
puts ' If yes, the following vulnerability -> risk mapping is used:'
|
38
44
|
puts " - #{Util::BashColor.red('unknown')} vulnerability\t-> #{Util::BashColor.red('high')} risk"
|
39
45
|
puts " - #{Util::BashColor.red('critical')} vulnerability\t-> #{Util::BashColor.red('high')} risk"
|
40
46
|
puts " - #{Util::BashColor.red('high')} vulnerability\t-> #{Util::BashColor.red('high')} risk"
|
41
47
|
puts " - #{Util::BashColor.orange('medium')} vulnerability\t-> #{Util::BashColor.orange('medium')} risk"
|
48
|
+
puts " - #{Util::BashColor.orange('moderate')} vulnerability\t-> #{Util::BashColor.orange('medium')} risk" # rubocop:disable Layout/LineLength
|
42
49
|
puts " - #{Util::BashColor.yellow('low')} vulnerability\t-> #{Util::BashColor.yellow('low')} risk"
|
43
50
|
|
44
51
|
puts
|
45
52
|
|
46
|
-
puts Util::BashColor.blue('2. Check the
|
47
|
-
puts " If no new releases by author for at least #{Const::YEARS_ELAPSED_TO_BE_OUTDATED} years:"
|
53
|
+
puts Util::BashColor.blue('2. Check the package for potential deprecation.')
|
54
|
+
puts " If no new releases by author for at least #{Const::Time::YEARS_ELAPSED_TO_BE_OUTDATED} years:"
|
48
55
|
puts " - assign the risk to\t-> #{Util::BashColor.orange('medium')} risk"
|
49
56
|
|
50
57
|
puts
|
51
58
|
|
52
|
-
puts Util::BashColor.blue('3. Check if a newer version of the
|
59
|
+
puts Util::BashColor.blue('3. Check if a newer version of the package is available.')
|
53
60
|
|
54
61
|
puts ' If yes, assign risk as follows:'
|
55
62
|
puts " - #{Util::BashColor.orange('major version')} mismatch\t-> #{Util::BashColor.orange('medium')} risk" # rubocop:disable Layout/LineLength
|
@@ -65,8 +72,8 @@ module Package
|
|
65
72
|
|
66
73
|
puts
|
67
74
|
|
68
|
-
puts Util::BashColor.blue('5. Check whether the
|
69
|
-
puts ' If a
|
75
|
+
puts Util::BashColor.blue('5. Check whether the package is used in production or not.')
|
76
|
+
puts ' If a package is limited to a non-production environment:'
|
70
77
|
puts " - cap risk severity to\t -> #{Util::BashColor.orange('medium')} risk"
|
71
78
|
end
|
72
79
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
class CommandService
|
4
|
+
NODE_MODULE: String
|
5
|
+
RUBY_GEM: String
|
6
|
+
|
7
|
+
@dir: String
|
8
|
+
@options: Hash[Symbol, untyped]
|
9
|
+
|
10
|
+
def initialize: (String, Hash[Symbol, untyped]) -> void
|
11
|
+
|
12
|
+
def all: -> bool
|
13
|
+
|
14
|
+
def deprecated: -> bool
|
15
|
+
|
16
|
+
def outdated: -> bool
|
17
|
+
|
18
|
+
def vulnerable: -> bool
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def node?: -> bool?
|
23
|
+
|
24
|
+
def print_success_message: (String) -> void
|
25
|
+
|
26
|
+
def ruby?: -> bool?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|