package-audit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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