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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/package/audit/cli.rb +14 -57
  3. data/lib/package/audit/command_service.rb +187 -0
  4. data/lib/package/audit/const/cmd.rb +16 -0
  5. data/lib/package/audit/const/fields.rb +36 -0
  6. data/lib/package/audit/const/file.rb +13 -0
  7. data/lib/package/audit/const/time.rb +11 -0
  8. data/lib/package/audit/duplicate_package_merger.rb +26 -0
  9. data/lib/package/audit/enum/environment.rb +0 -2
  10. data/lib/package/audit/enum/risk_explanation.rb +2 -2
  11. data/lib/package/audit/enum/vulnerability_type.rb +1 -0
  12. data/lib/package/audit/formatter/version.rb +6 -5
  13. data/lib/package/audit/formatter/version_date.rb +2 -2
  14. data/lib/package/audit/formatter/vulnerability.rb +1 -1
  15. data/lib/package/audit/npm/node_collection.rb +64 -0
  16. data/lib/package/audit/npm/npm_meta_data.rb +41 -0
  17. data/lib/package/audit/npm/vulnerability_finder.rb +43 -0
  18. data/lib/package/audit/npm/yarn_lock_parser.rb +42 -0
  19. data/lib/package/audit/{dependency.rb → package.rb} +38 -4
  20. data/lib/package/audit/{dependency_printer.rb → printer.rb} +29 -47
  21. data/lib/package/audit/risk_calculator.rb +49 -34
  22. data/lib/package/audit/ruby/bundler_specs.rb +1 -1
  23. data/lib/package/audit/ruby/gem_collection.rb +14 -18
  24. data/lib/package/audit/ruby/gem_meta_data.rb +11 -9
  25. data/lib/package/audit/ruby/vulnerability_finder.rb +22 -12
  26. data/lib/package/audit/util/summary_printer.rb +26 -19
  27. data/lib/package/audit/version.rb +1 -1
  28. data/sig/package/audit/command_service.rbs +29 -0
  29. data/sig/package/audit/const/cmd.rbs +14 -0
  30. data/sig/package/audit/const/fields.rbs +13 -0
  31. data/sig/package/audit/const/file.rbs +13 -0
  32. data/sig/package/audit/const/time.rbs +11 -0
  33. data/sig/package/audit/duplicate_package_merger.rbs +11 -0
  34. data/sig/package/audit/enum/vulnerability_type.rbs +1 -0
  35. data/sig/package/audit/npm/node_collection.rbs +29 -0
  36. data/sig/package/audit/npm/npm_meta_data.rbs +19 -0
  37. data/sig/package/audit/npm/vulnerability_finder.rbs +20 -0
  38. data/sig/package/audit/npm/yarn_lock_parser.rbs +19 -0
  39. data/sig/package/audit/{dependency.rbs → package.rbs} +14 -4
  40. data/sig/package/audit/printer.rbs +24 -0
  41. data/sig/package/audit/risk_calculator.rbs +6 -6
  42. data/sig/package/audit/ruby/gem_collection.rbs +4 -4
  43. data/sig/package/audit/ruby/gem_meta_data.rbs +7 -8
  44. data/sig/package/audit/ruby/vulnerability_finder.rbs +7 -1
  45. data/sig/package/audit/util/summary_printer.rbs +3 -5
  46. metadata +27 -9
  47. data/lib/package/audit/const.rb +0 -5
  48. data/sig/const.rbs +0 -5
  49. data/sig/package/audit/dependency_printer.rbs +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 40db656091a8559c095240ab373f96c905cc6b78898717c1932dff93428d8b6e
4
- data.tar.gz: a3220c0f098a071c3cd429914b40bda20826e3157035e778513410c01bd1a566
3
+ metadata.gz: 55535a9ef08ce99d8cdacbfe15e0ec11e770f4803e4ef212d9ff4565111c89a6
4
+ data.tar.gz: 3f1efc9489c0ac9139f94bb7ed66d8ccee255e9c61f79440c899c0dcc4cd5885
5
5
  SHA512:
6
- metadata.gz: 348811b4789ffb9dd446a15c6a73819adcf196737a3949689a18457f2b368daa7c2830f10063208e7073833c7b512feb1ddb36bf60e8376f2d84a2d40bd7d9e3
7
- data.tar.gz: b3a779536d324ea28160defac771a242020168f4668ac8e7540d89e6c5db7f833c0c9caa435b8bb50b2cb807f8e211e2257d33e64aa80c61affd271e1a3fd24e
6
+ metadata.gz: 9c7ee67ccfadae05b1ec3a7e53d77ed8d977ae43bd54bbc0b95dee86e33ddceb87592089abc7a1410dba71c00802cc6172c3849d23e789878a9e8e14a1de2d96
7
+ data.tar.gz: 4ff43ca9c9e7bc410b545cde33b77798bfe71d40ebf92874235a695fb1862b2fa826849ce62f67cab831dfa22b987506248754e0a03b3827612e17b12df3f9be
@@ -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 './dependency_printer'
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 gems'
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
- gems = Ruby::GemCollection.all
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', "Show gems with no updates by author for at least #{Const::YEARS_ELAPSED_TO_BE_OUTDATED} years"
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
- gems = Ruby::GemCollection.deprecated
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 gems, and optionally their dependencies, that are out of date'
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 # rubocop:disable Metrics/AbcSize
45
+ def outdated
63
46
  within_rescue_block do
64
- gems = Ruby::GemCollection.outdated(include_implicit: options[:'include-implicit'])
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 gems and their dependencies that have security vulnerabilities'
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
- gems = Ruby::GemCollection.vulnerable
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,13 @@
1
+ module Package
2
+ module Audit
3
+ module Const
4
+ module File
5
+ GEMFILE = 'Gemfile'
6
+ GEMFILE_LOCK = 'Gemfile.lock'
7
+ PACKAGE_JSON = 'package.json'
8
+ PACKAGE_LOCK_JSON = 'package-lock.json'
9
+ YARN_LOCK = 'yarn.lock'
10
+ end
11
+ end
12
+ end
13
+ 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,5 +1,3 @@
1
- require_relative '../const'
2
-
3
1
  module Package
4
2
  module Audit
5
3
  module Enum
@@ -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'
@@ -5,6 +5,7 @@ module Package
5
5
  NONE = 'none'
6
6
  LOW = 'low'
7
7
  MEDIUM = 'medium'
8
+ MODERATE = 'moderate'
8
9
  HIGH = 'high'
9
10
  CRITICAL = 'critical'
10
11
  UNKNOWN = 'unknown'
@@ -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 curr_tokens[0] && curr_tokens[0] < target_tokens[0]
19
+ if (version_parts.first || 0) < (latest_version_parts.first || 0)
19
20
  Util::BashColor.orange(@curr)
20
- elsif curr_tokens[1] && curr_tokens[1] < target_tokens[1]
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 curr_tokens[2] && curr_tokens[2] < target_tokens[2]
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 curr_tokens[3] && curr_tokens[3] < target_tokens[3]
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