bundler-audit 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -1
  3. data/ChangeLog.md +10 -0
  4. data/Gemfile +1 -1
  5. data/README.md +50 -34
  6. data/Rakefile +15 -2
  7. data/data/ruby-advisory-db/CONTRIBUTING.md +6 -0
  8. data/data/ruby-advisory-db/CONTRIBUTORS.md +13 -0
  9. data/data/ruby-advisory-db/Gemfile +3 -0
  10. data/data/ruby-advisory-db/LICENSE.txt +5 -0
  11. data/data/ruby-advisory-db/README.md +29 -7
  12. data/data/ruby-advisory-db/Rakefile +27 -0
  13. data/data/ruby-advisory-db/gems/actionpack/{2012-1099.yml → OSVDB-79727.yml} +3 -0
  14. data/data/ruby-advisory-db/gems/actionpack/{2012-3424.yml → OSVDB-84243.yml} +7 -0
  15. data/data/ruby-advisory-db/gems/actionpack/{2012-3465.yml → OSVDB-84513.yml} +3 -0
  16. data/data/ruby-advisory-db/gems/actionpack/{2012-3463.yml → OSVDB-84515.yml} +6 -0
  17. data/data/ruby-advisory-db/gems/actionpack/{2013-0156.yml → OSVDB-89026.yml} +3 -0
  18. data/data/ruby-advisory-db/gems/actionpack/OSVDB-91452.yml +20 -0
  19. data/data/ruby-advisory-db/gems/actionpack/OSVDB-91454.yml +23 -0
  20. data/data/ruby-advisory-db/gems/activerecord/{2012-2661.yml → OSVDB-82403.yml} +6 -0
  21. data/data/ruby-advisory-db/gems/activerecord/{2012-2660.yml → OSVDB-82610.yml} +3 -0
  22. data/data/ruby-advisory-db/gems/activerecord/{2013-0155.yml → OSVDB-89025.yml} +3 -0
  23. data/data/ruby-advisory-db/gems/activerecord/{2013-0276.yml → OSVDB-90072.yml} +3 -0
  24. data/data/ruby-advisory-db/gems/activerecord/{2013-0277.yml → OSVDB-90073.yml} +3 -0
  25. data/data/ruby-advisory-db/gems/activerecord/OSVDB-91453.yml +26 -0
  26. data/data/ruby-advisory-db/gems/activesupport/{2012-1098.yml → OSVDB-79726.yml} +6 -0
  27. data/data/ruby-advisory-db/gems/activesupport/{2012-3464.yml → OSVDB-84516.yml} +3 -0
  28. data/data/ruby-advisory-db/gems/activesupport/{2013-0333.yml → OSVDB-89594.yml} +3 -0
  29. data/data/ruby-advisory-db/gems/activesupport/OSVDB-91451.yml +28 -0
  30. data/data/ruby-advisory-db/gems/command_wrap/OSVDB-91450.yml +10 -0
  31. data/data/ruby-advisory-db/gems/crack/OSVDB-90742.yml +17 -0
  32. data/data/ruby-advisory-db/gems/cremefraiche/OSVDB-93395.yml +11 -0
  33. data/data/ruby-advisory-db/gems/curl/OSVDB-91230.yml +12 -0
  34. data/data/ruby-advisory-db/gems/devise/{2013-0233.yml → OSVDB-89642.yml} +2 -0
  35. data/data/ruby-advisory-db/gems/dragonfly/OSVDB-90647.yml +19 -0
  36. data/data/ruby-advisory-db/gems/enum_column3/OSVDB-94679.yml +9 -0
  37. data/data/ruby-advisory-db/gems/extlib/OSVDB-90740.yml +18 -0
  38. data/data/ruby-advisory-db/gems/fastreader/OSVDB-91232.yml +12 -0
  39. data/data/ruby-advisory-db/gems/fileutils/OSVDB-90715.yml +10 -0
  40. data/data/ruby-advisory-db/gems/fileutils/OSVDB-90716.yml +10 -0
  41. data/data/ruby-advisory-db/gems/fileutils/OSVDB-90717.yml +10 -0
  42. data/data/ruby-advisory-db/gems/flash_tool/OSVDB-90829.yml +9 -0
  43. data/data/ruby-advisory-db/gems/ftpd/OSVDB-90784.yml +18 -0
  44. data/data/ruby-advisory-db/gems/gtk2/{2007-6183.yml → OSVDB-40774.yml} +2 -0
  45. data/data/ruby-advisory-db/gems/httparty/OSVDB-90741.yml +19 -0
  46. data/data/ruby-advisory-db/gems/json/{2013-0269.yml → OSVDB-90074.yml} +4 -2
  47. data/data/ruby-advisory-db/gems/karteek-docsplit/OSVDB-92117.yml +10 -0
  48. data/data/ruby-advisory-db/gems/kelredd-pruview/OSVDB-92228.yml +10 -0
  49. data/data/ruby-advisory-db/gems/ldoce/OSVDB-91870.yml +10 -0
  50. data/data/ruby-advisory-db/gems/loofah/OSVDB-90945.yml +21 -0
  51. data/data/ruby-advisory-db/gems/mail/{2011-0739.yml → OSVDB-70667.yml} +2 -0
  52. data/data/ruby-advisory-db/gems/mail/{2012-2139.yml → OSVDB-81631.yml} +3 -0
  53. data/data/ruby-advisory-db/gems/mail/{2012-2140.yml → OSVDB-81632.yml} +7 -2
  54. data/data/ruby-advisory-db/gems/md2pdf/OSVDB-92290.yml +10 -0
  55. data/data/ruby-advisory-db/gems/mini_magick/OSVDB-91231.yml +15 -0
  56. data/data/ruby-advisory-db/gems/multi_xml/{2013-0175.yml → OSVDB-89148.yml} +2 -0
  57. data/data/ruby-advisory-db/gems/newrelic_rpm/{2013-0284.yml → OSVDB-90189.yml} +4 -2
  58. data/data/ruby-advisory-db/gems/nori/{2013-0285.yml → OSVDB-90196.yml} +4 -2
  59. data/data/ruby-advisory-db/gems/omniauth-oauth2/{2012-6134.yml → OSVDB-90264.yml} +4 -2
  60. data/data/ruby-advisory-db/gems/pdfkit/OSVDB-90867.yml +11 -0
  61. data/data/ruby-advisory-db/gems/rack-cache/{2012-267.yml → OSVDB-83077.yml} +3 -1
  62. data/data/ruby-advisory-db/gems/rack/{2013-0263.yml → OSVDB-89939.yml} +2 -0
  63. data/data/ruby-advisory-db/gems/rdoc/{2013-0256.yml → OSVDB-90004.yml} +2 -0
  64. data/data/ruby-advisory-db/gems/rgpg/OSVDB-95948.yml +13 -0
  65. data/data/ruby-advisory-db/gems/ruby_parser/OSVDB-90561.yml +11 -0
  66. data/data/ruby-advisory-db/gems/spree/OSVDB-91216.yml +10 -0
  67. data/data/ruby-advisory-db/gems/spree/OSVDB-91217.yml +10 -0
  68. data/data/ruby-advisory-db/gems/spree/OSVDB-91218.yml +10 -0
  69. data/data/ruby-advisory-db/gems/spree/OSVDB-91219.yml +10 -0
  70. data/data/ruby-advisory-db/gems/thumbshooter/OSVDB-91839.yml +10 -0
  71. data/data/ruby-advisory-db/lib/scrape.rb +87 -0
  72. data/data/ruby-advisory-db/spec/advisory_example.rb +97 -12
  73. data/gemspec.yml +3 -1
  74. data/lib/bundler/audit/advisory.rb +46 -16
  75. data/lib/bundler/audit/cli.rb +23 -19
  76. data/lib/bundler/audit/scanner.rb +97 -0
  77. data/lib/bundler/audit/version.rb +1 -1
  78. data/spec/advisory_spec.rb +66 -6
  79. data/spec/bundle/insecure_sources/Gemfile +39 -0
  80. data/spec/bundle/secure/Gemfile +1 -1
  81. data/spec/bundle/{vuln → unpatched_gems}/Gemfile +0 -0
  82. data/spec/integration_spec.rb +75 -6
  83. data/spec/scanner_spec.rb +74 -0
  84. metadata +77 -40
@@ -4,7 +4,9 @@ description: bundler-audit provides patch-level verification for Bundled apps.
4
4
  license: GPLv3
5
5
  authors: Postmodern
6
6
  email: postmodern.mod3@gmail.com
7
- homepage: https://github.com/postmodern/bundler-audit#readme
7
+ homepage: https://github.com/rubysec/bundler-audit#readme
8
+
9
+ required_rubygems_version: ">= 1.8.0"
8
10
 
9
11
  dependencies:
10
12
  bundler: ~> 1.2
@@ -20,11 +20,12 @@ require 'yaml'
20
20
  module Bundler
21
21
  module Audit
22
22
  class Advisory < Struct.new(:path,
23
- :cve,
23
+ :id,
24
24
  :url,
25
25
  :title,
26
26
  :description,
27
27
  :cvss_v2,
28
+ :unaffected_versions,
28
29
  :patched_versions)
29
30
 
30
31
  #
@@ -38,23 +39,28 @@ module Bundler
38
39
  # @api semipublic
39
40
  #
40
41
  def self.load(path)
41
- cve = File.basename(path).chomp('.yml')
42
+ id = File.basename(path).chomp('.yml')
42
43
  data = YAML.load_file(path)
43
44
 
44
45
  unless data.kind_of?(Hash)
45
46
  raise("advisory data in #{path.dump} was not a Hash")
46
47
  end
47
48
 
49
+ parse_versions = lambda { |versions|
50
+ Array(versions).map do |version|
51
+ Gem::Requirement.new(*version.split(', '))
52
+ end
53
+ }
54
+
48
55
  return new(
49
56
  path,
50
- cve,
57
+ id,
51
58
  data['url'],
52
59
  data['title'],
53
60
  data['description'],
54
61
  data['cvss_v2'],
55
- Array(data['patched_versions']).map { |version|
56
- Gem::Requirement.new(*version.split(', '))
57
- }
62
+ parse_versions[data['unaffected_versions']],
63
+ parse_versions[data['patched_versions']]
58
64
  )
59
65
  end
60
66
 
@@ -73,30 +79,54 @@ module Bundler
73
79
  end
74
80
 
75
81
  #
76
- # Checks whether the version is vulnerable to the advisory.
82
+ # Checks whether the version is not affected by the advisory.
77
83
  #
78
84
  # @param [Gem::Version] version
79
- # The version to compare against {#patched_versions}.
85
+ # The version to compare against {#unaffected_version}.
80
86
  #
81
87
  # @return [Boolean]
82
- # Specifies whether the version is vulnerable to the advisory or not.
88
+ # Specifies whether the version is not affected by the advisory.
83
89
  #
84
- def vulnerable?(version)
85
- !patched_versions.any? do |patched_version|
90
+ # @since 0.2.0
91
+ #
92
+ def unaffected?(version)
93
+ unaffected_versions.any? do |unaffected_version|
94
+ unaffected_version === version
95
+ end
96
+ end
97
+
98
+ #
99
+ # Checks whether the version is patched against the advisory.
100
+ #
101
+ # @param [Gem::Version] version
102
+ # The version to compare against {#patched_version}.
103
+ #
104
+ # @return [Boolean]
105
+ # Specifies whether the version is patched against the advisory.
106
+ #
107
+ # @since 0.2.0
108
+ #
109
+ def patched?(version)
110
+ patched_versions.any? do |patched_version|
86
111
  patched_version === version
87
112
  end
88
113
  end
89
114
 
90
115
  #
91
- # Converts the advisory to a String.
116
+ # Checks whether the version is vulnerable to the advisory.
92
117
  #
93
- # @return [String]
94
- # The CVE identifier.
118
+ # @param [Gem::Version] version
119
+ # The version to compare against {#patched_versions}.
120
+ #
121
+ # @return [Boolean]
122
+ # Specifies whether the version is vulnerable to the advisory or not.
95
123
  #
96
- def to_s
97
- "CVE-#{cve}"
124
+ def vulnerable?(version)
125
+ !patched?(version) && !unaffected?(version)
98
126
  end
99
127
 
128
+ alias to_s id
129
+
100
130
  end
101
131
  end
102
132
  end
@@ -15,11 +15,11 @@
15
15
  # along with bundler-audit. If not, see <http://www.gnu.org/licenses/>.
16
16
  #
17
17
 
18
- require 'bundler/audit/database'
18
+ require 'bundler/audit/scanner'
19
19
  require 'bundler/audit/version'
20
20
 
21
- require 'bundler/vendored_thor'
22
21
  require 'bundler'
22
+ require 'bundler/vendored_thor'
23
23
 
24
24
  module Bundler
25
25
  module Audit
@@ -30,16 +30,20 @@ module Bundler
30
30
 
31
31
  desc 'check', 'Checks the Gemfile.lock for insecure dependencies'
32
32
  method_option :verbose, :type => :boolean, :aliases => '-v'
33
+ method_option :ignore, :type => :array, :aliases => '-i'
33
34
 
34
35
  def check
35
- database = Database.new
36
- vulnerable = false
37
- lock_file = load_gemfile_lock('Gemfile.lock')
38
-
39
- lock_file.specs.each do |gem|
40
- database.check_gem(gem) do |advisory|
41
- vulnerable = true
42
- print_advisory gem, advisory
36
+ scanner = Scanner.new
37
+ vulnerable = false
38
+
39
+ scanner.scan(:ignore => options.ignore) do |result|
40
+ vulnerable = true
41
+
42
+ case result
43
+ when Scanner::InsecureSource
44
+ print_warning "Insecure Source URI found: #{result.source}"
45
+ when Scanner::UnpatchedGem
46
+ print_advisory result.gem, result.advisory
43
47
  end
44
48
  end
45
49
 
@@ -60,8 +64,13 @@ module Bundler
60
64
 
61
65
  protected
62
66
 
63
- def load_gemfile_lock(path)
64
- Bundler::LockfileParser.new(File.read(path))
67
+ def say(string="", color=nil)
68
+ color = nil unless $stdout.tty?
69
+ super(string, color)
70
+ end
71
+
72
+ def print_warning(message)
73
+ say message, :yellow
65
74
  end
66
75
 
67
76
  def print_advisory(gem, advisory)
@@ -71,8 +80,8 @@ module Bundler
71
80
  say "Version: ", :red
72
81
  say gem.version
73
82
 
74
- say "CVE: ", :red
75
- say advisory.cve
83
+ say "Advisory: ", :red
84
+ say advisory.id
76
85
 
77
86
  say "Criticality: ", :red
78
87
  case advisory.criticality
@@ -108,11 +117,6 @@ module Bundler
108
117
  say
109
118
  end
110
119
 
111
- def say(string="", color=nil)
112
- color = nil unless $stdout.tty?
113
- super(string, color)
114
- end
115
-
116
120
  end
117
121
  end
118
122
  end
@@ -0,0 +1,97 @@
1
+ require 'bundler'
2
+ require 'bundler/audit/database'
3
+ require 'bundler/lockfile_parser'
4
+
5
+ require 'set'
6
+
7
+ module Bundler
8
+ module Audit
9
+ class Scanner
10
+
11
+ # Represents a plain-text source
12
+ InsecureSource = Struct.new(:source)
13
+
14
+ # Represents a gem that is covered by an Advisory
15
+ UnpatchedGem = Struct.new(:gem, :advisory)
16
+
17
+ # The advisory database
18
+ #
19
+ # @return [Database]
20
+ attr_reader :database
21
+
22
+ # Project root directory
23
+ attr_reader :root
24
+
25
+ # The parsed `Gemfile.lock` from the project
26
+ #
27
+ # @return [Bundler::LockfileParser]
28
+ attr_reader :lockfile
29
+
30
+ #
31
+ # Initializes a scanner.
32
+ #
33
+ # @param [String] root
34
+ # The path to the project root.
35
+ #
36
+ def initialize(root=Dir.pwd)
37
+ @root = File.expand_path(root)
38
+ @database = Database.new
39
+ @lockfile = LockfileParser.new(
40
+ File.read(File.join(@root,'Gemfile.lock'))
41
+ )
42
+ end
43
+
44
+ #
45
+ # Scans the project for issues.
46
+ #
47
+ # @param [Hash] options
48
+ # Additional options.
49
+ #
50
+ # @option options [Array<String>] :ignore
51
+ # The advisories to ignore.
52
+ #
53
+ # @yield [result]
54
+ # The given block will be passed the results of the scan.
55
+ #
56
+ # @yieldparam [InsecureSource, UnpatchedGem] result
57
+ # A result from the scan.
58
+ #
59
+ # @return [Enumerator]
60
+ # If no block is given, an Enumerator will be returned.
61
+ #
62
+ def scan(options={})
63
+ return enum_for(__method__,options) unless block_given?
64
+
65
+ ignore = Set[]
66
+ ignore += options[:ignore] if options[:ignore]
67
+
68
+ @lockfile.sources.map do |source|
69
+ case source
70
+ when Source::Git
71
+ case source.uri
72
+ when /^git:/, /^http:/
73
+ yield InsecureSource.new(source.uri)
74
+ end
75
+ when Source::Rubygems
76
+ source.remotes.each do |uri|
77
+ if uri.scheme == 'http'
78
+ yield InsecureSource.new(uri.to_s)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ @lockfile.specs.each do |gem|
85
+ @database.check_gem(gem) do |advisory|
86
+ unless ignore.include?(advisory.id)
87
+ yield UnpatchedGem.new(gem,advisory)
88
+ end
89
+ end
90
+ end
91
+
92
+ return self
93
+ end
94
+
95
+ end
96
+ end
97
+ end
@@ -18,6 +18,6 @@
18
18
  module Bundler
19
19
  module Audit
20
20
  # bundler-audit version
21
- VERSION = "0.1.2"
21
+ VERSION = '0.2.0'
22
22
  end
23
23
  end
@@ -5,15 +5,15 @@ require 'bundler/audit/advisory'
5
5
  describe Bundler::Audit::Advisory do
6
6
  let(:root) { Bundler::Audit::Database::PATH }
7
7
  let(:gem) { 'actionpack' }
8
- let(:cve) { '2013-0156' }
9
- let(:path) { File.join(root,gem,"#{cve}.yml") }
8
+ let(:id) { 'OSVDB-84243' }
9
+ let(:path) { File.join(root,gem,"#{id}.yml") }
10
10
 
11
11
  describe "load" do
12
12
  let(:data) { YAML.load_file(path) }
13
13
 
14
14
  subject { described_class.load(path) }
15
15
 
16
- its(:cve) { should == cve }
16
+ its(:id) { should == id }
17
17
  its(:url) { should == data['url'] }
18
18
  its(:title) { should == data['title'] }
19
19
  its(:cvss_v2) { should == data['cvss_v2'] }
@@ -54,10 +54,50 @@ describe Bundler::Audit::Advisory do
54
54
  end
55
55
  end
56
56
 
57
+ describe "#unaffected?" do
58
+ subject { described_class.load(path) }
59
+
60
+ context "when passed a version that matches one unaffected version" do
61
+ let(:version) { Gem::Version.new('2.3.10') }
62
+
63
+ it "should return true" do
64
+ subject.unaffected?(version).should be_true
65
+ end
66
+ end
67
+
68
+ context "when passed a version that matches no unaffected version" do
69
+ let(:version) { Gem::Version.new('3.0.9') }
70
+
71
+ it "should return false" do
72
+ subject.unaffected?(version).should be_false
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "#patched?" do
78
+ subject { described_class.load(path) }
79
+
80
+ context "when passed a version that matches one patched version" do
81
+ let(:version) { Gem::Version.new('3.1.11') }
82
+
83
+ it "should return true" do
84
+ subject.patched?(version).should be_true
85
+ end
86
+ end
87
+
88
+ context "when passed a version that matches no patched version" do
89
+ let(:version) { Gem::Version.new('2.9.0') }
90
+
91
+ it "should return false" do
92
+ subject.patched?(version).should be_false
93
+ end
94
+ end
95
+ end
96
+
57
97
  describe "#vulnerable?" do
58
98
  subject { described_class.load(path) }
59
99
 
60
- context "when passed a version that matches one patched_version" do
100
+ context "when passed a version that matches one patched version" do
61
101
  let(:version) { Gem::Version.new('3.1.11') }
62
102
 
63
103
  it "should return false" do
@@ -65,12 +105,32 @@ describe Bundler::Audit::Advisory do
65
105
  end
66
106
  end
67
107
 
68
- context "when passed a version that matches no patched_version" do
69
- let(:version) { Gem::Version.new('3.1.9') }
108
+ context "when passed a version that matches no patched version" do
109
+ let(:version) { Gem::Version.new('2.9.0') }
70
110
 
71
111
  it "should return true" do
72
112
  subject.vulnerable?(version).should be_true
73
113
  end
114
+
115
+ context "when unaffected_versions is not empty" do
116
+ subject { described_class.load(path) }
117
+
118
+ context "when passed a version that matches one unaffected version" do
119
+ let(:version) { Gem::Version.new('2.3.12') }
120
+
121
+ it "should return false" do
122
+ subject.vulnerable?(version).should be_false
123
+ end
124
+ end
125
+
126
+ context "when passed a version that matches no unaffected version" do
127
+ let(:version) { Gem::Version.new('1.2.3') }
128
+
129
+ it "should return true" do
130
+ subject.vulnerable?(version).should be_true
131
+ end
132
+ end
133
+ end
74
134
  end
75
135
  end
76
136
  end
@@ -0,0 +1,39 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'rails', '3.2.12'
4
+
5
+ # Bundle edge Rails instead:
6
+ # gem 'rails', :git => 'git://github.com/rails/rails.git'
7
+
8
+ gem 'sqlite3'
9
+
10
+
11
+ # Gems used only for assets and not required
12
+ # in production environments by default.
13
+ group :assets do
14
+ # gem 'sass-rails', '~> 3.2.3'
15
+ # gem 'coffee-rails', '~> 3.2.1'
16
+
17
+ # See https://github.com/sstephenson/execjs#readme for more supported runtimes
18
+ # gem 'therubyracer', :platforms => :ruby
19
+
20
+ # gem 'uglifier', '>= 1.0.3'
21
+ end
22
+
23
+ gem 'jquery-rails', :git => 'git://github.com/rails/jquery-rails.git',
24
+ :tag => 'v2.2.1'
25
+
26
+ # To use ActiveModel has_secure_password
27
+ # gem 'bcrypt-ruby', '~> 3.0.0'
28
+
29
+ # To use Jbuilder templates for JSON
30
+ # gem 'jbuilder'
31
+
32
+ # Use unicorn as the app server
33
+ # gem 'unicorn'
34
+
35
+ # Deploy with Capistrano
36
+ # gem 'capistrano'
37
+
38
+ # To use debugger
39
+ # gem 'debugger'