bundler-audit 0.3.1 → 0.7.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 (129) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -1
  3. data/.travis.yml +13 -4
  4. data/ChangeLog.md +53 -0
  5. data/Gemfile +4 -3
  6. data/README.md +44 -18
  7. data/Rakefile +13 -21
  8. data/bin/bundler-audit +3 -0
  9. data/data/ruby-advisory-db.ts +1 -1
  10. data/gemspec.yml +4 -3
  11. data/lib/bundler/audit.rb +1 -1
  12. data/lib/bundler/audit/advisory.rb +71 -7
  13. data/lib/bundler/audit/cli.rb +41 -11
  14. data/lib/bundler/audit/database.rb +29 -7
  15. data/lib/bundler/audit/scanner.rb +126 -10
  16. data/lib/bundler/audit/task.rb +31 -0
  17. data/lib/bundler/audit/version.rb +2 -2
  18. data/spec/advisory_spec.rb +211 -35
  19. data/spec/audit_spec.rb +1 -1
  20. data/spec/bundle/insecure_sources/Gemfile +2 -37
  21. data/spec/bundle/secure/Gemfile +2 -36
  22. data/spec/bundle/unpatched_gems/Gemfile +1 -36
  23. data/spec/cli_spec.rb +126 -0
  24. data/spec/database_spec.rb +51 -25
  25. data/spec/integration_spec.rb +35 -13
  26. data/spec/scanner_spec.rb +11 -10
  27. data/spec/spec_helper.rb +9 -17
  28. metadata +38 -121
  29. data/data/ruby-advisory-db/.gitignore +0 -1
  30. data/data/ruby-advisory-db/.rspec +0 -1
  31. data/data/ruby-advisory-db/CONTRIBUTING.md +0 -6
  32. data/data/ruby-advisory-db/CONTRIBUTORS.md +0 -23
  33. data/data/ruby-advisory-db/Gemfile +0 -3
  34. data/data/ruby-advisory-db/LICENSE.txt +0 -5
  35. data/data/ruby-advisory-db/README.md +0 -82
  36. data/data/ruby-advisory-db/Rakefile +0 -27
  37. data/data/ruby-advisory-db/gems/actionmailer/OSVDB-98629.yml +0 -17
  38. data/data/ruby-advisory-db/gems/actionpack/OSVDB-100524.yml +0 -20
  39. data/data/ruby-advisory-db/gems/actionpack/OSVDB-100525.yml +0 -21
  40. data/data/ruby-advisory-db/gems/actionpack/OSVDB-100526.yml +0 -27
  41. data/data/ruby-advisory-db/gems/actionpack/OSVDB-100527.yml +0 -24
  42. data/data/ruby-advisory-db/gems/actionpack/OSVDB-100528.yml +0 -22
  43. data/data/ruby-advisory-db/gems/actionpack/OSVDB-103439.yml +0 -24
  44. data/data/ruby-advisory-db/gems/actionpack/OSVDB-103440.yml +0 -22
  45. data/data/ruby-advisory-db/gems/actionpack/OSVDB-79727.yml +0 -26
  46. data/data/ruby-advisory-db/gems/actionpack/OSVDB-84243.yml +0 -28
  47. data/data/ruby-advisory-db/gems/actionpack/OSVDB-84513.yml +0 -23
  48. data/data/ruby-advisory-db/gems/actionpack/OSVDB-84515.yml +0 -26
  49. data/data/ruby-advisory-db/gems/actionpack/OSVDB-89026.yml +0 -24
  50. data/data/ruby-advisory-db/gems/actionpack/OSVDB-91452.yml +0 -20
  51. data/data/ruby-advisory-db/gems/actionpack/OSVDB-91454.yml +0 -23
  52. data/data/ruby-advisory-db/gems/activerecord/OSVDB-103438.yml +0 -23
  53. data/data/ruby-advisory-db/gems/activerecord/OSVDB-82403.yml +0 -25
  54. data/data/ruby-advisory-db/gems/activerecord/OSVDB-82610.yml +0 -24
  55. data/data/ruby-advisory-db/gems/activerecord/OSVDB-89025.yml +0 -24
  56. data/data/ruby-advisory-db/gems/activerecord/OSVDB-90072.yml +0 -21
  57. data/data/ruby-advisory-db/gems/activerecord/OSVDB-90073.yml +0 -23
  58. data/data/ruby-advisory-db/gems/activerecord/OSVDB-91453.yml +0 -26
  59. data/data/ruby-advisory-db/gems/activesupport/OSVDB-79726.yml +0 -26
  60. data/data/ruby-advisory-db/gems/activesupport/OSVDB-84516.yml +0 -23
  61. data/data/ruby-advisory-db/gems/activesupport/OSVDB-89594.yml +0 -25
  62. data/data/ruby-advisory-db/gems/activesupport/OSVDB-91451.yml +0 -28
  63. data/data/ruby-advisory-db/gems/arabic-prawn/OSVDB-104365.yml +0 -15
  64. data/data/ruby-advisory-db/gems/cocaine/OSVDB-98835.yml +0 -15
  65. data/data/ruby-advisory-db/gems/command_wrap/OSVDB-91450.yml +0 -10
  66. data/data/ruby-advisory-db/gems/crack/OSVDB-90742.yml +0 -17
  67. data/data/ruby-advisory-db/gems/cremefraiche/OSVDB-93395.yml +0 -11
  68. data/data/ruby-advisory-db/gems/curl/OSVDB-91230.yml +0 -12
  69. data/data/ruby-advisory-db/gems/devise/OSVDB-89642.yml +0 -20
  70. data/data/ruby-advisory-db/gems/dragonfly/OSVDB-90647.yml +0 -19
  71. data/data/ruby-advisory-db/gems/echor/OSVDB-102129.yml +0 -11
  72. data/data/ruby-advisory-db/gems/echor/OSVDB-102130.yml +0 -10
  73. data/data/ruby-advisory-db/gems/enum_column3/OSVDB-94679.yml +0 -9
  74. data/data/ruby-advisory-db/gems/extlib/OSVDB-90740.yml +0 -18
  75. data/data/ruby-advisory-db/gems/fastreader/OSVDB-91232.yml +0 -12
  76. data/data/ruby-advisory-db/gems/fileutils/OSVDB-90715.yml +0 -10
  77. data/data/ruby-advisory-db/gems/fileutils/OSVDB-90716.yml +0 -10
  78. data/data/ruby-advisory-db/gems/fileutils/OSVDB-90717.yml +0 -10
  79. data/data/ruby-advisory-db/gems/flash_tool/OSVDB-90829.yml +0 -9
  80. data/data/ruby-advisory-db/gems/fog-dragonfly/OSVDB-96798.yml +0 -13
  81. data/data/ruby-advisory-db/gems/ftpd/OSVDB-90784.yml +0 -18
  82. data/data/ruby-advisory-db/gems/gitlab-grit/OSVDB-99370.yml +0 -14
  83. data/data/ruby-advisory-db/gems/gtk2/OSVDB-40774.yml +0 -20
  84. data/data/ruby-advisory-db/gems/httparty/OSVDB-90741.yml +0 -14
  85. data/data/ruby-advisory-db/gems/i18n/OSVDB-100528.yml +0 -17
  86. data/data/ruby-advisory-db/gems/json/OSVDB-90074.yml +0 -23
  87. data/data/ruby-advisory-db/gems/karteek-docsplit/OSVDB-92117.yml +0 -10
  88. data/data/ruby-advisory-db/gems/kelredd-pruview/OSVDB-92228.yml +0 -10
  89. data/data/ruby-advisory-db/gems/ldoce/OSVDB-91870.yml +0 -10
  90. data/data/ruby-advisory-db/gems/loofah/OSVDB-90945.yml +0 -21
  91. data/data/ruby-advisory-db/gems/mail/OSVDB-70667.yml +0 -21
  92. data/data/ruby-advisory-db/gems/mail/OSVDB-81631.yml +0 -14
  93. data/data/ruby-advisory-db/gems/mail/OSVDB-81632.yml +0 -16
  94. data/data/ruby-advisory-db/gems/md2pdf/OSVDB-92290.yml +0 -10
  95. data/data/ruby-advisory-db/gems/mini_magick/OSVDB-91231.yml +0 -15
  96. data/data/ruby-advisory-db/gems/multi_xml/OSVDB-89148.yml +0 -16
  97. data/data/ruby-advisory-db/gems/newrelic_rpm/OSVDB-90189.yml +0 -17
  98. data/data/ruby-advisory-db/gems/nokogiri/OSVDB-101179.yml +0 -12
  99. data/data/ruby-advisory-db/gems/nokogiri/OSVDB-101458.yml +0 -15
  100. data/data/ruby-advisory-db/gems/nori/OSVDB-90196.yml +0 -19
  101. data/data/ruby-advisory-db/gems/omniauth-facebook/OSVDB-99693.yml +0 -22
  102. data/data/ruby-advisory-db/gems/omniauth-facebook/OSVDB-99888.yml +0 -17
  103. data/data/ruby-advisory-db/gems/omniauth-oauth2/OSVDB-90264.yml +0 -16
  104. data/data/ruby-advisory-db/gems/paperclip/OSVDB-103151.yml +0 -13
  105. data/data/ruby-advisory-db/gems/paratrooper-newrelic/OSVDB-101839.yml +0 -12
  106. data/data/ruby-advisory-db/gems/paratrooper-pingdom/OSVDB-101847.yml +0 -13
  107. data/data/ruby-advisory-db/gems/pdfkit/OSVDB-90867.yml +0 -11
  108. data/data/ruby-advisory-db/gems/rack-cache/OSVDB-83077.yml +0 -18
  109. data/data/ruby-advisory-db/gems/rack/OSVDB-89939.yml +0 -23
  110. data/data/ruby-advisory-db/gems/rbovirt/OSVDB-104080.yml +0 -20
  111. data/data/ruby-advisory-db/gems/rdoc/OSVDB-90004.yml +0 -27
  112. data/data/ruby-advisory-db/gems/redis-namespace/OSVDB-96425.yml +0 -16
  113. data/data/ruby-advisory-db/gems/rgpg/OSVDB-95948.yml +0 -14
  114. data/data/ruby-advisory-db/gems/ruby_parser/OSVDB-90561.yml +0 -11
  115. data/data/ruby-advisory-db/gems/sfpagent/OSVDB-105971.yml +0 -13
  116. data/data/ruby-advisory-db/gems/sounder/OSVDB-96278.yml +0 -13
  117. data/data/ruby-advisory-db/gems/spree/OSVDB-91216.yml +0 -11
  118. data/data/ruby-advisory-db/gems/spree/OSVDB-91217.yml +0 -11
  119. data/data/ruby-advisory-db/gems/spree/OSVDB-91218.yml +0 -11
  120. data/data/ruby-advisory-db/gems/spree/OSVDB-91219.yml +0 -11
  121. data/data/ruby-advisory-db/gems/sprout/OSVDB-100598.yml +0 -14
  122. data/data/ruby-advisory-db/gems/thumbshooter/OSVDB-91839.yml +0 -10
  123. data/data/ruby-advisory-db/gems/webbynode/OSVDB-100920.yml +0 -11
  124. data/data/ruby-advisory-db/gems/wicked/OSVDB-98270.yml +0 -14
  125. data/data/ruby-advisory-db/gems/will_paginate/OSVDB-101138.yml +0 -15
  126. data/data/ruby-advisory-db/lib/scrape.rb +0 -87
  127. data/data/ruby-advisory-db/spec/advisory_example.rb +0 -165
  128. data/data/ruby-advisory-db/spec/gems_spec.rb +0 -7
  129. data/data/ruby-advisory-db/spec/spec_helper.rb +0 -1
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2013-2014 Hal Brodigan (postmodern.mod3 at gmail.com)
2
+ # Copyright (c) 2013-2020 Hal Brodigan (postmodern.mod3 at gmail.com)
3
3
  #
4
4
  # bundler-audit is free software: you can redistribute it and/or modify
5
5
  # it under the terms of the GNU General Public License as published by
@@ -30,10 +30,14 @@ module Bundler
30
30
  map '--version' => :version
31
31
 
32
32
  desc 'check', 'Checks the Gemfile.lock for insecure dependencies'
33
+ method_option :quiet, :type => :boolean, :aliases => '-q'
33
34
  method_option :verbose, :type => :boolean, :aliases => '-v'
34
35
  method_option :ignore, :type => :array, :aliases => '-i'
36
+ method_option :update, :type => :boolean, :aliases => '-u'
35
37
 
36
38
  def check
39
+ update if options[:update]
40
+
37
41
  scanner = Scanner.new
38
42
  vulnerable = false
39
43
 
@@ -49,19 +53,36 @@ module Bundler
49
53
  end
50
54
 
51
55
  if vulnerable
52
- say "Unpatched versions found!", :red
56
+ say "Vulnerabilities found!", :red
53
57
  exit 1
54
58
  else
55
- say "No unpatched versions found", :green
59
+ say("No vulnerabilities found", :green) unless options.quiet?
56
60
  end
57
61
  end
58
62
 
59
63
  desc 'update', 'Updates the ruby-advisory-db'
64
+ method_option :quiet, :type => :boolean, :aliases => '-q'
65
+
60
66
  def update
61
- say "Updating ruby-advisory-db ..."
67
+ say("Updating ruby-advisory-db ...") unless options.quiet?
62
68
 
63
- Database.update!
64
- puts "ruby-advisory-db: #{Database.new.size} advisories"
69
+ case Database.update!(quiet: options.quiet?)
70
+ when true
71
+ say("Updated ruby-advisory-db", :green) unless options.quiet?
72
+ when false
73
+ say "Failed updating ruby-advisory-db!", :red
74
+ exit 1
75
+ when nil
76
+ unless Bundler.git_present?
77
+ say "Git is not installed!", :red
78
+ exit 1
79
+ end
80
+ say "Skipping update", :yellow
81
+ end
82
+
83
+ unless options.quiet?
84
+ puts("ruby-advisory-db: #{Database.new.size} advisories")
85
+ end
65
86
  end
66
87
 
67
88
  desc 'version', 'Prints the bundler-audit version'
@@ -90,14 +111,23 @@ module Bundler
90
111
  say gem.version
91
112
 
92
113
  say "Advisory: ", :red
93
- say advisory.id
114
+
115
+ if advisory.cve
116
+ say advisory.cve_id
117
+ elsif advisory.osvdb
118
+ say advisory.osvdb_id
119
+ elsif advisory.ghsa
120
+ say advisory.ghsa_id
121
+ end
94
122
 
95
123
  say "Criticality: ", :red
96
124
  case advisory.criticality
97
- when :low then say "Low"
98
- when :medium then say "Medium", :yellow
99
- when :high then say "High", [:red, :bold]
100
- else say "Unknown"
125
+ when :none then say "None"
126
+ when :low then say "Low"
127
+ when :medium then say "Medium", :yellow
128
+ when :high then say "High", [:red, :bold]
129
+ when :critical then say "Critical", [:red, :bold]
130
+ else say "Unknown"
101
131
  end
102
132
 
103
133
  say "URL: ", :red
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2013-2014 Hal Brodigan (postmodern.mod3 at gmail.com)
2
+ # Copyright (c) 2013-2020 Hal Brodigan (postmodern.mod3 at gmail.com)
3
3
  #
4
4
  # bundler-audit is free software: you can redistribute it and/or modify
5
5
  # it under the terms of the GNU General Public License as published by
@@ -68,7 +68,7 @@ module Bundler
68
68
  #
69
69
  def self.path
70
70
  if File.directory?(USER_PATH)
71
- t1 = Dir.chdir(USER_PATH) { Time.parse(`git log --pretty="%cd" -1`) }
71
+ t1 = Dir.chdir(USER_PATH) { Time.parse(`git log --date=iso8601 --pretty="%cd" -1`).utc }
72
72
  t2 = VENDORED_TIMESTAMP
73
73
 
74
74
  if t1 >= t2 then USER_PATH
@@ -82,21 +82,43 @@ module Bundler
82
82
  #
83
83
  # Updates the ruby-advisory-db.
84
84
  #
85
- # @return [Boolean]
85
+ # @param [Hash] options
86
+ # Additional options.
87
+ #
88
+ # @option options [Boolean] :quiet
89
+ # Specify whether `git` should be `--quiet`.
90
+ #
91
+ # @return [Boolean, nil]
86
92
  # Specifies whether the update was successful.
93
+ # A `nil` indicates no update was performed.
94
+ #
95
+ # @raise [ArgumentError]
96
+ # Invalid options were given.
87
97
  #
88
98
  # @note
89
99
  # Requires network access.
90
100
  #
91
101
  # @since 0.3.0
92
102
  #
93
- def self.update!
103
+ def self.update!(options={})
104
+ unless (options.keys - [:quiet]).empty?
105
+ raise(ArgumentError,"Invalid option(s)")
106
+ end
107
+
94
108
  if File.directory?(USER_PATH)
95
- Dir.chdir(USER_PATH) do
96
- system 'git', 'pull', 'origin', 'master'
109
+ if File.directory?(File.join(USER_PATH, ".git"))
110
+ Dir.chdir(USER_PATH) do
111
+ command = %w(git pull --no-rebase)
112
+ command << '--quiet' if options[:quiet]
113
+ command << 'origin' << 'master'
114
+ system *command
115
+ end
97
116
  end
98
117
  else
99
- system 'git', 'clone', URL, USER_PATH
118
+ command = %w(git clone)
119
+ command << '--quiet' if options[:quiet]
120
+ command << URL << USER_PATH
121
+ system *command
100
122
  end
101
123
  end
102
124
 
@@ -2,7 +2,10 @@ require 'bundler'
2
2
  require 'bundler/audit/database'
3
3
  require 'bundler/lockfile_parser'
4
4
 
5
+ require 'ipaddr'
6
+ require 'resolv'
5
7
  require 'set'
8
+ require 'uri'
6
9
 
7
10
  module Bundler
8
11
  module Audit
@@ -33,11 +36,14 @@ module Bundler
33
36
  # @param [String] root
34
37
  # The path to the project root.
35
38
  #
36
- def initialize(root=Dir.pwd)
39
+ # @param [String] gemfile_lock
40
+ # Alternative name for the `Gemfile.lock` file.
41
+ #
42
+ def initialize(root=Dir.pwd,gemfile_lock='Gemfile.lock')
37
43
  @root = File.expand_path(root)
38
44
  @database = Database.new
39
45
  @lockfile = LockfileParser.new(
40
- File.read(File.join(@root,'Gemfile.lock'))
46
+ File.read(File.join(@root,gemfile_lock))
41
47
  )
42
48
  end
43
49
 
@@ -59,39 +65,149 @@ module Bundler
59
65
  # @return [Enumerator]
60
66
  # If no block is given, an Enumerator will be returned.
61
67
  #
62
- def scan(options={})
63
- return enum_for(__method__,options) unless block_given?
68
+ def scan(options={},&block)
69
+ return enum_for(__method__,options) unless block
64
70
 
65
71
  ignore = Set[]
66
72
  ignore += options[:ignore] if options[:ignore]
67
73
 
74
+ scan_sources(options,&block)
75
+ scan_specs(options,&block)
76
+
77
+ return self
78
+ end
79
+
80
+ #
81
+ # Scans the gem sources in the lockfile.
82
+ #
83
+ # @param [Hash] options
84
+ # Additional options.
85
+ #
86
+ # @yield [result]
87
+ # The given block will be passed the results of the scan.
88
+ #
89
+ # @yieldparam [InsecureSource] result
90
+ # A result from the scan.
91
+ #
92
+ # @return [Enumerator]
93
+ # If no block is given, an Enumerator will be returned.
94
+ #
95
+ # @api semipublic
96
+ #
97
+ # @since 0.4.0
98
+ #
99
+ def scan_sources(options={})
100
+ return enum_for(__method__,options) unless block_given?
101
+
68
102
  @lockfile.sources.map do |source|
69
103
  case source
70
104
  when Source::Git
71
105
  case source.uri
72
106
  when /^git:/, /^http:/
73
- yield InsecureSource.new(source.uri)
107
+ unless internal_source?(source.uri)
108
+ yield InsecureSource.new(source.uri)
109
+ end
74
110
  end
75
111
  when Source::Rubygems
76
112
  source.remotes.each do |uri|
77
- if uri.scheme == 'http'
113
+ if (uri.scheme == 'http' && !internal_source?(uri))
78
114
  yield InsecureSource.new(uri.to_s)
79
115
  end
80
116
  end
81
117
  end
82
118
  end
119
+ end
120
+
121
+ #
122
+ # Scans the gem sources in the lockfile.
123
+ #
124
+ # @param [Hash] options
125
+ # Additional options.
126
+ #
127
+ # @option options [Array<String>] :ignore
128
+ # The advisories to ignore.
129
+ #
130
+ # @yield [result]
131
+ # The given block will be passed the results of the scan.
132
+ #
133
+ # @yieldparam [UnpatchedGem] result
134
+ # A result from the scan.
135
+ #
136
+ # @return [Enumerator]
137
+ # If no block is given, an Enumerator will be returned.
138
+ #
139
+ # @api semipublic
140
+ #
141
+ # @since 0.4.0
142
+ #
143
+ def scan_specs(options={})
144
+ return enum_for(__method__,options) unless block_given?
145
+
146
+ ignore = Set[]
147
+ ignore += options[:ignore] if options[:ignore]
83
148
 
84
149
  @lockfile.specs.each do |gem|
85
150
  @database.check_gem(gem) do |advisory|
86
- unless ignore.include?(advisory.id)
87
- yield UnpatchedGem.new(gem,advisory)
88
- end
151
+ is_ignored = ignore.intersect?(advisory.identifiers.to_set)
152
+ next if is_ignored
153
+
154
+ yield UnpatchedGem.new(gem,advisory)
89
155
  end
90
156
  end
157
+ end
91
158
 
92
- return self
159
+ private
160
+
161
+ #
162
+ # Determines whether a source is internal.
163
+ #
164
+ # @param [URI, String] uri
165
+ # The URI.
166
+ #
167
+ # @return [Boolean]
168
+ #
169
+ def internal_source?(uri)
170
+ uri = URI.parse(uri.to_s)
171
+
172
+ internal_host?(uri.host) if uri.host
93
173
  end
94
174
 
175
+ #
176
+ # Determines whether a host is internal.
177
+ #
178
+ # @param [String] host
179
+ # The hostname.
180
+ #
181
+ # @return [Boolean]
182
+ #
183
+ def internal_host?(host)
184
+ Resolv.getaddresses(host).all? { |ip| internal_ip?(ip) }
185
+ rescue URI::Error
186
+ false
187
+ end
188
+
189
+ # List of internal IP address ranges.
190
+ #
191
+ # @see https://tools.ietf.org/html/rfc1918#section-3
192
+ # @see https://tools.ietf.org/html/rfc4193#section-8
193
+ INTERNAL_SUBNETS = %w[
194
+ 10.0.0.0/8
195
+ 172.16.0.0/12
196
+ 192.168.0.0/16
197
+ fc00::/7
198
+ ].map(&IPAddr.method(:new))
199
+
200
+ #
201
+ # Determines whether an IP is internal.
202
+ #
203
+ # @param [String] ip
204
+ # The IPv4/IPv6 address.
205
+ #
206
+ # @return [Boolean]
207
+ #
208
+ def internal_ip?(ip)
209
+ INTERNAL_SUBNETS.any? { |subnet| subnet.include?(ip) }
210
+ end
95
211
  end
96
212
  end
97
213
  end
@@ -0,0 +1,31 @@
1
+ require 'rake/tasklib'
2
+
3
+ module Bundler
4
+ module Audit
5
+ class Task < Rake::TaskLib
6
+ #
7
+ # Initializes the task.
8
+ #
9
+ def initialize
10
+ define
11
+ end
12
+
13
+ protected
14
+
15
+ #
16
+ # Defines the `bundle:audit` task.
17
+ #
18
+ def define
19
+ namespace :bundle do
20
+ desc 'Updates the ruby-advisory-db then runs bundle-audit'
21
+ task :audit do
22
+ require 'bundler/audit/cli'
23
+ %w(update check).each do |command|
24
+ Bundler::Audit::CLI.start [command]
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2013-2014 Hal Brodigan (postmodern.mod3 at gmail.com)
2
+ # Copyright (c) 2013-2020 Hal Brodigan (postmodern.mod3 at gmail.com)
3
3
  #
4
4
  # bundler-audit is free software: you can redistribute it and/or modify
5
5
  # it under the terms of the GNU General Public License as published by
@@ -18,6 +18,6 @@
18
18
  module Bundler
19
19
  module Audit
20
20
  # bundler-audit version
21
- VERSION = '0.3.1'
21
+ VERSION = '0.7.0'
22
22
  end
23
23
  end
@@ -5,7 +5,7 @@ require 'bundler/audit/advisory'
5
5
  describe Bundler::Audit::Advisory do
6
6
  let(:root) { Bundler::Audit::Database::VENDORED_PATH }
7
7
  let(:gem) { 'actionpack' }
8
- let(:id) { 'OSVDB-84243' }
8
+ let(:id) { 'CVE-2012-3424' }
9
9
  let(:path) { File.join(root,'gems',gem,"#{id}.yml") }
10
10
  let(:an_unaffected_version) do
11
11
  Bundler::Audit::Advisory.load(path).unaffected_versions.map { |version_rule|
@@ -24,16 +24,45 @@ describe Bundler::Audit::Advisory do
24
24
  }.flatten.first
25
25
  end
26
26
 
27
+ subject { described_class.load(path) }
28
+
27
29
  describe "load" do
28
30
  let(:data) { YAML.load_file(path) }
29
31
 
30
- subject { described_class.load(path) }
32
+ describe '#id' do
33
+ subject { super().id }
34
+ it { is_expected.to eq(id) }
35
+ end
36
+
37
+ describe '#url' do
38
+ subject { super().url }
39
+ it { is_expected.to eq(data['url']) }
40
+ end
41
+
42
+ describe '#title' do
43
+ subject { super().title }
44
+ it { is_expected.to eq(data['title']) }
45
+ end
46
+
47
+ describe '#date' do
48
+ subject { super().date }
49
+ it { is_expected.to eq(data['date']) }
50
+ end
51
+
52
+ describe '#cvss_v2' do
53
+ subject { super().cvss_v2 }
54
+ it { is_expected.to eq(data['cvss_v2']) }
55
+ end
31
56
 
32
- its(:id) { should == id }
33
- its(:url) { should == data['url'] }
34
- its(:title) { should == data['title'] }
35
- its(:cvss_v2) { should == data['cvss_v2'] }
36
- its(:description) { should == data['description'] }
57
+ describe '#cvss_v3' do
58
+ subject { super().cvss_v3 }
59
+ it { is_expected.to eq(data['cvss_v3']) }
60
+ end
61
+
62
+ describe '#description' do
63
+ subject { super().description }
64
+ it { is_expected.to eq(data['description']) }
65
+ end
37
66
 
38
67
  context "YAML data not representing a hash" do
39
68
  it "should raise an exception" do
@@ -48,45 +77,196 @@ describe Bundler::Audit::Advisory do
48
77
  subject { described_class.load(path).patched_versions }
49
78
 
50
79
  it "should all be Gem::Requirement objects" do
51
- subject.all? { |version|
52
- version.should be_kind_of(Gem::Requirement)
53
- }.should be_true
80
+ expect(subject.all? { |version|
81
+ expect(version).to be_kind_of(Gem::Requirement)
82
+ }).to be_truthy
54
83
  end
55
84
 
56
85
  it "should parse the versions" do
57
- subject.map(&:to_s).should == data['patched_versions']
86
+ expect(subject.map(&:to_s)).to eq(data['patched_versions'])
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "#cve_id" do
92
+ let(:cve) { "2015-1234" }
93
+
94
+ subject do
95
+ described_class.new.tap do |advisory|
96
+ advisory.cve = cve
58
97
  end
59
98
  end
99
+
100
+ it "should prepend CVE- to the CVE id" do
101
+ expect(subject.cve_id).to be == "CVE-#{cve}"
102
+ end
103
+
104
+ context "when cve is nil" do
105
+ subject { described_class.new }
106
+
107
+ it { expect(subject.cve_id).to be_nil }
108
+ end
109
+ end
110
+
111
+ describe "#osvdb_id" do
112
+ let(:osvdb) { "123456" }
113
+
114
+ subject do
115
+ described_class.new.tap do |advisory|
116
+ advisory.osvdb = osvdb
117
+ end
118
+ end
119
+
120
+ it "should prepend OSVDB- to the OSVDB id" do
121
+ expect(subject.osvdb_id).to be == "OSVDB-#{osvdb}"
122
+ end
123
+
124
+ context "when cve is nil" do
125
+ subject { described_class.new }
126
+
127
+ it { expect(subject.osvdb_id).to be_nil }
128
+ end
129
+ end
130
+
131
+ describe "#ghsa_id" do
132
+ let(:ghsa) { "xfhh-rx56-rxcr" }
133
+
134
+ subject do
135
+ described_class.new.tap do |advisory|
136
+ advisory.ghsa = ghsa
137
+ end
138
+ end
139
+
140
+ it "should prepend GHSA- to the GHSA id" do
141
+ expect(subject.ghsa_id).to be == "GHSA-#{ghsa}"
142
+ end
143
+
144
+ context "when ghsa is nil" do
145
+ subject { described_class.new }
146
+
147
+ it { expect(subject.ghsa_id).to be_nil }
148
+ end
149
+ end
150
+
151
+ describe "#identifiers" do
152
+ it "should include all identifiers if defined" do
153
+ advisory = described_class.new.tap do |advisory|
154
+ advisory.cve = "2018-1234"
155
+ advisory.osvdb = "2019-2345"
156
+ advisory.ghsa = "2020-3456"
157
+ end
158
+
159
+ expect(advisory.identifiers).to eq([
160
+ "CVE-2018-1234",
161
+ "OSVDB-2019-2345",
162
+ "GHSA-2020-3456"
163
+ ])
164
+ end
165
+
166
+ it "should exclude nil identifiers" do
167
+ advisory = described_class.new
168
+ expect(advisory.identifiers).to eq([])
169
+
170
+ advisory = described_class.new.tap do |advisory|
171
+ advisory.cve = "2018-1234"
172
+ end
173
+ expect(advisory.identifiers).to eq(["CVE-2018-1234"])
174
+
175
+ advisory = described_class.new.tap do |advisory|
176
+ advisory.ghsa = "2020-3456"
177
+ end
178
+ expect(advisory.identifiers).to eq(["GHSA-2020-3456"])
179
+ end
60
180
  end
61
181
 
62
182
  describe "#criticality" do
63
- context "when cvss_v2 is between 0.0 and 3.3" do
64
- before { subject.stub(:cvss_v2).and_return(3.3) }
183
+ context "when cvss_v2 is between 0.0 and 3.9" do
184
+ subject do
185
+ described_class.new.tap do |advisory|
186
+ advisory.cvss_v2 = 3.9
187
+ end
188
+ end
189
+
190
+ it { expect(subject.criticality).to eq(:low) }
191
+ end
192
+
193
+ context "when cvss_v2 is between 4.0 and 6.9" do
194
+ subject do
195
+ described_class.new.tap do |advisory|
196
+ advisory.cvss_v2 = 6.9
197
+ end
198
+ end
199
+
200
+ it { expect(subject.criticality).to eq(:medium) }
201
+ end
202
+
203
+ context "when cvss_v2 is between 7.0 and 10.0" do
204
+ subject do
205
+ described_class.new.tap do |advisory|
206
+ advisory.cvss_v2 = 10.0
207
+ end
208
+ end
209
+
210
+ it { expect(subject.criticality).to eq(:high) }
211
+ end
212
+
213
+ context "when cvss_v3 is 0.0" do
214
+ subject do
215
+ described_class.new.tap do |advisory|
216
+ advisory.cvss_v3 = 0.0
217
+ end
218
+ end
219
+
220
+ it { expect(subject.criticality).to eq(:none) }
221
+ end
222
+
223
+ context "when cvss_v3 is between 0.1 and 3.9" do
224
+ subject do
225
+ described_class.new.tap do |advisory|
226
+ advisory.cvss_v3 = 3.9
227
+ end
228
+ end
229
+
230
+ it { expect(subject.criticality).to eq(:low) }
231
+ end
65
232
 
66
- its(:criticality) { should == :low }
233
+ context "when cvss_v3 is between 4.0 and 6.9" do
234
+ subject do
235
+ described_class.new.tap do |advisory|
236
+ advisory.cvss_v3 = 6.9
237
+ end
238
+ end
239
+
240
+ it { expect(subject.criticality).to eq(:medium) }
67
241
  end
68
242
 
69
- context "when cvss_v2 is between 3.3 and 6.6" do
70
- before { subject.stub(:cvss_v2).and_return(6.6) }
243
+ context "when cvss_v3 is between 7.0 and 8.9" do
244
+ subject do
245
+ described_class.new.tap do |advisory|
246
+ advisory.cvss_v3 = 8.9
247
+ end
248
+ end
71
249
 
72
- its(:criticality) { should == :medium }
250
+ it { expect(subject.criticality).to eq(:high) }
73
251
  end
74
252
 
75
- context "when cvss_v2 is between 6.6 and 10.0" do
76
- before { subject.stub(:cvss_v2).and_return(10.0) }
253
+ context "when cvss_v3 is between 9.0 and 10.0" do
254
+ subject do
255
+ described_class.new.tap do |advisory|
256
+ advisory.cvss_v3 = 10.0
257
+ end
258
+ end
77
259
 
78
- its(:criticality) { should == :high }
260
+ it { expect(subject.criticality).to eq(:critical) }
79
261
  end
80
262
  end
81
263
 
82
264
  describe "#unaffected?" do
83
- subject { described_class.load(path) }
84
-
85
265
  context "when passed a version that matches one unaffected version" do
86
266
  let(:version) { Gem::Version.new(an_unaffected_version) }
87
267
 
88
268
  it "should return true" do
89
- subject.unaffected?(version).should be_true
269
+ expect(subject.unaffected?(version)).to be_truthy
90
270
  end
91
271
  end
92
272
 
@@ -94,19 +274,17 @@ describe Bundler::Audit::Advisory do
94
274
  let(:version) { Gem::Version.new('3.0.9') }
95
275
 
96
276
  it "should return false" do
97
- subject.unaffected?(version).should be_false
277
+ expect(subject.unaffected?(version)).to be_falsey
98
278
  end
99
279
  end
100
280
  end
101
281
 
102
282
  describe "#patched?" do
103
- subject { described_class.load(path) }
104
-
105
283
  context "when passed a version that matches one patched version" do
106
284
  let(:version) { Gem::Version.new('3.1.11') }
107
285
 
108
286
  it "should return true" do
109
- subject.patched?(version).should be_true
287
+ expect(subject.patched?(version)).to be_truthy
110
288
  end
111
289
  end
112
290
 
@@ -114,19 +292,17 @@ describe Bundler::Audit::Advisory do
114
292
  let(:version) { Gem::Version.new('2.9.0') }
115
293
 
116
294
  it "should return false" do
117
- subject.patched?(version).should be_false
295
+ expect(subject.patched?(version)).to be_falsey
118
296
  end
119
297
  end
120
298
  end
121
299
 
122
300
  describe "#vulnerable?" do
123
- subject { described_class.load(path) }
124
-
125
301
  context "when passed a version that matches one patched version" do
126
302
  let(:version) { Gem::Version.new('3.1.11') }
127
303
 
128
304
  it "should return false" do
129
- subject.vulnerable?(version).should be_false
305
+ expect(subject.vulnerable?(version)).to be_falsey
130
306
  end
131
307
  end
132
308
 
@@ -134,17 +310,17 @@ describe Bundler::Audit::Advisory do
134
310
  let(:version) { Gem::Version.new('2.9.0') }
135
311
 
136
312
  it "should return true" do
137
- subject.vulnerable?(version).should be_true
313
+ expect(subject.vulnerable?(version)).to be_truthy
138
314
  end
139
315
 
140
316
  context "when unaffected_versions is not empty" do
141
317
  subject { described_class.load(path) }
142
-
318
+
143
319
  context "when passed a version that matches one unaffected version" do
144
320
  let(:version) { Gem::Version.new(an_unaffected_version) }
145
321
 
146
322
  it "should return false" do
147
- subject.vulnerable?(version).should be_false
323
+ expect(subject.vulnerable?(version)).to be_falsey
148
324
  end
149
325
  end
150
326
 
@@ -152,7 +328,7 @@ describe Bundler::Audit::Advisory do
152
328
  let(:version) { Gem::Version.new('1.2.3') }
153
329
 
154
330
  it "should return true" do
155
- subject.vulnerable?(version).should be_true
331
+ expect(subject.vulnerable?(version)).to be_truthy
156
332
  end
157
333
  end
158
334
  end