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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 55535a9ef08ce99d8cdacbfe15e0ec11e770f4803e4ef212d9ff4565111c89a6
|
4
|
+
data.tar.gz: 3f1efc9489c0ac9139f94bb7ed66d8ccee255e9c61f79440c899c0dcc4cd5885
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c7ee67ccfadae05b1ec3a7e53d77ed8d977ae43bd54bbc0b95dee86e33ddceb87592089abc7a1410dba71c00802cc6172c3849d23e789878a9e8e14a1de2d96
|
7
|
+
data.tar.gz: 4ff43ca9c9e7bc410b545cde33b77798bfe71d40ebf92874235a695fb1862b2fa826849ce62f67cab831dfa22b987506248754e0a03b3827612e17b12df3f9be
|
data/lib/package/audit/cli.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
require_relative './const'
|
1
|
+
require_relative './const/time'
|
2
2
|
require_relative './version'
|
3
3
|
require_relative './util/summary_printer'
|
4
4
|
require_relative './ruby/bundler_specs'
|
5
|
-
require_relative './
|
5
|
+
require_relative './printer'
|
6
6
|
require_relative './ruby/gem_collection'
|
7
|
+
require_relative './npm/node_collection'
|
8
|
+
require_relative './command_service'
|
7
9
|
|
8
10
|
require 'json'
|
9
11
|
require 'thor'
|
@@ -15,81 +17,44 @@ module Package
|
|
15
17
|
|
16
18
|
map '--version' => :version
|
17
19
|
|
18
|
-
desc 'report', 'Show a report of potentially deprecated, outdated or vulnerable
|
20
|
+
desc 'report', 'Show a report of potentially deprecated, outdated or vulnerable packages'
|
19
21
|
method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
|
20
22
|
method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
|
21
23
|
|
22
24
|
def report
|
23
25
|
within_rescue_block do
|
24
|
-
|
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
|
26
|
+
exit CommandService.new(Dir.pwd, options).all
|
34
27
|
end
|
35
28
|
end
|
36
29
|
|
37
|
-
desc 'deprecated',
|
30
|
+
desc 'deprecated',
|
31
|
+
"Show packages with no updates by author for at least #{Const::Time::YEARS_ELAPSED_TO_BE_OUTDATED} years"
|
38
32
|
method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
|
39
33
|
method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
|
40
34
|
|
41
35
|
def deprecated
|
42
36
|
within_rescue_block do
|
43
|
-
|
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
|
37
|
+
exit CommandService.new(Dir.pwd, options).deprecated
|
53
38
|
end
|
54
39
|
end
|
55
40
|
|
56
|
-
desc 'outdated', 'Show
|
57
|
-
method_option :'include-implicit', type: :boolean, default: false,
|
58
|
-
desc: 'Only both gems specified in Gemfile and their dependencies'
|
41
|
+
desc 'outdated', 'Show packages that are out of date'
|
59
42
|
method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
|
60
43
|
method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
|
61
44
|
|
62
|
-
def outdated
|
45
|
+
def outdated
|
63
46
|
within_rescue_block do
|
64
|
-
|
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
|
47
|
+
exit CommandService.new(Dir.pwd, options).outdated
|
74
48
|
end
|
75
49
|
end
|
76
50
|
|
77
|
-
desc 'vulnerable', 'Show
|
51
|
+
desc 'vulnerable', 'Show packages and their dependencies that have security vulnerabilities'
|
78
52
|
method_option :csv, type: :boolean, default: false, desc: 'Output using comma separated values (CSV)'
|
79
53
|
method_option :'exclude-headers', type: :boolean, default: false, desc: 'Hide headers if when using CSV'
|
80
54
|
|
81
55
|
def vulnerable
|
82
56
|
within_rescue_block do
|
83
|
-
|
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
|
57
|
+
exit CommandService.new(Dir.pwd, options).vulnerable
|
93
58
|
end
|
94
59
|
end
|
95
60
|
|
@@ -112,9 +77,6 @@ module Package
|
|
112
77
|
private
|
113
78
|
|
114
79
|
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
80
|
yield
|
119
81
|
rescue StandardError => e
|
120
82
|
exit_with_error "#{e.class}: #{e.message}"
|
@@ -124,11 +86,6 @@ module Package
|
|
124
86
|
puts Util::BashColor.red msg
|
125
87
|
exit 1
|
126
88
|
end
|
127
|
-
|
128
|
-
def exit_with_success(msg)
|
129
|
-
puts Util::BashColor.green msg
|
130
|
-
exit 0
|
131
|
-
end
|
132
89
|
end
|
133
90
|
end
|
134
91
|
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require_relative './const/cmd'
|
2
|
+
require_relative './const/file'
|
3
|
+
|
4
|
+
module Package
|
5
|
+
module Audit
|
6
|
+
class CommandService # rubocop:disable Metrics/ClassLength
|
7
|
+
RUBY_GEM = 'ruby gem'
|
8
|
+
NODE_MODULE = 'node module'
|
9
|
+
|
10
|
+
def initialize(dir, options)
|
11
|
+
@dir = dir
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def all # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
16
|
+
pkgs = []
|
17
|
+
|
18
|
+
if ruby?
|
19
|
+
gems = Ruby::GemCollection.all
|
20
|
+
pkgs += gems
|
21
|
+
Printer.new(gems, @options).print(Const::Fields::REPORT)
|
22
|
+
|
23
|
+
unless @options[:csv]
|
24
|
+
if gems.any?
|
25
|
+
Util::SummaryPrinter.statistics(RUBY_GEM, gems)
|
26
|
+
Util::SummaryPrinter.vulnerable(RUBY_GEM, Const::Cmd::BUNDLE_AUDIT)
|
27
|
+
else
|
28
|
+
print_success_message "There are no deprecated, outdated or vulnerable #{RUBY_GEM}s!"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if node?
|
34
|
+
npms = Npm::NodeCollection.new(@dir).all
|
35
|
+
pkgs += npms
|
36
|
+
Printer.new(npms, @options).print(Const::Fields::REPORT)
|
37
|
+
|
38
|
+
unless @options[:csv]
|
39
|
+
if npms.any?
|
40
|
+
Util::SummaryPrinter.statistics(NODE_MODULE, npms)
|
41
|
+
Util::SummaryPrinter.vulnerable(NODE_MODULE, Const::Cmd::YARN_AUDIT)
|
42
|
+
else
|
43
|
+
print_success_message "There are no deprecated, outdated or vulnerable #{NODE_MODULE}s!"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
pkgs.any?
|
49
|
+
end
|
50
|
+
|
51
|
+
def vulnerable # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
52
|
+
pkgs = []
|
53
|
+
|
54
|
+
if ruby?
|
55
|
+
gems = Ruby::GemCollection.vulnerable
|
56
|
+
pkgs += gems
|
57
|
+
Printer.new(gems, @options).print(Const::Fields::VULNERABLE)
|
58
|
+
|
59
|
+
unless @options[:csv]
|
60
|
+
if gems.any?
|
61
|
+
Util::SummaryPrinter.total(RUBY_GEM, gems)
|
62
|
+
Util::SummaryPrinter.vulnerable(RUBY_GEM, Const::Cmd::BUNDLE_AUDIT)
|
63
|
+
else
|
64
|
+
print_success_message "There are no #{RUBY_GEM} vulnerabilities!"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
if node?
|
70
|
+
npms = Npm::NodeCollection.new(@dir).vulnerable
|
71
|
+
pkgs += npms
|
72
|
+
Printer.new(npms, @options).print(Const::Fields::VULNERABLE)
|
73
|
+
|
74
|
+
unless @options[:csv]
|
75
|
+
if npms.any?
|
76
|
+
Util::SummaryPrinter.total(NODE_MODULE, npms)
|
77
|
+
Util::SummaryPrinter.vulnerable(NODE_MODULE, Const::Cmd::YARN_AUDIT)
|
78
|
+
else
|
79
|
+
print_success_message "There are no #{NODE_MODULE} vulnerabilities!"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
pkgs.any?
|
85
|
+
end
|
86
|
+
|
87
|
+
def outdated # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
88
|
+
pkgs = []
|
89
|
+
|
90
|
+
if ruby?
|
91
|
+
gems = Ruby::GemCollection.outdated
|
92
|
+
pkgs += gems
|
93
|
+
Printer.new(gems, @options).print(Const::Fields::OUTDATED)
|
94
|
+
|
95
|
+
unless @options[:csv]
|
96
|
+
if gems.any?
|
97
|
+
Util::SummaryPrinter.total(RUBY_GEM, gems)
|
98
|
+
else
|
99
|
+
print_success_message "There are no outdated #{RUBY_GEM}s!"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
if node?
|
105
|
+
npms = Npm::NodeCollection.new(@dir).outdated
|
106
|
+
pkgs += npms
|
107
|
+
Printer.new(npms, @options).print(Const::Fields::OUTDATED)
|
108
|
+
|
109
|
+
unless @options[:csv]
|
110
|
+
if npms.any?
|
111
|
+
Util::SummaryPrinter.total(NODE_MODULE, npms)
|
112
|
+
else
|
113
|
+
print_success_message "There are no outdated #{NODE_MODULE}s!"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
pkgs.any?
|
119
|
+
end
|
120
|
+
|
121
|
+
def deprecated # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
|
122
|
+
pkgs = []
|
123
|
+
|
124
|
+
if ruby?
|
125
|
+
gems = Ruby::GemCollection.deprecated
|
126
|
+
pkgs += gems
|
127
|
+
Printer.new(gems, @options).print(Const::Fields::OUTDATED)
|
128
|
+
|
129
|
+
unless @options[:csv]
|
130
|
+
if gems.any?
|
131
|
+
Util::SummaryPrinter.total(RUBY_GEM, gems)
|
132
|
+
Util::SummaryPrinter.deprecated
|
133
|
+
else
|
134
|
+
print_success_message "There are no potentially deprecated #{RUBY_GEM}s!"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
if node?
|
140
|
+
npms = Npm::NodeCollection.new(@dir).deprecated
|
141
|
+
pkgs += npms
|
142
|
+
Printer.new(npms, @options).print(Const::Fields::OUTDATED)
|
143
|
+
|
144
|
+
unless @options[:csv]
|
145
|
+
if npms.any?
|
146
|
+
Util::SummaryPrinter.total(NODE_MODULE, npms)
|
147
|
+
Util::SummaryPrinter.deprecated
|
148
|
+
else
|
149
|
+
print_success_message "There are no potentially deprecated #{NODE_MODULE}s!"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
pkgs.any?
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def ruby?
|
160
|
+
gemfile_present = File.exist?("#{@dir}/#{Const::File::GEMFILE}")
|
161
|
+
gemfile_lock_present = File.exist?("#{@dir}/#{Const::File::GEMFILE_LOCK}")
|
162
|
+
|
163
|
+
if gemfile_present && gemfile_lock_present
|
164
|
+
true
|
165
|
+
elsif gemfile_present
|
166
|
+
raise "#{Const::File::GEMFILE_LOCK} was not found in #{@dir}/"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def node?
|
171
|
+
package_json_present = File.exist?("#{@dir}/#{Const::File::PACKAGE_JSON}")
|
172
|
+
package_lock_json_present = File.exist?("#{@dir}/#{Const::File::PACKAGE_LOCK_JSON}")
|
173
|
+
yarn_lock_present = File.exist?("#{@dir}/#{Const::File::YARN_LOCK}")
|
174
|
+
|
175
|
+
if package_json_present && (package_lock_json_present || yarn_lock_present)
|
176
|
+
true
|
177
|
+
elsif package_json_present
|
178
|
+
raise "#{Const::File::PACKAGE_LOCK_JSON} or #{Const::File::YARN_LOCK} was not found in #{@dir}/"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def print_success_message(msg)
|
183
|
+
puts Util::BashColor.green msg
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
module Const
|
4
|
+
module Cmd
|
5
|
+
BUNDLE_AUDIT = 'bundle exec bundle-audit check --update'
|
6
|
+
BUNDLE_AUDIT_JSON = 'bundle exec bundle-audit check --update --quiet --format json'
|
7
|
+
|
8
|
+
NPM_AUDIT = 'npm audit'
|
9
|
+
NPM_AUDIT_JSON = 'npm audit --json'
|
10
|
+
|
11
|
+
YARN_AUDIT = 'yarn audit'
|
12
|
+
YARN_AUDIT_JSON = 'yarn audit --json'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
module Const
|
4
|
+
module Fields
|
5
|
+
ALL = %i[
|
6
|
+
name
|
7
|
+
version
|
8
|
+
version_date
|
9
|
+
latest_version
|
10
|
+
latest_version_date
|
11
|
+
groups
|
12
|
+
vulnerabilities
|
13
|
+
risk_type
|
14
|
+
risk_explanation
|
15
|
+
]
|
16
|
+
|
17
|
+
REPORT = %i[name version latest_version latest_version_date groups vulnerabilities risk_type risk_explanation]
|
18
|
+
VULNERABLE = %i[name version latest_version groups vulnerabilities]
|
19
|
+
OUTDATED = %i[name version latest_version latest_version_date groups]
|
20
|
+
|
21
|
+
# the names of these fields must match the instance variables in the Dependency class
|
22
|
+
HEADERS = {
|
23
|
+
name: 'Package',
|
24
|
+
version: 'Version',
|
25
|
+
version_date: 'Date',
|
26
|
+
latest_version: 'Latest',
|
27
|
+
latest_version_date: 'Latest Date',
|
28
|
+
groups: 'Groups',
|
29
|
+
vulnerabilities: 'Vulnerabilities',
|
30
|
+
risk_type: 'Risk',
|
31
|
+
risk_explanation: 'Risk Explanation'
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
module Const
|
4
|
+
module Time
|
5
|
+
SECONDS_PER_YEAR = 31_556_952 # length of a gregorian year (365.2425 days)
|
6
|
+
YEARS_ELAPSED_TO_BE_OUTDATED = 2
|
7
|
+
SECONDS_ELAPSED_TO_BE_OUTDATED = SECONDS_PER_YEAR * YEARS_ELAPSED_TO_BE_OUTDATED
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
class DuplicatePackageMerger
|
4
|
+
def initialize(pkgs)
|
5
|
+
@pkgs = pkgs.sort_by(&:full_name)
|
6
|
+
end
|
7
|
+
|
8
|
+
def run # rubocop:disable Metrics/AbcSize
|
9
|
+
pkgs = @pkgs.take(1)
|
10
|
+
|
11
|
+
@pkgs[1..]&.each do |curr|
|
12
|
+
prev = pkgs[-1]
|
13
|
+
if curr.full_name == prev.full_name
|
14
|
+
prev.update(groups: prev.groups | curr.groups,
|
15
|
+
vulnerabilities: prev.vulnerabilities + curr.vulnerabilities,
|
16
|
+
risks: prev.risks + curr.risks)
|
17
|
+
else
|
18
|
+
pkgs << curr
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
pkgs
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
require_relative '../const'
|
1
|
+
require_relative '../const/time'
|
2
2
|
|
3
3
|
module Package
|
4
4
|
module Audit
|
5
5
|
module Enum
|
6
6
|
module RiskExplanation
|
7
|
-
POTENTIAL_DEPRECATION = "no updates by author in over #{Const::YEARS_ELAPSED_TO_BE_OUTDATED} years"
|
7
|
+
POTENTIAL_DEPRECATION = "no updates by author in over #{Const::Time::YEARS_ELAPSED_TO_BE_OUTDATED} years"
|
8
8
|
OUTDATED_BY_MAJOR_VERSION = 'behind by a major version'
|
9
9
|
OUTDATED = 'not at latest version'
|
10
10
|
VULNERABILITY = 'security vulnerability'
|
@@ -12,16 +12,17 @@ module Package
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def format # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
15
|
+
version_parts = @curr.split('.').map(&:to_i)
|
16
|
+
latest_version_parts = @target.split('.').map(&:to_i)
|
15
17
|
curr_tokens = @curr.split('.')
|
16
|
-
target_tokens = @target.split('.')
|
17
18
|
|
18
|
-
if
|
19
|
+
if (version_parts.first || 0) < (latest_version_parts.first || 0)
|
19
20
|
Util::BashColor.orange(@curr)
|
20
|
-
elsif
|
21
|
+
elsif version_parts[1] && latest_version_parts[1] && version_parts[1] < latest_version_parts[1]
|
21
22
|
"#{curr_tokens[0]}.#{Util::BashColor.yellow(curr_tokens[1..]&.join('.'))}"
|
22
|
-
elsif
|
23
|
+
elsif version_parts[2] && latest_version_parts[2] && version_parts[2] < latest_version_parts[2]
|
23
24
|
"#{curr_tokens[0..1]&.join('.')}.#{Util::BashColor.green(curr_tokens[2..]&.join('.'))}"
|
24
|
-
elsif
|
25
|
+
elsif version_parts[3] && latest_version_parts[3] && version_parts[3] < latest_version_parts[3]
|
25
26
|
"#{curr_tokens[0..2]&.join('.')}.#{Util::BashColor.green(curr_tokens[3])}"
|
26
27
|
else
|
27
28
|
@curr
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require_relative '../const'
|
2
1
|
require_relative './base'
|
2
|
+
require_relative '../const/time'
|
3
3
|
require_relative '../util/bash_color'
|
4
4
|
|
5
5
|
require 'time'
|
@@ -16,7 +16,7 @@ module Package
|
|
16
16
|
def format
|
17
17
|
seconds_since_date = (Time.now - Time.parse(@date)).to_i
|
18
18
|
|
19
|
-
if seconds_since_date >= Const::SECONDS_ELAPSED_TO_BE_OUTDATED
|
19
|
+
if seconds_since_date >= Const::Time::SECONDS_ELAPSED_TO_BE_OUTDATED
|
20
20
|
Util::BashColor.yellow(@date)
|
21
21
|
else
|
22
22
|
@date
|
@@ -16,7 +16,7 @@ module Package
|
|
16
16
|
case vulnerability
|
17
17
|
when Enum::VulnerabilityType::UNKNOWN, Enum::VulnerabilityType::CRITICAL, Enum::VulnerabilityType::HIGH
|
18
18
|
Util::BashColor.red(vulnerability)
|
19
|
-
when Enum::VulnerabilityType::MEDIUM
|
19
|
+
when Enum::VulnerabilityType::MEDIUM, Enum::VulnerabilityType::MODERATE
|
20
20
|
Util::BashColor.orange(vulnerability)
|
21
21
|
when Enum::VulnerabilityType::LOW
|
22
22
|
Util::BashColor.yellow(vulnerability)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative './yarn_lock_parser'
|
2
|
+
require_relative './npm_meta_data'
|
3
|
+
require_relative './vulnerability_finder'
|
4
|
+
require_relative '../duplicate_package_merger'
|
5
|
+
|
6
|
+
module Package
|
7
|
+
module Audit
|
8
|
+
module Npm
|
9
|
+
class NodeCollection
|
10
|
+
PACKAGE_JSON = 'package.json'
|
11
|
+
PACKAGE_LOCK = 'package-lock.json'
|
12
|
+
YARN_LOCK = 'yarn.lock'
|
13
|
+
|
14
|
+
def initialize(dir)
|
15
|
+
@dir = dir
|
16
|
+
end
|
17
|
+
|
18
|
+
def all
|
19
|
+
implicit_pkgs = fetch_from_lock_file
|
20
|
+
vulnerable_pkgs = VulnerabilityFinder.new(implicit_pkgs).run
|
21
|
+
pkgs = NpmMetaData.new(vulnerable_pkgs + implicit_pkgs).fetch.filter(&:risk?)
|
22
|
+
DuplicatePackageMerger.new(pkgs).run
|
23
|
+
end
|
24
|
+
|
25
|
+
def deprecated
|
26
|
+
implicit_pkgs = fetch_from_lock_file
|
27
|
+
pkgs = NpmMetaData.new(implicit_pkgs).fetch.filter(&:deprecated?)
|
28
|
+
DuplicatePackageMerger.new(pkgs).run
|
29
|
+
end
|
30
|
+
|
31
|
+
def outdated
|
32
|
+
implicit_pkgs = fetch_from_lock_file
|
33
|
+
pkgs = NpmMetaData.new(implicit_pkgs).fetch.filter(&:outdated?)
|
34
|
+
DuplicatePackageMerger.new(pkgs).run
|
35
|
+
end
|
36
|
+
|
37
|
+
def vulnerable
|
38
|
+
implicit_pkgs = fetch_from_lock_file
|
39
|
+
vulnerable_pkgs = VulnerabilityFinder.new(implicit_pkgs).run
|
40
|
+
pkgs = NpmMetaData.new(vulnerable_pkgs).fetch
|
41
|
+
DuplicatePackageMerger.new(pkgs).run
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def fetch_from_package_json
|
47
|
+
package_json = JSON.parse(File.read("#{@dir}/#{PACKAGE_JSON}"), symbolize_names: true)
|
48
|
+
default_deps = package_json[:dependencies] || {}
|
49
|
+
dev_deps = package_json[:devDependencies] || {}
|
50
|
+
[default_deps, dev_deps]
|
51
|
+
end
|
52
|
+
|
53
|
+
def fetch_from_lock_file
|
54
|
+
default_deps, dev_deps = fetch_from_package_json
|
55
|
+
if File.exist?("#{@dir}/#{YARN_LOCK}")
|
56
|
+
YarnLockParser.new("#{@dir}/#{YARN_LOCK}").fetch(default_deps || {}, dev_deps || {})
|
57
|
+
else
|
58
|
+
[]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module Package
|
5
|
+
module Audit
|
6
|
+
module Npm
|
7
|
+
class NpmMetaData
|
8
|
+
REGISTRY_URL = 'https://registry.npmjs.org'
|
9
|
+
|
10
|
+
def initialize(packages)
|
11
|
+
@packages = packages
|
12
|
+
end
|
13
|
+
|
14
|
+
def fetch
|
15
|
+
threads = @packages.map do |package|
|
16
|
+
Thread.new do
|
17
|
+
response = Net::HTTP.get_response(URI.parse("#{REGISTRY_URL}/#{package.name}"))
|
18
|
+
raise response.error unless response.is_a?(Net::HTTPSuccess)
|
19
|
+
|
20
|
+
json_package = JSON.parse(response.body, symbolize_names: true)
|
21
|
+
update_meta_data(package, json_package)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
threads.each(&:join)
|
25
|
+
@packages
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def update_meta_data(package, json_data)
|
31
|
+
latest_version = json_data[:'dist-tags'][:latest]
|
32
|
+
version_date = json_data[:time][package.version.to_sym]
|
33
|
+
latest_version_date = json_data[:time][latest_version.to_sym]
|
34
|
+
package.update version_date: Time.parse(version_date).strftime('%Y-%m-%d'),
|
35
|
+
latest_version: latest_version,
|
36
|
+
latest_version_date: Time.parse(latest_version_date).strftime('%Y-%m-%d')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,43 @@
|
|
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(pkgs)
|
11
|
+
@pkg_hash = pkgs.to_h { |pkg| [pkg.name, pkg] }
|
12
|
+
@vuln_hash = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
json_string_lines = `#{Const::Cmd::YARN_AUDIT_JSON}`
|
17
|
+
array = json_string_lines.scan(AUDIT_ADVISORY_REGEX)
|
18
|
+
|
19
|
+
vulnerability_json_array = JSON.parse("[#{array.join(',')}]", symbolize_names: true)
|
20
|
+
vulnerability_json_array.each do |vulnerability_json|
|
21
|
+
update_meta_data(vulnerability_json)
|
22
|
+
end
|
23
|
+
@vuln_hash.values
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def update_meta_data(json) # rubocop:disable Metrics/AbcSize
|
29
|
+
parent_name = json[:data][:resolution][:path].split('>').first
|
30
|
+
advisory = json[:data][:advisory]
|
31
|
+
name = advisory[:module_name]
|
32
|
+
version = advisory[:findings][0][:version]
|
33
|
+
full_name = "#{name}@#{version}"
|
34
|
+
vulnerability = advisory[:severity] || Enum::VulnerabilityType::UNKNOWN
|
35
|
+
|
36
|
+
@vuln_hash[full_name] = Package.new(name, version) unless @vuln_hash.key? full_name
|
37
|
+
@vuln_hash[full_name].update vulnerabilities: @vuln_hash[full_name].vulnerabilities + [vulnerability]
|
38
|
+
@vuln_hash[full_name].update groups: @pkg_hash[parent_name].groups
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|