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