package-audit 0.1.0 → 0.3.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 +23 -66
- 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/risk.rb +1 -1
- data/lib/package/audit/formatter/version.rb +7 -6
- data/lib/package/audit/formatter/version_date.rb +3 -3
- data/lib/package/audit/formatter/vulnerability.rb +2 -2
- 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 +44 -0
- data/lib/package/audit/npm/yarn_lock_parser.rb +46 -0
- data/lib/package/audit/package.rb +91 -0
- data/lib/package/audit/{dependency_printer.rb → printer.rb} +33 -51
- data/lib/package/audit/risk_calculator.rb +49 -34
- data/lib/package/audit/ruby/bundler_specs.rb +16 -9
- data/lib/package/audit/ruby/gem_collection.rb +26 -26
- data/lib/package/audit/ruby/gem_meta_data.rb +11 -9
- data/lib/package/audit/ruby/vulnerability_finder.rb +23 -12
- data/lib/package/audit/util/summary_printer.rb +28 -21
- 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 +21 -0
- data/sig/package/audit/npm/yarn_lock_parser.rbs +20 -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/bundler_specs.rbs +2 -2
- data/sig/package/audit/ruby/gem_collection.rbs +8 -4
- data/sig/package/audit/ruby/gem_meta_data.rbs +7 -8
- data/sig/package/audit/ruby/vulnerability_finder.rbs +10 -1
- data/sig/package/audit/util/summary_printer.rbs +3 -5
- metadata +27 -9
- data/lib/package/audit/const.rb +0 -5
- data/lib/package/audit/dependency.rb +0 -57
- data/sig/const.rbs +0 -5
- data/sig/package/audit/dependency_printer.rbs +0 -24
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative '../const/cmd'
|
2
|
+
require_relative '../enum/vulnerability_type'
|
3
|
+
|
4
|
+
module Package
|
5
|
+
module Audit
|
6
|
+
module Npm
|
7
|
+
class VulnerabilityFinder
|
8
|
+
AUDIT_ADVISORY_REGEX = /^{"type":"auditAdvisory".*$/
|
9
|
+
|
10
|
+
def initialize(dir, pkgs)
|
11
|
+
@dir = dir
|
12
|
+
@pkg_hash = pkgs.to_h { |pkg| [pkg.name, pkg] }
|
13
|
+
@vuln_hash = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
json_string_lines = `#{format(Const::Cmd::YARN_AUDIT_JSON, @dir)}`
|
18
|
+
array = json_string_lines.scan(AUDIT_ADVISORY_REGEX)
|
19
|
+
|
20
|
+
vulnerability_json_array = JSON.parse("[#{array.join(',')}]", symbolize_names: true)
|
21
|
+
vulnerability_json_array.each do |vulnerability_json|
|
22
|
+
update_meta_data(vulnerability_json)
|
23
|
+
end
|
24
|
+
@vuln_hash.values
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def update_meta_data(json) # rubocop:disable Metrics/AbcSize
|
30
|
+
parent_name = json[:data][:resolution][:path].split('>').first
|
31
|
+
advisory = json[:data][:advisory]
|
32
|
+
name = advisory[:module_name]
|
33
|
+
version = advisory[:findings][0][:version]
|
34
|
+
full_name = "#{name}@#{version}"
|
35
|
+
vulnerability = advisory[:severity] || Enum::VulnerabilityType::UNKNOWN
|
36
|
+
|
37
|
+
@vuln_hash[full_name] = Package.new(name, version) unless @vuln_hash.key? full_name
|
38
|
+
@vuln_hash[full_name].update vulnerabilities: @vuln_hash[full_name].vulnerabilities + [vulnerability]
|
39
|
+
@vuln_hash[full_name].update groups: @pkg_hash[parent_name].groups
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
module Npm
|
4
|
+
class YarnLockParser
|
5
|
+
def initialize(yarn_lock_path)
|
6
|
+
@yarn_lock_file = File.read(yarn_lock_path)
|
7
|
+
@yarn_lock_path = yarn_lock_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def fetch(default_deps, dev_deps)
|
11
|
+
pkgs = []
|
12
|
+
default_deps.merge(dev_deps).each do |dep_name, expected_version|
|
13
|
+
pkg_block = fetch_package_block(dep_name, expected_version)
|
14
|
+
version = fetch_package_version(dep_name, pkg_block)
|
15
|
+
pks = Package.new(dep_name.to_s, version)
|
16
|
+
pks.update groups: dev_deps.key?(dep_name) ? %i[development] : %i[default development]
|
17
|
+
pkgs << pks
|
18
|
+
end
|
19
|
+
pkgs
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def fetch_package_block(dep_name, expected_version)
|
25
|
+
regex = /#{Regexp.escape(dep_name)}@#{Regexp.escape(expected_version)}.*?:.*?(\n\n|\z)/m
|
26
|
+
blocks = @yarn_lock_file.match(regex)
|
27
|
+
if blocks.nil? || blocks[0].nil?
|
28
|
+
raise NoMatchingPatternError, "Unable to find \"#{dep_name}\" in #{@yarn_lock_path}"
|
29
|
+
end
|
30
|
+
|
31
|
+
blocks[0] || ''
|
32
|
+
end
|
33
|
+
|
34
|
+
def fetch_package_version(dep_name, pkg_block)
|
35
|
+
version = pkg_block.match(/version"?\s*"(.*?)"/)&.captures&.[](0)
|
36
|
+
if version.nil?
|
37
|
+
raise NoMatchingPatternError,
|
38
|
+
"Unable to find the version of \"#{dep_name}\" in #{@yarn_lock_path}"
|
39
|
+
end
|
40
|
+
|
41
|
+
version || '0.0.0.0'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,91 @@
|
|
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 Package
|
10
|
+
attr_reader :name, :version
|
11
|
+
attr_accessor :groups, :version_date, :latest_version, :latest_version_date, :vulnerabilities
|
12
|
+
|
13
|
+
def initialize(name, version, **attr)
|
14
|
+
@name = name.to_s
|
15
|
+
@version = version.to_s
|
16
|
+
@groups = []
|
17
|
+
@vulnerabilities = []
|
18
|
+
@risks = []
|
19
|
+
update(**attr)
|
20
|
+
end
|
21
|
+
|
22
|
+
def full_name
|
23
|
+
"#{name}@#{version}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def update(**attr)
|
27
|
+
attr.each { |key, value| instance_variable_set("@#{key}", value) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def risk
|
31
|
+
risks.max || Risk.new(Enum::RiskType::NONE)
|
32
|
+
end
|
33
|
+
|
34
|
+
def risks
|
35
|
+
RiskCalculator.new(self).find
|
36
|
+
end
|
37
|
+
|
38
|
+
def risk?
|
39
|
+
risks.any?
|
40
|
+
end
|
41
|
+
|
42
|
+
def group_list
|
43
|
+
@groups.join('|')
|
44
|
+
end
|
45
|
+
|
46
|
+
def vulnerabilities_grouped
|
47
|
+
@vulnerabilities.group_by(&:itself).map { |k, v| "#{k}(#{v.length})" }.join('|')
|
48
|
+
end
|
49
|
+
|
50
|
+
def risk_type
|
51
|
+
risk.type
|
52
|
+
end
|
53
|
+
|
54
|
+
def risk_explanation
|
55
|
+
risk.explanation
|
56
|
+
end
|
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
|
+
|
82
|
+
def to_csv(fields)
|
83
|
+
fields.map { |field| send(field) }.join(',')
|
84
|
+
end
|
85
|
+
|
86
|
+
def to_s
|
87
|
+
"#{@name} #{@version} - [#{@groups.sort.join(', ')}]"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -1,65 +1,49 @@
|
|
1
|
-
require_relative '
|
2
|
-
require_relative '
|
3
|
-
require_relative '
|
4
|
-
require_relative '
|
1
|
+
require_relative 'const/fields'
|
2
|
+
require_relative 'formatter/risk'
|
3
|
+
require_relative 'formatter/version'
|
4
|
+
require_relative 'formatter/version_date'
|
5
|
+
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 '
|
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,6 +1,6 @@
|
|
1
|
-
require_relative '../
|
2
|
-
require_relative '
|
3
|
-
require_relative '
|
1
|
+
require_relative '../package'
|
2
|
+
require_relative 'gem_meta_data'
|
3
|
+
require_relative 'vulnerability_finder'
|
4
4
|
|
5
5
|
require 'bundler'
|
6
6
|
|
@@ -8,16 +8,23 @@ module Package
|
|
8
8
|
module Audit
|
9
9
|
module Ruby
|
10
10
|
class BundlerSpecs
|
11
|
-
def self.all
|
12
|
-
Bundler.
|
11
|
+
def self.all(dir)
|
12
|
+
Bundler.with_unbundled_env do
|
13
|
+
ENV['BUNDLE_GEMFILE'] = "#{dir}/Gemfile"
|
14
|
+
Bundler.ui.silence { Bundler.definition.resolve }
|
15
|
+
end
|
13
16
|
end
|
14
17
|
|
15
|
-
def self.gemfile
|
16
|
-
current_dependencies = Bundler.
|
17
|
-
|
18
|
+
def self.gemfile(dir)
|
19
|
+
current_dependencies = Bundler.with_unbundled_env do
|
20
|
+
ENV['BUNDLE_GEMFILE'] = "#{dir}/Gemfile"
|
21
|
+
Bundler.reset!
|
22
|
+
Bundler.ui.silence do
|
23
|
+
Bundler.load.dependencies.to_h { |dep| [dep.name, dep] }
|
24
|
+
end
|
18
25
|
end
|
19
26
|
|
20
|
-
gemfile_specs, = all.partition do |spec|
|
27
|
+
gemfile_specs, = all(dir).partition do |spec|
|
21
28
|
current_dependencies.key? spec.name
|
22
29
|
end
|
23
30
|
gemfile_specs
|
@@ -1,41 +1,41 @@
|
|
1
|
-
require_relative '
|
2
|
-
require_relative '
|
1
|
+
require_relative 'bundler_specs'
|
2
|
+
require_relative '../enum/risk_type'
|
3
|
+
require_relative '../duplicate_package_merger'
|
3
4
|
|
4
5
|
module Package
|
5
6
|
module Audit
|
6
7
|
module Ruby
|
7
8
|
class GemCollection
|
8
|
-
def
|
9
|
-
|
10
|
-
dependencies = specs.map { |spec| Dependency.new(spec.name, spec.version) }
|
11
|
-
vulnerable_deps = VulnerabilityFinder.run
|
12
|
-
GemMetaData.new(dependencies + vulnerable_deps).fetch.filter(&:risk?).sort_by(&:name).uniq(&:name)
|
9
|
+
def initialize(dir)
|
10
|
+
@dir = dir
|
13
11
|
end
|
14
12
|
|
15
|
-
def
|
16
|
-
specs = BundlerSpecs.gemfile
|
17
|
-
|
18
|
-
|
19
|
-
GemMetaData.new(
|
20
|
-
|
21
|
-
end.sort_by(&:name).uniq(&:name)
|
13
|
+
def all
|
14
|
+
specs = BundlerSpecs.gemfile(@dir)
|
15
|
+
pkgs = specs.map { |spec| Package.new(spec.name, spec.version) }
|
16
|
+
vulnerable_pkgs = VulnerabilityFinder.new(@dir).run
|
17
|
+
pkgs = GemMetaData.new(pkgs + vulnerable_pkgs).fetch.filter(&:risk?)
|
18
|
+
DuplicatePackageMerger.new(pkgs).run
|
22
19
|
end
|
23
20
|
|
24
|
-
def
|
25
|
-
specs =
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
dep.version < dep.latest_version
|
30
|
-
end.sort_by(&:name).uniq(&:name)
|
21
|
+
def deprecated
|
22
|
+
specs = BundlerSpecs.gemfile(@dir)
|
23
|
+
pkgs = specs.map { |spec| Package.new(spec.name, spec.version) }
|
24
|
+
pkgs = GemMetaData.new(pkgs).fetch.filter(&:deprecated?)
|
25
|
+
DuplicatePackageMerger.new(pkgs).run
|
31
26
|
end
|
32
27
|
|
33
|
-
def
|
34
|
-
|
28
|
+
def outdated(include_implicit: false)
|
29
|
+
specs = include_implicit ? BundlerSpecs.all(@dir) : BundlerSpecs.gemfile(@dir)
|
30
|
+
pkgs = specs.map { |spec| Package.new(spec.name, spec.version) }
|
31
|
+
pkgs = GemMetaData.new(pkgs).fetch.filter(&:outdated?)
|
32
|
+
DuplicatePackageMerger.new(pkgs).run
|
33
|
+
end
|
35
34
|
|
36
|
-
|
37
|
-
|
38
|
-
|
35
|
+
def vulnerable
|
36
|
+
pkgs = VulnerabilityFinder.new(@dir).run
|
37
|
+
pkgs = GemMetaData.new(pkgs).fetch.filter(&:vulnerable?)
|
38
|
+
DuplicatePackageMerger.new(pkgs).run
|
39
39
|
end
|
40
40
|
end
|
41
41
|
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,33 @@
|
|
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
|
-
|
17
|
-
gem_hash[gem_name].update vulnerabilities: gem_hash[gem_name].vulnerabilities + [vulnerability]
|
8
|
+
def initialize(dir)
|
9
|
+
@dir = dir
|
10
|
+
@vuln_hash = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
json_result = `#{format(Const::Cmd::BUNDLE_AUDIT_JSON, @dir)}`
|
15
|
+
vulnerability_json_array = JSON.parse(json_result, symbolize_names: true)[:results]
|
16
|
+
vulnerability_json_array.each do |vulnerability_json|
|
17
|
+
update_meta_data(vulnerability_json)
|
18
18
|
end
|
19
|
-
|
19
|
+
@vuln_hash.values
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def update_meta_data(json)
|
25
|
+
name = json[:gem][:name]
|
26
|
+
version = json[:gem][:version]
|
27
|
+
full_name = "#{name}@#{version}"
|
28
|
+
vulnerability = json[:advisory][:criticality] || Enum::VulnerabilityType::UNKNOWN
|
29
|
+
@vuln_hash[full_name] = Package.new(name, version) unless @vuln_hash.key? full_name
|
30
|
+
@vuln_hash[full_name].update vulnerabilities: @vuln_hash[full_name].vulnerabilities + [vulnerability]
|
20
31
|
end
|
21
32
|
end
|
22
33
|
end
|