package-audit 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/exe/package-audit +10 -0
- data/lib/package/audit/cli.rb +134 -0
- data/lib/package/audit/const.rb +5 -0
- data/lib/package/audit/dependency.rb +57 -0
- data/lib/package/audit/dependency_printer.rb +128 -0
- data/lib/package/audit/enum/environment.rb +15 -0
- data/lib/package/audit/enum/risk_explanation.rb +14 -0
- data/lib/package/audit/enum/risk_type.rb +12 -0
- data/lib/package/audit/enum/vulnerability_type.rb +14 -0
- data/lib/package/audit/formatter/base.rb +11 -0
- data/lib/package/audit/formatter/risk.rb +28 -0
- data/lib/package/audit/formatter/version.rb +33 -0
- data/lib/package/audit/formatter/version_date.rb +28 -0
- data/lib/package/audit/formatter/vulnerability.rb +37 -0
- data/lib/package/audit/risk.rb +27 -0
- data/lib/package/audit/risk_calculator.rb +65 -0
- data/lib/package/audit/ruby/bundler_specs.rb +28 -0
- data/lib/package/audit/ruby/gem_collection.rb +43 -0
- data/lib/package/audit/ruby/gem_meta_data.rb +58 -0
- data/lib/package/audit/ruby/vulnerability_finder.rb +24 -0
- data/lib/package/audit/util/bash_color.rb +35 -0
- data/lib/package/audit/util/summary_printer.rb +75 -0
- data/lib/package/audit/version.rb +5 -0
- data/sig/const.rbs +5 -0
- data/sig/package/audit/cli.rbs +31 -0
- data/sig/package/audit/dependency.rbs +35 -0
- data/sig/package/audit/dependency_printer.rbs +24 -0
- data/sig/package/audit/enum/environment.rbs +13 -0
- data/sig/package/audit/enum/risk_explanation.rbs +12 -0
- data/sig/package/audit/enum/risk_type.rbs +12 -0
- data/sig/package/audit/enum/vulnerability_type.rbs +14 -0
- data/sig/package/audit/formatter/base.rbs +9 -0
- data/sig/package/audit/formatter/risk_printer.rbs +13 -0
- data/sig/package/audit/formatter/version_date.rbs +13 -0
- data/sig/package/audit/formatter/version_printer.rbs +14 -0
- data/sig/package/audit/formatter/vulnerability.rbs +13 -0
- data/sig/package/audit/risk.rbs +12 -0
- data/sig/package/audit/risk_calculator.rbs +21 -0
- data/sig/package/audit/ruby/bundler_specs.rbs +11 -0
- data/sig/package/audit/ruby/gem_collection.rbs +15 -0
- data/sig/package/audit/ruby/gem_meta_data.rbs +23 -0
- data/sig/package/audit/ruby/vulnerability_finder.rbs +9 -0
- data/sig/package/audit/util/bash_color.rbs +21 -0
- data/sig/package/audit/util/summary_printer.rbs +21 -0
- data/sig/package/audit/version.rbs +5 -0
- metadata +121 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative './bundler_specs'
|
2
|
+
require_relative './../enum/risk_type'
|
3
|
+
|
4
|
+
module Package
|
5
|
+
module Audit
|
6
|
+
module Ruby
|
7
|
+
class GemCollection
|
8
|
+
def self.all
|
9
|
+
specs = BundlerSpecs.gemfile
|
10
|
+
dependencies = specs.map { |spec| Dependency.new(spec.name, spec.version) }
|
11
|
+
vulnerable_deps = VulnerabilityFinder.run
|
12
|
+
GemMetaData.new(dependencies + vulnerable_deps).fetch.filter(&:risk?).sort_by(&:name).uniq(&:name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.deprecated
|
16
|
+
specs = BundlerSpecs.gemfile
|
17
|
+
dependencies = specs.map { |spec| Dependency.new(spec.name, spec.version) }
|
18
|
+
|
19
|
+
GemMetaData.new(dependencies).fetch.filter do |dep|
|
20
|
+
dep.risk.explanation == Enum::RiskExplanation::POTENTIAL_DEPRECATION
|
21
|
+
end.sort_by(&:name).uniq(&:name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.outdated(include_implicit: false)
|
25
|
+
specs = include_implicit ? BundlerSpecs.all : BundlerSpecs.gemfile
|
26
|
+
dependencies = specs.map { |spec| Dependency.new(spec.name, spec.version) }
|
27
|
+
|
28
|
+
GemMetaData.new(dependencies).fetch.filter do |dep|
|
29
|
+
dep.version < dep.latest_version
|
30
|
+
end.sort_by(&:name).uniq(&:name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.vulnerable
|
34
|
+
dependencies = VulnerabilityFinder.run
|
35
|
+
|
36
|
+
GemMetaData.new(dependencies).fetch.filter do |dep|
|
37
|
+
dep.risk.explanation == Enum::RiskExplanation::VULNERABILITY
|
38
|
+
end.sort_by(&:name).uniq(&:name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative '../dependency'
|
2
|
+
|
3
|
+
module Package
|
4
|
+
module Audit
|
5
|
+
module Ruby
|
6
|
+
class GemMetaData
|
7
|
+
def initialize(dependencies)
|
8
|
+
@dependencies = dependencies
|
9
|
+
@gem_hash = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def fetch
|
13
|
+
find_rubygems_metadata
|
14
|
+
assign_groups
|
15
|
+
@gem_hash.values
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def find_rubygems_metadata # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
21
|
+
fetcher = Gem::SpecFetcher.fetcher
|
22
|
+
|
23
|
+
@dependencies.each do |dep|
|
24
|
+
gem_dependency = Gem::Dependency.new dep.name, ">= #{dep.version}"
|
25
|
+
local_version_date = Time.new(0)
|
26
|
+
latest_version_date = Time.new(0)
|
27
|
+
local_version = Gem::Version.new(dep.version)
|
28
|
+
latest_version = Gem::Version.new('0.0.0')
|
29
|
+
|
30
|
+
remote_dependencies, = fetcher.spec_for_dependency gem_dependency
|
31
|
+
|
32
|
+
remote_dependencies.each do |remote_spec, _|
|
33
|
+
latest_version = remote_spec.version if latest_version < remote_spec.version
|
34
|
+
latest_version_date = remote_spec.date if latest_version_date < remote_spec.date
|
35
|
+
local_version_date = remote_spec.date if local_version == remote_spec.version
|
36
|
+
end
|
37
|
+
|
38
|
+
@gem_hash[dep.name] = dep
|
39
|
+
dep.update latest_version: latest_version.to_s,
|
40
|
+
version_date: local_version_date.strftime('%Y-%m-%d'),
|
41
|
+
latest_version_date: latest_version_date.strftime('%Y-%m-%d')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def assign_groups # rubocop:disable Metrics/AbcSize
|
46
|
+
definition = Bundler.definition
|
47
|
+
groups = definition.groups.uniq.sort
|
48
|
+
groups.each do |group|
|
49
|
+
specs = definition.specs_for([group])
|
50
|
+
specs.each do |spec|
|
51
|
+
@gem_hash[spec.name].update(groups: @gem_hash[spec.name].groups | [group]) if @gem_hash.key? spec.name
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative '../enum/vulnerability_type'
|
2
|
+
|
3
|
+
module Package
|
4
|
+
module Audit
|
5
|
+
module Ruby
|
6
|
+
class VulnerabilityFinder
|
7
|
+
def self.run # rubocop:disable Metrics/AbcSize
|
8
|
+
gem_hash = {}
|
9
|
+
json_str = `bundle exec bundle-audit check --update --quiet --format json`
|
10
|
+
json_results = JSON.parse(json_str, symbolize_names: true)[:results]
|
11
|
+
json_results.each do |result|
|
12
|
+
gem_name = result[:gem][:name]
|
13
|
+
vulnerability = result[:advisory][:criticality] || Enum::VulnerabilityType::UNKNOWN
|
14
|
+
unless gem_hash.key? gem_name
|
15
|
+
gem_hash[gem_name] = Dependency.new result[:gem][:name], result[:gem][:version]
|
16
|
+
end
|
17
|
+
gem_hash[gem_name].update vulnerabilities: gem_hash[gem_name].vulnerabilities + [vulnerability]
|
18
|
+
end
|
19
|
+
gem_hash.values
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
module Util
|
4
|
+
module BashColor
|
5
|
+
def self.green(str)
|
6
|
+
"\e[32m#{str}\e[0m"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.yellow(str)
|
10
|
+
"\e[33m#{str}\e[0m"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.orange(str)
|
14
|
+
"\e[38;5;208m#{str}\e[0m"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.red(str)
|
18
|
+
"\e[31m#{str}\e[0m"
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.cyan(str)
|
22
|
+
"\e[36m#{str}\e[0m"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.magenta(str)
|
26
|
+
"\e[35m#{str}\e[0m"
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.blue(str)
|
30
|
+
"\e[34m#{str}\e[0m"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require_relative '../const'
|
2
|
+
require_relative './bash_color'
|
3
|
+
|
4
|
+
module Package
|
5
|
+
module Audit
|
6
|
+
module Util
|
7
|
+
module SummaryPrinter
|
8
|
+
def self.report
|
9
|
+
printf("\n%<info>s\n%<cmd>s\n\n",
|
10
|
+
info: Util::BashColor.blue('To show how risk is calculated run:'),
|
11
|
+
cmd: Util::BashColor.magenta(' > bundle exec package-audit risk'))
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.deprecated
|
15
|
+
puts Util::BashColor.blue("\nAlthough gems listed above have no recent updates, they may not be deprecated.")
|
16
|
+
puts Util::BashColor.blue("Please contact the gem author for more information about its status.\n")
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.outdated
|
20
|
+
printf("\n%<info>s\n%<cmd>s\n\n",
|
21
|
+
info: Util::BashColor.blue('To show both Gemfile gems and their dependencies run:'),
|
22
|
+
cmd: Util::BashColor.magenta(' > bundle exec package-audit outdated --include-implicit'))
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.vulnerable
|
26
|
+
printf("\n%<info>s\n%<cmd>s\n\n",
|
27
|
+
info: Util::BashColor.blue('To get more information about the vulnerabilities run:'),
|
28
|
+
cmd: Util::BashColor.magenta(' > bundle exec bundle-audit check --update'))
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.total(num)
|
32
|
+
puts Util::BashColor.cyan("\nFound a total of #{num} gems.")
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.risk # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
36
|
+
puts Util::BashColor.blue('1. Check if the dependency has a security vulnerability.')
|
37
|
+
puts ' If yes, the following vulnerability -> risk mapping is used:'
|
38
|
+
puts " - #{Util::BashColor.red('unknown')} vulnerability\t-> #{Util::BashColor.red('high')} risk"
|
39
|
+
puts " - #{Util::BashColor.red('critical')} vulnerability\t-> #{Util::BashColor.red('high')} risk"
|
40
|
+
puts " - #{Util::BashColor.red('high')} vulnerability\t-> #{Util::BashColor.red('high')} risk"
|
41
|
+
puts " - #{Util::BashColor.orange('medium')} vulnerability\t-> #{Util::BashColor.orange('medium')} risk"
|
42
|
+
puts " - #{Util::BashColor.yellow('low')} vulnerability\t-> #{Util::BashColor.yellow('low')} risk"
|
43
|
+
|
44
|
+
puts
|
45
|
+
|
46
|
+
puts Util::BashColor.blue('2. Check the dependency for potential deprecation.')
|
47
|
+
puts " If no new releases by author for at least #{Const::YEARS_ELAPSED_TO_BE_OUTDATED} years:"
|
48
|
+
puts " - assign the risk to\t-> #{Util::BashColor.orange('medium')} risk"
|
49
|
+
|
50
|
+
puts
|
51
|
+
|
52
|
+
puts Util::BashColor.blue('3. Check if a newer version of the dependency is available.')
|
53
|
+
|
54
|
+
puts ' If yes, assign risk as follows:'
|
55
|
+
puts " - #{Util::BashColor.orange('major version')} mismatch\t-> #{Util::BashColor.orange('medium')} risk" # rubocop:disable Layout/LineLength
|
56
|
+
puts " - #{Util::BashColor.yellow('minor version')} mismatch\t-> #{Util::BashColor.yellow('low')} risk"
|
57
|
+
puts " - #{Util::BashColor.green('patch version')} mismatch\t-> #{Util::BashColor.yellow('low')} risk"
|
58
|
+
puts " - #{Util::BashColor.green('build version')} mismatch\t-> #{Util::BashColor.yellow('low')} risk"
|
59
|
+
|
60
|
+
puts
|
61
|
+
|
62
|
+
puts Util::BashColor.blue('4. Take the highest risk from the first 3 steps.')
|
63
|
+
puts ' If two risks match in severity, use the following precedence:'
|
64
|
+
puts " - #{Util::BashColor.red('vulnerability')} > #{Util::BashColor.orange('deprecation')} > #{Util::BashColor.yellow('outdatedness')}" # rubocop:disable Layout/LineLength
|
65
|
+
|
66
|
+
puts
|
67
|
+
|
68
|
+
puts Util::BashColor.blue('5. Check whether the dependency is used in production or not.')
|
69
|
+
puts ' If a dependency is limited to a non-production environment:'
|
70
|
+
puts " - cap risk severity to\t -> #{Util::BashColor.orange('medium')} risk"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/sig/const.rbs
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
class CLI
|
4
|
+
def self.exit_on_failure?: -> bool
|
5
|
+
|
6
|
+
def deprecated: (String) -> void
|
7
|
+
|
8
|
+
def outdated: (String) -> void
|
9
|
+
|
10
|
+
def report: (String) -> void
|
11
|
+
|
12
|
+
def risk: -> void
|
13
|
+
|
14
|
+
def version: -> void
|
15
|
+
|
16
|
+
def vulnerable: (String) -> void
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def exit_with_error: (String) -> void
|
21
|
+
|
22
|
+
def exit_with_success: (String) -> void
|
23
|
+
|
24
|
+
def print_total: (Integer) -> void
|
25
|
+
|
26
|
+
def print_vulnerability_info: (String) -> void
|
27
|
+
|
28
|
+
def within_rescue_block: (String) { () -> void } -> void
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
class Dependency
|
4
|
+
@groups: Array[Symbol]
|
5
|
+
@risk: Risk
|
6
|
+
@vulnerabilities: Array[String]
|
7
|
+
|
8
|
+
attr_accessor groups: Array[Symbol]
|
9
|
+
attr_accessor latest_version: String
|
10
|
+
attr_accessor latest_version_date: String
|
11
|
+
attr_reader name: String
|
12
|
+
attr_reader version: String
|
13
|
+
attr_accessor version_date: String
|
14
|
+
attr_accessor vulnerabilities: Array[String]
|
15
|
+
|
16
|
+
def initialize: (String, String) -> void
|
17
|
+
|
18
|
+
def group_list: -> String
|
19
|
+
|
20
|
+
def risk?: -> bool
|
21
|
+
|
22
|
+
def risk: -> Risk
|
23
|
+
|
24
|
+
def risk_explanation: -> String?
|
25
|
+
|
26
|
+
def risk_type: -> String
|
27
|
+
|
28
|
+
def to_csv: (Array[Symbol]) -> String
|
29
|
+
|
30
|
+
def update: (**untyped) -> void
|
31
|
+
|
32
|
+
def vulnerabilities_grouped: -> String
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
class DependencyPrinter
|
4
|
+
BASH_FORMATTING_REGEX: Regexp
|
5
|
+
COLUMN_GAP: Integer
|
6
|
+
CSV_HEADERS: Hash[Symbol, String]
|
7
|
+
FIELDS: Array[Symbol]
|
8
|
+
HEADERS: Hash[Symbol, String]
|
9
|
+
|
10
|
+
@dependencies: Array[Dependency]
|
11
|
+
@options: Hash[Symbol, untyped]
|
12
|
+
|
13
|
+
def initialize: (Array[Dependency], Hash[Symbol, untyped]) -> void
|
14
|
+
|
15
|
+
def print: (?Array[Symbol]) -> void
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def csv: (Array[Symbol], ?exclude_headers: bool) -> void
|
20
|
+
|
21
|
+
def pretty: (Array[Symbol]) -> void
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
class RiskCalculator
|
4
|
+
@dependency: Dependency
|
5
|
+
|
6
|
+
def initialize: (Dependency) -> void
|
7
|
+
|
8
|
+
def find: -> Risk?
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def assess_deprecation_risk: -> Risk
|
13
|
+
|
14
|
+
def assess_version_risk: -> Risk
|
15
|
+
|
16
|
+
def assess_vulnerability_risk: -> Risk
|
17
|
+
|
18
|
+
def production_dependency?: -> bool
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
module Ruby
|
4
|
+
class GemCollection
|
5
|
+
def self.all: -> Array[Dependency]
|
6
|
+
|
7
|
+
def self.deprecated: -> Array[Dependency]
|
8
|
+
|
9
|
+
def self.outdated: (?include_implicit: bool) -> Array[Dependency]
|
10
|
+
|
11
|
+
def self.vulnerable: -> Array[Dependency]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
module Ruby
|
4
|
+
class GemMetaData
|
5
|
+
@dependencies: Array[Dependency]
|
6
|
+
|
7
|
+
@gem_hash: Hash[String, Dependency]
|
8
|
+
|
9
|
+
def initialize: (Array[Dependency]) -> void
|
10
|
+
|
11
|
+
def fetch: -> Array[Dependency]
|
12
|
+
|
13
|
+
def find: -> Array[Dependency]
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def assign_groups: -> Array[Dependency]
|
18
|
+
|
19
|
+
def find_rubygems_metadata: -> Array[Dependency]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
module Util
|
4
|
+
module BashColor
|
5
|
+
def self.blue: (String?) -> String
|
6
|
+
|
7
|
+
def self.green: (String?) -> String
|
8
|
+
|
9
|
+
def self.magenta: (String?) -> String
|
10
|
+
|
11
|
+
def self.orange: (String?) -> String
|
12
|
+
|
13
|
+
def self.red: (String?) -> String
|
14
|
+
|
15
|
+
def self.yellow: (String?) -> String
|
16
|
+
|
17
|
+
def self.cyan: (String?) -> String
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Package
|
2
|
+
module Audit
|
3
|
+
module Util
|
4
|
+
module SummaryPrinter
|
5
|
+
def self.deprecated: -> void
|
6
|
+
|
7
|
+
def self.outdated: -> void
|
8
|
+
|
9
|
+
def self.report: -> void
|
10
|
+
|
11
|
+
def self.risk: -> void
|
12
|
+
|
13
|
+
def self.total: (Integer) -> void
|
14
|
+
|
15
|
+
def self.vulnerable: -> void
|
16
|
+
|
17
|
+
def risk: -> void
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|