package-audit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/exe/package-audit +10 -0
  3. data/lib/package/audit/cli.rb +134 -0
  4. data/lib/package/audit/const.rb +5 -0
  5. data/lib/package/audit/dependency.rb +57 -0
  6. data/lib/package/audit/dependency_printer.rb +128 -0
  7. data/lib/package/audit/enum/environment.rb +15 -0
  8. data/lib/package/audit/enum/risk_explanation.rb +14 -0
  9. data/lib/package/audit/enum/risk_type.rb +12 -0
  10. data/lib/package/audit/enum/vulnerability_type.rb +14 -0
  11. data/lib/package/audit/formatter/base.rb +11 -0
  12. data/lib/package/audit/formatter/risk.rb +28 -0
  13. data/lib/package/audit/formatter/version.rb +33 -0
  14. data/lib/package/audit/formatter/version_date.rb +28 -0
  15. data/lib/package/audit/formatter/vulnerability.rb +37 -0
  16. data/lib/package/audit/risk.rb +27 -0
  17. data/lib/package/audit/risk_calculator.rb +65 -0
  18. data/lib/package/audit/ruby/bundler_specs.rb +28 -0
  19. data/lib/package/audit/ruby/gem_collection.rb +43 -0
  20. data/lib/package/audit/ruby/gem_meta_data.rb +58 -0
  21. data/lib/package/audit/ruby/vulnerability_finder.rb +24 -0
  22. data/lib/package/audit/util/bash_color.rb +35 -0
  23. data/lib/package/audit/util/summary_printer.rb +75 -0
  24. data/lib/package/audit/version.rb +5 -0
  25. data/sig/const.rbs +5 -0
  26. data/sig/package/audit/cli.rbs +31 -0
  27. data/sig/package/audit/dependency.rbs +35 -0
  28. data/sig/package/audit/dependency_printer.rbs +24 -0
  29. data/sig/package/audit/enum/environment.rbs +13 -0
  30. data/sig/package/audit/enum/risk_explanation.rbs +12 -0
  31. data/sig/package/audit/enum/risk_type.rbs +12 -0
  32. data/sig/package/audit/enum/vulnerability_type.rbs +14 -0
  33. data/sig/package/audit/formatter/base.rbs +9 -0
  34. data/sig/package/audit/formatter/risk_printer.rbs +13 -0
  35. data/sig/package/audit/formatter/version_date.rbs +13 -0
  36. data/sig/package/audit/formatter/version_printer.rbs +14 -0
  37. data/sig/package/audit/formatter/vulnerability.rbs +13 -0
  38. data/sig/package/audit/risk.rbs +12 -0
  39. data/sig/package/audit/risk_calculator.rbs +21 -0
  40. data/sig/package/audit/ruby/bundler_specs.rbs +11 -0
  41. data/sig/package/audit/ruby/gem_collection.rbs +15 -0
  42. data/sig/package/audit/ruby/gem_meta_data.rbs +23 -0
  43. data/sig/package/audit/ruby/vulnerability_finder.rbs +9 -0
  44. data/sig/package/audit/util/bash_color.rbs +21 -0
  45. data/sig/package/audit/util/summary_printer.rbs +21 -0
  46. data/sig/package/audit/version.rbs +5 -0
  47. 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
@@ -0,0 +1,5 @@
1
+ module Package
2
+ module Audit
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
data/sig/const.rbs ADDED
@@ -0,0 +1,5 @@
1
+ module Const
2
+ SECONDS_ELAPSED_TO_BE_OUTDATED: Integer
3
+ YEARS_ELAPSED_TO_BE_OUTDATED: Integer
4
+ SECONDS_PER_YEAR: Integer
5
+ end
@@ -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,13 @@
1
+ module Package
2
+ module Audit
3
+ module Enum
4
+ module Environment
5
+ DEFAULT: Symbol
6
+ DEV: Symbol
7
+ PRODUCTION: Symbol
8
+ STAGING: Symbol
9
+ TEST: Symbol
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Package
2
+ module Audit
3
+ module Enum
4
+ module RiskExplanation
5
+ OUTDATED: String
6
+ OUTDATED_BY_MAJOR_VERSION: String
7
+ POTENTIAL_DEPRECATION: String
8
+ VULNERABILITY: String
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Package
2
+ module Audit
3
+ module Enum
4
+ module RiskType
5
+ HIGH: String
6
+ LOW: String
7
+ MEDIUM: String
8
+ NONE: String
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module Package
2
+ module Audit
3
+ module Enum
4
+ module VulnerabilityType
5
+ CRITICAL: String
6
+ HIGH: String
7
+ LOW: String
8
+ MEDIUM: String
9
+ NONE: String
10
+ UNKNOWN: String
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ module Package
2
+ module Audit
3
+ module Formatter
4
+ class Base
5
+ def format: -> String
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Package
2
+ module Audit
3
+ module Formatter
4
+ class Risk
5
+ @risk_type: String
6
+
7
+ def initialize: (String) -> void
8
+
9
+ def format: -> String
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Package
2
+ module Audit
3
+ module Formatter
4
+ class VersionDate
5
+ @date: String
6
+
7
+ def initialize: (String) -> void
8
+
9
+ def format: -> void
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module Package
2
+ module Audit
3
+ module Formatter
4
+ class Version
5
+ @curr: String
6
+ @target: String
7
+
8
+ def initialize: (String, String) -> void
9
+
10
+ def format: -> String
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Package
2
+ module Audit
3
+ module Formatter
4
+ class Vulnerability
5
+ @vulnerabilities: Array[String]
6
+
7
+ def initialize: (Array[String]) -> void
8
+
9
+ def format: -> String
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Package
2
+ module Audit
3
+ class Risk
4
+ attr_reader explanation: String?
5
+ attr_reader type: String
6
+
7
+ def initialize: (String, ?String?)-> void
8
+
9
+ def <=>: (Risk) -> Integer?
10
+ end
11
+ end
12
+ 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,11 @@
1
+ module Package
2
+ module Audit
3
+ module Ruby
4
+ class BundlerSpecs
5
+ def self.all: -> untyped
6
+
7
+ def self.gemfile: -> untyped
8
+ end
9
+ end
10
+ end
11
+ 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,9 @@
1
+ module Package
2
+ module Audit
3
+ module Ruby
4
+ class VulnerabilityFinder
5
+ def self.run: -> Array[Dependency]
6
+ end
7
+ end
8
+ end
9
+ 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
@@ -0,0 +1,5 @@
1
+ module Package
2
+ module Audit
3
+ VERSION: String
4
+ end
5
+ end