package-audit 0.5.1 → 0.6.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 +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.
|