package-audit 0.1.0 → 0.2.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 +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
|