package-audit 0.5.1 → 0.6.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 +11 -9
- data/lib/package/audit/const/cmd.rb +2 -2
- data/lib/package/audit/enum/format.rb +14 -0
- data/lib/package/audit/enum/option.rb +1 -1
- data/lib/package/audit/enum/technology.rb +1 -1
- data/lib/package/audit/models/package.rb +2 -2
- data/lib/package/audit/ruby/bundler_specs.rb +1 -0
- data/lib/package/audit/services/command_parser.rb +30 -11
- data/lib/package/audit/services/package_filter.rb +24 -4
- data/lib/package/audit/services/package_finder.rb +1 -1
- data/lib/package/audit/services/package_printer.rb +65 -56
- data/lib/package/audit/technology/validator.rb +7 -14
- data/lib/package/audit/util/risk_legend.rb +49 -0
- data/lib/package/audit/util/summary_printer.rb +52 -40
- data/lib/package/audit/version.rb +1 -1
- data/sig/package/audit/cli.rbs +2 -0
- data/sig/package/audit/enum/format.rbs +12 -0
- data/sig/package/audit/enum/option.rbs +1 -1
- data/sig/package/audit/formatter/version_date.rbs +1 -1
- data/sig/package/audit/services/command_parser.rbs +7 -1
- data/sig/package/audit/services/package_filter.rbs +8 -1
- data/sig/package/audit/services/package_printer.rbs +7 -1
- data/sig/package/audit/util/risk_legend.rbs +9 -0
- data/sig/package/audit/util/summary_printer.rbs +16 -2
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9f4f23e752d513ff8a35a325296edf1d2d0a99b200d3e64ae92ce00ce145ac1
|
4
|
+
data.tar.gz: 043a77af2e0a46688ba0964e6d595bfe3a50fecc59f73f4957c24ca0ad4c9cbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ebd17673929a378540502f8757397bd37c1a86121fa99f691fb94b537b53f9eec5f7698be6076af0cc0aa7c163458eede4875316e6755a1a05e4a7b8e33e83b
|
7
|
+
data.tar.gz: 190255842a47fbf044d75703cf8263b177a91ed5a282c7d610cb99f27ad3fe194170b73ba09575afb6fc181b26f2d797fa11f4e9f481cb8f1619af581bd61f86
|
data/lib/package/audit/cli.rb
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
require_relative 'const/file'
|
2
2
|
require_relative 'const/time'
|
3
|
+
require_relative 'enum/format'
|
3
4
|
require_relative 'enum/option'
|
4
5
|
require_relative 'services/command_parser'
|
6
|
+
require_relative 'util//risk_legend'
|
5
7
|
require_relative 'version'
|
6
8
|
|
7
9
|
require 'json'
|
@@ -10,7 +12,7 @@ require 'thor'
|
|
10
12
|
module Package
|
11
13
|
module Audit
|
12
14
|
class CLI < Thor
|
13
|
-
default_task :
|
15
|
+
default_task :default
|
14
16
|
|
15
17
|
class_option Enum::Option::CONFIG,
|
16
18
|
aliases: '-c', banner: 'FILE',
|
@@ -24,18 +26,18 @@ module Package
|
|
24
26
|
class_option Enum::Option::INCLUDE_IGNORED,
|
25
27
|
type: :boolean, default: false,
|
26
28
|
desc: 'Include packages ignored by a configuration file'
|
27
|
-
class_option Enum::Option::
|
28
|
-
|
29
|
-
desc: 'Output reports using
|
29
|
+
class_option Enum::Option::FORMAT,
|
30
|
+
aliases: '-f', banner: Enum::Format.all.join('|'), type: :string,
|
31
|
+
desc: 'Output reports using a different format (e.g. CSV or Markdown)'
|
30
32
|
class_option Enum::Option::CSV_EXCLUDE_HEADERS,
|
31
33
|
type: :boolean, default: false,
|
32
|
-
desc: "Hide headers when using the
|
34
|
+
desc: "Hide headers when using the #{Enum::Format::CSV} format"
|
33
35
|
|
34
36
|
map '-v' => :version
|
35
37
|
map '--version' => :version
|
36
38
|
|
37
|
-
desc '
|
38
|
-
def
|
39
|
+
desc '[DIR]', 'Show a report of potentially deprecated, outdated or vulnerable packages'
|
40
|
+
def default(dir = Dir.pwd)
|
39
41
|
within_rescue_block { exit CommandParser.new(dir, options, Enum::Report::ALL).run }
|
40
42
|
end
|
41
43
|
|
@@ -57,7 +59,7 @@ module Package
|
|
57
59
|
|
58
60
|
desc 'risk', 'Print information on how risk is calculated'
|
59
61
|
def risk
|
60
|
-
Util::
|
62
|
+
Util::RiskLegend.print
|
61
63
|
end
|
62
64
|
|
63
65
|
desc 'version', 'Print the currently installed version of the package-audit gem'
|
@@ -70,7 +72,7 @@ module Package
|
|
70
72
|
end
|
71
73
|
|
72
74
|
def method_missing(command, *args)
|
73
|
-
invoke :
|
75
|
+
invoke :default, [command], args
|
74
76
|
end
|
75
77
|
|
76
78
|
def respond_to_missing?
|
@@ -3,13 +3,13 @@ module Package
|
|
3
3
|
module Const
|
4
4
|
module Cmd
|
5
5
|
BUNDLE_AUDIT = 'bundle-audit check --update'
|
6
|
-
BUNDLE_AUDIT_JSON = 'bundle-audit check --update --quiet --format json %s'
|
6
|
+
BUNDLE_AUDIT_JSON = 'bundle-audit check --update --quiet --format json "%s" 2>/dev/null'
|
7
7
|
|
8
8
|
NPM_AUDIT = 'npm audit'
|
9
9
|
NPM_AUDIT_JSON = 'npm audit --json'
|
10
10
|
|
11
11
|
YARN_AUDIT = 'yarn audit'
|
12
|
-
YARN_AUDIT_JSON = 'yarn audit --json --cwd %s'
|
12
|
+
YARN_AUDIT_JSON = 'yarn audit --json --cwd "%s"'
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -25,7 +25,7 @@ module Package
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def update(**attr)
|
28
|
-
attr.each { |key, value| instance_variable_set("@#{key}", value) }
|
28
|
+
attr.each { |key, value| instance_variable_set(:"@#{key}", value) }
|
29
29
|
end
|
30
30
|
|
31
31
|
def risk
|
@@ -41,7 +41,7 @@ module Package
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def group_list
|
44
|
-
@groups.join('
|
44
|
+
@groups.join(' ')
|
45
45
|
end
|
46
46
|
|
47
47
|
def vulnerabilities_grouped
|
@@ -18,6 +18,7 @@ module Package
|
|
18
18
|
def self.gemfile(dir)
|
19
19
|
current_dependencies = Bundler.with_unbundled_env do
|
20
20
|
ENV['BUNDLE_GEMFILE'] = "#{dir}/Gemfile"
|
21
|
+
Bundler.ui.level = 'error'
|
21
22
|
Bundler.reset!
|
22
23
|
Bundler.ui.silence do
|
23
24
|
Bundler.load.dependencies.to_h { |dep| [dep.name, dep] }
|
@@ -18,13 +18,28 @@ module Package
|
|
18
18
|
@dir = dir
|
19
19
|
@options = options
|
20
20
|
@report = report
|
21
|
-
@config = parse_config_file
|
21
|
+
@config = parse_config_file!
|
22
22
|
@groups = @options[Enum::Option::GROUP]
|
23
|
-
@technologies = parse_technologies
|
23
|
+
@technologies = parse_technologies!
|
24
|
+
validate_format!
|
24
25
|
@spinner = Util::Spinner.new('Evaluating packages and their dependencies...')
|
25
26
|
end
|
26
27
|
|
27
|
-
def run
|
28
|
+
def run
|
29
|
+
if File.file? @dir.to_s
|
30
|
+
raise "\"#{@dir}\" is a file instead of directory"
|
31
|
+
elsif !File.directory? @dir.to_s
|
32
|
+
raise "\"#{@dir}\" is not a valid directory"
|
33
|
+
elsif @technologies.empty?
|
34
|
+
raise 'No supported technologies found in this directory'
|
35
|
+
else
|
36
|
+
process_technologies
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def process_technologies # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
28
43
|
mutex = Mutex.new
|
29
44
|
cumulative_pkgs = []
|
30
45
|
thread_index = 0
|
@@ -34,7 +49,7 @@ module Package
|
|
34
49
|
Thread.new do
|
35
50
|
all_pkgs, ignored_pkgs = PackageFinder.new(@config, @dir, @report, @groups).run(technology)
|
36
51
|
ignored_pkgs = [] if @options[Enum::Option::INCLUDE_IGNORED]
|
37
|
-
cumulative_pkgs += all_pkgs || []
|
52
|
+
cumulative_pkgs += (all_pkgs || []) - (ignored_pkgs || [])
|
38
53
|
sleep 0.1 while technology_index != thread_index # print each technology in order
|
39
54
|
mutex.synchronize do
|
40
55
|
@spinner.stop
|
@@ -55,17 +70,15 @@ module Package
|
|
55
70
|
@spinner.stop
|
56
71
|
end
|
57
72
|
|
58
|
-
private
|
59
|
-
|
60
73
|
def print_results(technology, pkgs, ignored_pkgs)
|
61
74
|
PackagePrinter.new(@options, pkgs).print(Const::Fields::DEFAULT)
|
62
|
-
print_summary(technology, pkgs, ignored_pkgs) unless @options[Enum::Option::CSV
|
63
|
-
print_disclaimer(technology) unless @options[Enum::Option::
|
75
|
+
print_summary(technology, pkgs, ignored_pkgs) unless @options[Enum::Option::FORMAT] == Enum::Format::CSV
|
76
|
+
print_disclaimer(technology) unless @options[Enum::Option::FORMAT] == Enum::Format::CSV || pkgs.empty?
|
64
77
|
end
|
65
78
|
|
66
79
|
def print_summary(technology, pkgs, ignored_pkgs)
|
67
80
|
if @report == Enum::Report::ALL
|
68
|
-
Util::SummaryPrinter.statistics(technology, @report, pkgs, ignored_pkgs)
|
81
|
+
Util::SummaryPrinter.statistics(@options[Enum::Option::FORMAT], technology, @report, pkgs, ignored_pkgs)
|
69
82
|
else
|
70
83
|
Util::SummaryPrinter.total(technology, @report, pkgs, ignored_pkgs)
|
71
84
|
end
|
@@ -91,7 +104,7 @@ module Package
|
|
91
104
|
end
|
92
105
|
end
|
93
106
|
|
94
|
-
def parse_config_file
|
107
|
+
def parse_config_file!
|
95
108
|
if @options[Enum::Option::CONFIG].nil?
|
96
109
|
YAML.load_file("#{@dir}/#{Const::File::CONFIG}") if File.exist? "#{@dir}/#{Const::File::CONFIG}"
|
97
110
|
elsif File.exist? @options[Enum::Option::CONFIG]
|
@@ -101,7 +114,13 @@ module Package
|
|
101
114
|
end
|
102
115
|
end
|
103
116
|
|
104
|
-
def
|
117
|
+
def validate_format!
|
118
|
+
format = @options[Enum::Option::FORMAT]
|
119
|
+
raise ArgumentError, "Invalid format: #{format}, should be one of [#{Enum::Format.all.join('|')}]" unless
|
120
|
+
@options[Enum::Option::FORMAT].nil? || Enum::Format.all.include?(format)
|
121
|
+
end
|
122
|
+
|
123
|
+
def parse_technologies!
|
105
124
|
technology_validator = Technology::Validator.new(@dir)
|
106
125
|
@options[Enum::Option::TECHNOLOGY]&.each { |technology| technology_validator.validate! technology }
|
107
126
|
@options[Enum::Option::TECHNOLOGY] || Technology::Detector.new(@dir).detect
|
@@ -9,7 +9,8 @@ require 'yaml'
|
|
9
9
|
module Package
|
10
10
|
module Audit
|
11
11
|
class PackageFilter
|
12
|
-
def initialize(config)
|
12
|
+
def initialize(report, config)
|
13
|
+
@report = report
|
13
14
|
@config = config
|
14
15
|
end
|
15
16
|
|
@@ -30,9 +31,28 @@ module Package
|
|
30
31
|
end
|
31
32
|
|
32
33
|
def ignore_package?(pkg, yaml)
|
33
|
-
|
34
|
-
|
35
|
-
(
|
34
|
+
case @report
|
35
|
+
when Enum::Report::DEPRECATED
|
36
|
+
ignore_deprecated?(pkg, yaml)
|
37
|
+
when Enum::Report::OUTDATED
|
38
|
+
ignore_outdated?(pkg, yaml)
|
39
|
+
when Enum::Report::VULNERABLE
|
40
|
+
ignore_vulnerable?(pkg, yaml)
|
41
|
+
else
|
42
|
+
ignore_deprecated?(pkg, yaml) && ignore_outdated?(pkg, yaml) && ignore_vulnerable?(pkg, yaml)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def ignore_deprecated?(pkg, yaml)
|
47
|
+
!pkg.deprecated? || yaml&.dig(Const::YAML::DEPRECATED) == false
|
48
|
+
end
|
49
|
+
|
50
|
+
def ignore_outdated?(pkg, yaml)
|
51
|
+
!pkg.outdated? || yaml&.dig(Const::YAML::OUTDATED) == false
|
52
|
+
end
|
53
|
+
|
54
|
+
def ignore_vulnerable?(pkg, yaml)
|
55
|
+
!pkg.vulnerable? || yaml&.dig(Const::YAML::VULNERABLE) == false
|
36
56
|
end
|
37
57
|
end
|
38
58
|
end
|
@@ -21,8 +21,11 @@ module Package
|
|
21
21
|
check_fields(fields)
|
22
22
|
return if @pkgs.empty?
|
23
23
|
|
24
|
-
|
24
|
+
case @options[Enum::Option::FORMAT]
|
25
|
+
when Enum::Format::CSV
|
25
26
|
csv(fields, exclude_headers: @options[Enum::Option::CSV_EXCLUDE_HEADERS])
|
27
|
+
when Enum::Format::MARKDOWN
|
28
|
+
markdown(fields)
|
26
29
|
else
|
27
30
|
pretty(fields)
|
28
31
|
end
|
@@ -39,72 +42,78 @@ module Package
|
|
39
42
|
"Available fields names are: #{Const::Fields::DEFAULT}."
|
40
43
|
end
|
41
44
|
|
42
|
-
def pretty(fields = Const::Fields::DEFAULT) # rubocop:disable Metrics/AbcSize, Metrics/
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
curr_field_length = case key
|
49
|
-
when :vulnerabilities
|
50
|
-
gem.vulnerabilities_grouped.length
|
51
|
-
when :groups
|
52
|
-
gem.group_list.length
|
53
|
-
else
|
54
|
-
gem.send(key)&.gsub(BASH_FORMATTING_REGEX, '')&.length || 0
|
55
|
-
end
|
56
|
-
max_field_length = instance_variable_get "@max_#{key}"
|
57
|
-
instance_variable_set "@max_#{key}", [curr_field_length, max_field_length].max
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
line_length = fields.sum { |key| instance_variable_get "@max_#{key}" } +
|
62
|
-
(COLUMN_GAP * (fields.length - 1))
|
45
|
+
def pretty(fields = Const::Fields::DEFAULT) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
46
|
+
max_widths = get_field_max_widths(fields)
|
47
|
+
header = fields.map.with_index do |field, index|
|
48
|
+
Const::Fields::HEADERS[field].gsub(BASH_FORMATTING_REGEX, '').ljust(max_widths[index])
|
49
|
+
end.join(' ' * COLUMN_GAP)
|
50
|
+
separator = max_widths.map { |width| '=' * width }.join('=' * COLUMN_GAP)
|
63
51
|
|
64
|
-
puts
|
65
|
-
puts
|
66
|
-
|
67
|
-
}.join(' ' * COLUMN_GAP)
|
68
|
-
puts '=' * line_length
|
52
|
+
puts separator
|
53
|
+
puts header
|
54
|
+
puts separator
|
69
55
|
|
70
56
|
@pkgs.each do |pkg|
|
71
|
-
puts fields.map { |key|
|
72
|
-
val = pkg
|
73
|
-
val = case key
|
74
|
-
when :groups
|
75
|
-
pkg.group_list
|
76
|
-
when :risk_type
|
77
|
-
Formatter::Risk.new(pkg.risk_type).format
|
78
|
-
when :version
|
79
|
-
Formatter::Version.new(pkg.version, pkg.latest_version).format
|
80
|
-
when :vulnerabilities
|
81
|
-
Formatter::Vulnerability.new(pkg.vulnerabilities).format
|
82
|
-
when :latest_version_date
|
83
|
-
Formatter::VersionDate.new(pkg.latest_version_date).format
|
84
|
-
else
|
85
|
-
val
|
86
|
-
end
|
87
|
-
|
57
|
+
puts fields.map.with_index { |key, index|
|
58
|
+
val = get_field_value(pkg, key)
|
88
59
|
formatting_length = val.length - val.gsub(BASH_FORMATTING_REGEX, '').length
|
89
|
-
val.ljust(
|
60
|
+
val.ljust(max_widths[index] + formatting_length)
|
90
61
|
}.join(' ' * COLUMN_GAP)
|
91
62
|
end
|
92
63
|
end
|
93
64
|
|
94
|
-
def csv(fields, exclude_headers: false)
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
65
|
+
def csv(fields = Const::Fields::DEFAULT, exclude_headers: false)
|
66
|
+
puts fields.join(',') unless exclude_headers
|
67
|
+
@pkgs.map do |pkg|
|
68
|
+
puts fields.map { |field| get_field_value(pkg, field) }.join(',').gsub(BASH_FORMATTING_REGEX, '')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def markdown(fields = Const::Fields::DEFAULT) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
73
|
+
max_widths = get_field_max_widths(fields)
|
74
|
+
header = fields.map.with_index do |field, index|
|
75
|
+
Const::Fields::HEADERS[field].gsub(BASH_FORMATTING_REGEX, '').ljust(max_widths[index])
|
76
|
+
end.join(' | ')
|
77
|
+
separator = max_widths.map { |width| ":#{'-' * width}" }.join('-|')
|
78
|
+
|
79
|
+
puts "| #{header} |"
|
80
|
+
puts "|#{separator}-|"
|
81
|
+
|
82
|
+
@pkgs.each do |pkg|
|
83
|
+
row = fields.map.with_index do |key, index|
|
84
|
+
val = get_field_value(pkg, key)
|
85
|
+
formatting_length = val.length - val.gsub(BASH_FORMATTING_REGEX, '').length
|
86
|
+
val.ljust(max_widths[index] + formatting_length)
|
103
87
|
end
|
88
|
+
puts "| #{row.join(' | ')} |"
|
104
89
|
end
|
90
|
+
end
|
105
91
|
|
106
|
-
|
107
|
-
|
92
|
+
def get_field_max_widths(fields)
|
93
|
+
# Calculate the maximum width for each column, including header titles and content
|
94
|
+
fields.map do |field|
|
95
|
+
[@pkgs.map do |pkg|
|
96
|
+
value = get_field_value(pkg, field).to_s.gsub(BASH_FORMATTING_REGEX, '').length
|
97
|
+
value
|
98
|
+
end.max, Const::Fields::HEADERS[field].gsub(BASH_FORMATTING_REGEX, '').length].max
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_field_value(pkg, field) # rubocop:disable Metrics/MethodLength
|
103
|
+
case field
|
104
|
+
when :groups
|
105
|
+
pkg.group_list
|
106
|
+
when :risk_type
|
107
|
+
Formatter::Risk.new(pkg.risk_type).format
|
108
|
+
when :version
|
109
|
+
Formatter::Version.new(pkg.version, pkg.latest_version).format
|
110
|
+
when :vulnerabilities
|
111
|
+
Formatter::Vulnerability.new(pkg.vulnerabilities).format
|
112
|
+
when :latest_version_date
|
113
|
+
Formatter::VersionDate.new(pkg.latest_version_date).format
|
114
|
+
else
|
115
|
+
pkg.send(field) || ''
|
116
|
+
end
|
108
117
|
end
|
109
118
|
end
|
110
119
|
end
|
@@ -28,27 +28,20 @@ module Package
|
|
28
28
|
package_lock_json_present = File.exist?("#{@dir}/#{Const::File::PACKAGE_LOCK_JSON}")
|
29
29
|
yarn_lock_present = File.exist?("#{@dir}/#{Const::File::YARN_LOCK}")
|
30
30
|
|
31
|
-
unless package_json_present
|
32
|
-
|
33
|
-
|
34
|
-
unless package_lock_json_present || yarn_lock_present
|
35
|
-
puts Util::BashColor.red("\"#{Const::File::PACKAGE_LOCK_JSON}\" or \"#{Const::File::YARN_LOCK}\" " \
|
36
|
-
"was not found in #{@dir}")
|
37
|
-
end
|
31
|
+
raise "\"#{Const::File::PACKAGE_JSON}\" was not found in #{@dir}" unless package_json_present
|
32
|
+
|
33
|
+
return if package_lock_json_present || yarn_lock_present
|
38
34
|
|
39
|
-
|
35
|
+
raise "\"#{Const::File::PACKAGE_LOCK_JSON}\" or \"#{Const::File::YARN_LOCK}\" " \
|
36
|
+
"was not found in #{@dir}"
|
40
37
|
end
|
41
38
|
|
42
39
|
def validate_ruby!
|
43
40
|
gemfile_present = File.exist?("#{@dir}/#{Const::File::GEMFILE}")
|
44
41
|
gemfile_lock_present = File.exist?("#{@dir}/#{Const::File::GEMFILE_LOCK}")
|
45
42
|
|
46
|
-
|
47
|
-
unless gemfile_lock_present
|
48
|
-
puts Util::BashColor.red("\"#{Const::File::GEMFILE_LOCK}\" was not found in #{@dir}")
|
49
|
-
end
|
50
|
-
|
51
|
-
exit 1 unless gemfile_present && gemfile_lock_present
|
43
|
+
raise "\"#{Const::File::GEMFILE}\" was not found in #{@dir}" unless gemfile_present
|
44
|
+
raise "\"#{Const::File::GEMFILE_LOCK}\" was not found in #{@dir}" unless gemfile_lock_present
|
52
45
|
end
|
53
46
|
end
|
54
47
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative '../const/time'
|
2
|
+
require_relative 'bash_color'
|
3
|
+
|
4
|
+
module Package
|
5
|
+
module Audit
|
6
|
+
module Util
|
7
|
+
module RiskLegend
|
8
|
+
def self.print # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
9
|
+
puts Util::BashColor.blue('1. Check if the package has a security vulnerability.')
|
10
|
+
puts ' If yes, the following vulnerability -> risk mapping is used:'
|
11
|
+
puts " - #{Util::BashColor.red('unknown')} vulnerability\t-> #{Util::BashColor.red('high')} risk"
|
12
|
+
puts " - #{Util::BashColor.red('critical')} vulnerability\t-> #{Util::BashColor.red('high')} risk"
|
13
|
+
puts " - #{Util::BashColor.red('high')} vulnerability\t-> #{Util::BashColor.red('high')} risk"
|
14
|
+
puts " - #{Util::BashColor.orange('medium')} vulnerability\t-> #{Util::BashColor.orange('medium')} risk"
|
15
|
+
puts " - #{Util::BashColor.orange('moderate')} vulnerability\t-> #{Util::BashColor.orange('medium')} risk" # rubocop:disable Layout/LineLength
|
16
|
+
puts " - #{Util::BashColor.yellow('low')} vulnerability\t-> #{Util::BashColor.yellow('low')} risk"
|
17
|
+
|
18
|
+
puts
|
19
|
+
|
20
|
+
puts Util::BashColor.blue('2. Check the package for potential deprecation.')
|
21
|
+
puts " If no new releases by author for at least #{Const::Time::YEARS_ELAPSED_TO_BE_OUTDATED} years:"
|
22
|
+
puts " - assign the risk to\t-> #{Util::BashColor.orange('medium')} risk"
|
23
|
+
|
24
|
+
puts
|
25
|
+
|
26
|
+
puts Util::BashColor.blue('3. Check if a newer version of the package is available.')
|
27
|
+
|
28
|
+
puts ' If yes, assign risk as follows:'
|
29
|
+
puts " - #{Util::BashColor.orange('major version')} mismatch\t-> #{Util::BashColor.orange('medium')} risk" # rubocop:disable Layout/LineLength
|
30
|
+
puts " - #{Util::BashColor.yellow('minor version')} mismatch\t-> #{Util::BashColor.yellow('low')} risk"
|
31
|
+
puts " - #{Util::BashColor.green('patch version')} mismatch\t-> #{Util::BashColor.yellow('low')} risk"
|
32
|
+
puts " - #{Util::BashColor.green('build version')} mismatch\t-> #{Util::BashColor.yellow('low')} risk"
|
33
|
+
|
34
|
+
puts
|
35
|
+
|
36
|
+
puts Util::BashColor.blue('4. Take the highest risk from the first 3 steps.')
|
37
|
+
puts ' If two risks match in severity, use the following precedence:'
|
38
|
+
puts " - #{Util::BashColor.red('vulnerability')} > #{Util::BashColor.orange('deprecation')} > #{Util::BashColor.yellow('outdatedness')}" # rubocop:disable Layout/LineLength
|
39
|
+
|
40
|
+
puts
|
41
|
+
|
42
|
+
puts Util::BashColor.blue('5. Check whether the package is used in production or not.')
|
43
|
+
puts ' If a package is limited to a non-production group:'
|
44
|
+
puts " - cap risk severity to\t -> #{Util::BashColor.yellow('low')} risk"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -33,59 +33,71 @@ module Package
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
def self.statistics(technology, report, pkgs, ignored_pkgs)
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
def self.statistics(format, technology, report, pkgs, ignored_pkgs)
|
37
|
+
stats = calculate_statistics(pkgs, ignored_pkgs)
|
38
|
+
display_results(format, technology, report, pkgs, ignored_pkgs, stats)
|
39
|
+
end
|
40
|
+
|
41
|
+
private_class_method def self.calculate_statistics(pkgs, ignored_pkgs)
|
42
|
+
stats = {
|
43
|
+
outdated: count_status(pkgs, :outdated?),
|
44
|
+
deprecated: count_status(pkgs, :deprecated?),
|
45
|
+
vulnerable: count_status(pkgs, :vulnerable?),
|
46
|
+
outdated_ignored: count_status(ignored_pkgs, :outdated?),
|
47
|
+
deprecated_ignored: count_status(ignored_pkgs, :deprecated?),
|
48
|
+
vulnerable_ignored: count_status(ignored_pkgs, :vulnerable?)
|
49
|
+
}
|
50
|
+
|
51
|
+
stats[:vulnerabilities] = pkgs.sum { |pkg| pkg.vulnerabilities.length }
|
52
|
+
stats
|
53
|
+
end
|
40
54
|
|
41
|
-
|
55
|
+
private_class_method def self.count_status(pkgs, status)
|
56
|
+
pkgs.count(&status)
|
57
|
+
end
|
42
58
|
|
59
|
+
private_class_method def self.display_results(format, technology, report, pkgs, ignored_pkgs, stats) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
|
43
60
|
if pkgs.any?
|
44
|
-
|
45
|
-
|
61
|
+
print status_message(stats)
|
62
|
+
print Util::BashColor.cyan(' \\') if format == Enum::Format::MARKDOWN
|
63
|
+
puts
|
46
64
|
total(technology, report, pkgs, ignored_pkgs)
|
65
|
+
elsif ignored_pkgs.any?
|
66
|
+
print status_message(stats)
|
67
|
+
print Util::BashColor.cyan(' \\') if format == Enum::Format::MARKDOWN
|
68
|
+
puts
|
69
|
+
puts Util::BashColor.green("There are no deprecated, outdated or vulnerable #{technology} " \
|
70
|
+
"packages (#{ignored_pkgs.length} ignored)!\n")
|
47
71
|
else
|
48
72
|
puts Util::BashColor.green("There are no deprecated, outdated or vulnerable #{technology} packages!\n")
|
49
73
|
end
|
50
74
|
end
|
51
75
|
|
52
|
-
def self.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
puts " - #{Util::BashColor.red('critical')} vulnerability\t-> #{Util::BashColor.red('high')} risk"
|
57
|
-
puts " - #{Util::BashColor.red('high')} vulnerability\t-> #{Util::BashColor.red('high')} risk"
|
58
|
-
puts " - #{Util::BashColor.orange('medium')} vulnerability\t-> #{Util::BashColor.orange('medium')} risk"
|
59
|
-
puts " - #{Util::BashColor.orange('moderate')} vulnerability\t-> #{Util::BashColor.orange('medium')} risk" # rubocop:disable Layout/LineLength
|
60
|
-
puts " - #{Util::BashColor.yellow('low')} vulnerability\t-> #{Util::BashColor.yellow('low')} risk"
|
61
|
-
|
62
|
-
puts
|
63
|
-
|
64
|
-
puts Util::BashColor.blue('2. Check the package for potential deprecation.')
|
65
|
-
puts " If no new releases by author for at least #{Const::Time::YEARS_ELAPSED_TO_BE_OUTDATED} years:"
|
66
|
-
puts " - assign the risk to\t-> #{Util::BashColor.orange('medium')} risk"
|
76
|
+
private_class_method def self.status_message(stats)
|
77
|
+
outdated_str = "#{stats[:outdated]} outdated" + outdated_details(stats)
|
78
|
+
deprecated_str = "#{stats[:deprecated]} deprecated" + deprecated_details(stats)
|
79
|
+
vulnerable_str = "#{stats[:vulnerable]} vulnerable" + vulnerability_details(stats)
|
67
80
|
|
68
|
-
|
69
|
-
|
70
|
-
puts Util::BashColor.blue('3. Check if a newer version of the package is available.')
|
71
|
-
|
72
|
-
puts ' If yes, assign risk as follows:'
|
73
|
-
puts " - #{Util::BashColor.orange('major version')} mismatch\t-> #{Util::BashColor.orange('medium')} risk" # rubocop:disable Layout/LineLength
|
74
|
-
puts " - #{Util::BashColor.yellow('minor version')} mismatch\t-> #{Util::BashColor.yellow('low')} risk"
|
75
|
-
puts " - #{Util::BashColor.green('patch version')} mismatch\t-> #{Util::BashColor.yellow('low')} risk"
|
76
|
-
puts " - #{Util::BashColor.green('build version')} mismatch\t-> #{Util::BashColor.yellow('low')} risk"
|
77
|
-
|
78
|
-
puts
|
81
|
+
Util::BashColor.cyan("#{vulnerable_str}, #{outdated_str}, #{deprecated_str}.")
|
82
|
+
end
|
79
83
|
|
80
|
-
|
81
|
-
|
82
|
-
|
84
|
+
private_class_method def self.deprecated_details(stats)
|
85
|
+
details = []
|
86
|
+
details << "#{stats[:deprecated_ignored]} ignored" if stats[:deprecated_ignored].positive?
|
87
|
+
details.any? ? " (#{details.join(', ')})" : ''
|
88
|
+
end
|
83
89
|
|
84
|
-
|
90
|
+
private_class_method def self.outdated_details(stats)
|
91
|
+
details = []
|
92
|
+
details << "#{stats[:outdated_ignored]} ignored" if stats[:outdated_ignored].positive?
|
93
|
+
details.any? ? " (#{details.join(', ')})" : ''
|
94
|
+
end
|
85
95
|
|
86
|
-
|
87
|
-
|
88
|
-
|
96
|
+
private_class_method def self.vulnerability_details(stats)
|
97
|
+
details = []
|
98
|
+
details << "#{stats[:vulnerabilities]} vulnerabilities" if stats[:vulnerabilities].positive?
|
99
|
+
details << "#{stats[:vulnerable_ignored]} ignored" if stats[:vulnerable_ignored].positive?
|
100
|
+
details.any? ? " (#{details.join(', ')})" : ''
|
89
101
|
end
|
90
102
|
end
|
91
103
|
end
|
data/sig/package/audit/cli.rbs
CHANGED
@@ -17,15 +17,21 @@ module Package
|
|
17
17
|
|
18
18
|
def learn_more_command: (String) -> String?
|
19
19
|
|
20
|
-
def parse_config_file
|
20
|
+
def parse_config_file!: -> Hash[String, untyped]?
|
21
21
|
|
22
22
|
def parse_technologies: -> Array[String]
|
23
23
|
|
24
|
+
def parse_technologies!: -> Array[String]
|
25
|
+
|
24
26
|
def print_disclaimer: (String) -> void
|
25
27
|
|
26
28
|
def print_results: (String, Array[Package], Array[Package]) -> void
|
27
29
|
|
28
30
|
def print_summary: (String, Array[Package], Array[Package]) -> void
|
31
|
+
|
32
|
+
def process_technologies: -> int
|
33
|
+
|
34
|
+
def validate_format!: -> void
|
29
35
|
end
|
30
36
|
end
|
31
37
|
end
|
@@ -2,15 +2,22 @@ module Package
|
|
2
2
|
module Audit
|
3
3
|
class PackageFilter
|
4
4
|
@config: Hash[String, untyped]?
|
5
|
+
@report: Symbol
|
5
6
|
|
6
|
-
def initialize: (Hash[String, untyped]?) -> void
|
7
|
+
def initialize: (Symbol, Hash[String, untyped]?) -> void
|
7
8
|
|
8
9
|
def ignored?: (Package) -> bool
|
9
10
|
|
10
11
|
private
|
11
12
|
|
13
|
+
def ignore_deprecated?: (Package, Hash[String, untyped]?) -> bool
|
14
|
+
|
15
|
+
def ignore_outdated?: (Package, Hash[String, untyped]?) -> bool
|
16
|
+
|
12
17
|
def ignore_package?: (Package, Hash[String, untyped]?) -> bool
|
13
18
|
|
19
|
+
def ignore_vulnerable?: (Package, Hash[String, untyped]?) -> bool
|
20
|
+
|
14
21
|
def pkg_version_in_config?: (Package, Hash[String, untyped]?) -> bool
|
15
22
|
|
16
23
|
def pkg_yaml_from_config: (Package) -> Hash[String, untyped]?
|
@@ -18,7 +18,13 @@ module Package
|
|
18
18
|
|
19
19
|
def csv: (Array[Symbol], ?exclude_headers: bool) -> void
|
20
20
|
|
21
|
-
def
|
21
|
+
def get_field_max_widths: (Array[Symbol]) -> Array[Integer]
|
22
|
+
|
23
|
+
def get_field_value: (Package, Symbol) -> String
|
24
|
+
|
25
|
+
def markdown: (Array[Symbol]) -> void
|
26
|
+
|
27
|
+
def pretty: (Array[Symbol]) -> void
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
@@ -4,15 +4,29 @@ module Package
|
|
4
4
|
module SummaryPrinter
|
5
5
|
def self.all: -> void
|
6
6
|
|
7
|
+
def self.calculate_statistics: (Array[Package], Array[Package]) -> Hash[Symbol, Integer]
|
8
|
+
|
9
|
+
def self.count_status: (Array[Package], Symbol) -> Integer
|
10
|
+
|
7
11
|
def self.deprecated: -> void
|
8
12
|
|
9
|
-
def self.
|
13
|
+
def self.display_results: (String, String, Symbol, Array[Package], Array[Package], Hash[Symbol, Integer]) -> void
|
10
14
|
|
11
|
-
def self.statistics: (String, Symbol, Array[Package], Array[Package]) -> void
|
15
|
+
def self.statistics: (String, String, Symbol, Array[Package], Array[Package]) -> void
|
16
|
+
|
17
|
+
def self.status_message: (Hash[Symbol, Integer]) -> String
|
12
18
|
|
13
19
|
def self.total: (String, Symbol, Array[Package], Array[Package]) -> void
|
14
20
|
|
15
21
|
def self.vulnerable: (String, String?) -> void
|
22
|
+
|
23
|
+
def self.deprecated_details: (Hash[Symbol, Integer]) -> String
|
24
|
+
|
25
|
+
def self.outdated_details: (Hash[Symbol, Integer]) -> String
|
26
|
+
|
27
|
+
def self.vulnerability_details: (Hash[Symbol, Integer]) -> String
|
28
|
+
|
29
|
+
def calculate_statistics: (Array[Package], Array[Package]) -> Hash[Symbol, Integer]
|
16
30
|
end
|
17
31
|
end
|
18
32
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: package-audit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tactica Communications Inc.
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler-audit
|
@@ -54,6 +54,7 @@ files:
|
|
54
54
|
- lib/package/audit/const/file.rb
|
55
55
|
- lib/package/audit/const/time.rb
|
56
56
|
- lib/package/audit/const/yaml.rb
|
57
|
+
- lib/package/audit/enum/format.rb
|
57
58
|
- lib/package/audit/enum/group.rb
|
58
59
|
- lib/package/audit/enum/option.rb
|
59
60
|
- lib/package/audit/enum/report.rb
|
@@ -85,6 +86,7 @@ files:
|
|
85
86
|
- lib/package/audit/technology/detector.rb
|
86
87
|
- lib/package/audit/technology/validator.rb
|
87
88
|
- lib/package/audit/util/bash_color.rb
|
89
|
+
- lib/package/audit/util/risk_legend.rb
|
88
90
|
- lib/package/audit/util/spinner.rb
|
89
91
|
- lib/package/audit/util/summary_printer.rb
|
90
92
|
- lib/package/audit/version.rb
|
@@ -94,6 +96,7 @@ files:
|
|
94
96
|
- sig/package/audit/const/file.rbs
|
95
97
|
- sig/package/audit/const/time.rbs
|
96
98
|
- sig/package/audit/const/yaml.rbs
|
99
|
+
- sig/package/audit/enum/format.rbs
|
97
100
|
- sig/package/audit/enum/group.rbs
|
98
101
|
- sig/package/audit/enum/option.rbs
|
99
102
|
- sig/package/audit/enum/report.rbs
|
@@ -125,6 +128,7 @@ files:
|
|
125
128
|
- sig/package/audit/technology/detector.rbs
|
126
129
|
- sig/package/audit/technology/validator.rbs
|
127
130
|
- sig/package/audit/util/bash_color.rbs
|
131
|
+
- sig/package/audit/util/risk_legend.rbs
|
128
132
|
- sig/package/audit/util/spinner.rbs
|
129
133
|
- sig/package/audit/util/summary_printer.rbs
|
130
134
|
- sig/package/audit/version.rbs
|
@@ -143,14 +147,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
143
147
|
requirements:
|
144
148
|
- - ">="
|
145
149
|
- !ruby/object:Gem::Version
|
146
|
-
version: 2.
|
150
|
+
version: 2.6.0
|
147
151
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
152
|
requirements:
|
149
153
|
- - ">="
|
150
154
|
- !ruby/object:Gem::Version
|
151
155
|
version: '0'
|
152
156
|
requirements: []
|
153
|
-
rubygems_version: 3.4.
|
157
|
+
rubygems_version: 3.4.22
|
154
158
|
signing_key:
|
155
159
|
specification_version: 4
|
156
160
|
summary: A helper tool to find outdated, deprecated and vulnerable dependencies.
|