bundler-leak 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.gitignore +11 -0
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/.travis.yml +13 -0
- data/.yardopts +1 -0
- data/COPYING.txt +674 -0
- data/ChangeLog.md +125 -0
- data/Gemfile +15 -0
- data/README.md +118 -0
- data/Rakefile +57 -0
- data/bin/bundle-leak +10 -0
- data/bin/bundler-leak +3 -0
- data/bundler-leak.gemspec +67 -0
- data/data/ruby-mem-advisory-db.ts +1 -0
- data/data/ruby-mem-advisory-db/.gitignore +1 -0
- data/data/ruby-mem-advisory-db/.rspec +1 -0
- data/data/ruby-mem-advisory-db/.travis.yml +12 -0
- data/data/ruby-mem-advisory-db/CONTRIBUTING.md +69 -0
- data/data/ruby-mem-advisory-db/CONTRIBUTORS.md +40 -0
- data/data/ruby-mem-advisory-db/Gemfile +9 -0
- data/data/ruby-mem-advisory-db/LICENSE.txt +5 -0
- data/data/ruby-mem-advisory-db/README.md +72 -0
- data/data/ruby-mem-advisory-db/Rakefile +26 -0
- data/data/ruby-mem-advisory-db/gems/celluloid/670.yml +10 -0
- data/data/ruby-mem-advisory-db/gems/grape/301.yml +9 -0
- data/data/ruby-mem-advisory-db/gems/oj/229.yml +9 -0
- data/data/ruby-mem-advisory-db/gems/redcarpet/516.yml +12 -0
- data/data/ruby-mem-advisory-db/gems/redis/612.yml +9 -0
- data/data/ruby-mem-advisory-db/gems/sidekiq-statistic/73.yml +9 -0
- data/data/ruby-mem-advisory-db/gems/sidekiq/2598.yml +9 -0
- data/data/ruby-mem-advisory-db/gems/therubyracer/336.yml +13 -0
- data/data/ruby-mem-advisory-db/gems/zipruby/PRE-SA-2012-02.yml +9 -0
- data/data/ruby-mem-advisory-db/scripts/post-advisories.sh +18 -0
- data/data/ruby-mem-advisory-db/spec/advisories_spec.rb +23 -0
- data/data/ruby-mem-advisory-db/spec/advisory_example.rb +209 -0
- data/data/ruby-mem-advisory-db/spec/gem_example.rb +37 -0
- data/data/ruby-mem-advisory-db/spec/library_example.rb +21 -0
- data/data/ruby-mem-advisory-db/spec/ruby_example.rb +22 -0
- data/data/ruby-mem-advisory-db/spec/spec_helper.rb +1 -0
- data/gemspec.yml +14 -0
- data/lib/bundler/plumber.rb +20 -0
- data/lib/bundler/plumber/advisory.rb +119 -0
- data/lib/bundler/plumber/cli.rb +135 -0
- data/lib/bundler/plumber/database.rb +249 -0
- data/lib/bundler/plumber/scanner.rb +133 -0
- data/lib/bundler/plumber/task.rb +49 -0
- data/lib/bundler/plumber/version.rb +24 -0
- data/spec/advisory_spec.rb +155 -0
- data/spec/audit_spec.rb +8 -0
- data/spec/bundle/insecure_sources/Gemfile +39 -0
- data/spec/bundle/secure/Gemfile +38 -0
- data/spec/bundle/unpatched_gems/Gemfile +39 -0
- data/spec/cli_spec.rb +99 -0
- data/spec/database_spec.rb +138 -0
- data/spec/fixtures/not_a_hash.yml +2 -0
- data/spec/integration_spec.rb +68 -0
- data/spec/scanner_spec.rb +61 -0
- data/spec/spec_helper.rb +62 -0
- metadata +141 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem "celluloid", "0.17.0"
|
4
|
+
gem "therubyracer", "0.12.1"
|
5
|
+
|
6
|
+
# Bundle edge Rails instead:
|
7
|
+
# gem 'rails', :git => 'git://github.com/rails/rails.git'
|
8
|
+
|
9
|
+
gem 'sqlite3', platform: [:mri, :rbx]
|
10
|
+
|
11
|
+
|
12
|
+
# Gems used only for assets and not required
|
13
|
+
# in production environments by default.
|
14
|
+
group :assets do
|
15
|
+
# gem 'sass-rails', '~> 3.2.3'
|
16
|
+
# gem 'coffee-rails', '~> 3.2.1'
|
17
|
+
|
18
|
+
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
|
19
|
+
# gem 'therubyracer', :platforms => :ruby
|
20
|
+
|
21
|
+
# gem 'uglifier', '>= 1.0.3'
|
22
|
+
end
|
23
|
+
|
24
|
+
gem 'jquery-rails'
|
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'
|
data/spec/cli_spec.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'bundler/plumber/cli'
|
3
|
+
|
4
|
+
describe Bundler::Plumber::CLI do
|
5
|
+
describe "#update" do
|
6
|
+
context "not --quiet (the default)" do
|
7
|
+
context "when update succeeds" do
|
8
|
+
|
9
|
+
before { expect(Bundler::Plumber::Database).to receive(:update!).and_return(true) }
|
10
|
+
|
11
|
+
it "prints updated message" do
|
12
|
+
expect { subject.update }.to output(/Updated ruby-mem-advisory-db/).to_stdout
|
13
|
+
end
|
14
|
+
|
15
|
+
it "prints total advisory count" do
|
16
|
+
database = double
|
17
|
+
expect(database).to receive(:size).and_return(1234)
|
18
|
+
expect(Bundler::Plumber::Database).to receive(:new).and_return(database)
|
19
|
+
|
20
|
+
expect { subject.update }.to output(/ruby-mem-advisory-db: 1234 advisories/).to_stdout
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when update fails" do
|
25
|
+
|
26
|
+
before { expect(Bundler::Plumber::Database).to receive(:update!).and_return(false) }
|
27
|
+
|
28
|
+
it "prints failure message" do
|
29
|
+
expect do
|
30
|
+
begin
|
31
|
+
subject.update
|
32
|
+
rescue SystemExit
|
33
|
+
end
|
34
|
+
end.to output(/Failed updating ruby-mem-advisory-db!/).to_stdout
|
35
|
+
end
|
36
|
+
|
37
|
+
it "exits with error status code" do
|
38
|
+
expect {
|
39
|
+
# Capture output of `update` only to keep spec output clean.
|
40
|
+
# The test regarding specific output is above.
|
41
|
+
expect { subject.update }.to output.to_stdout
|
42
|
+
}.to raise_error(SystemExit) do |error|
|
43
|
+
expect(error.success?).to eq(false)
|
44
|
+
expect(error.status).to eq(1)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "--quiet" do
|
52
|
+
before do
|
53
|
+
allow(subject).to receive(:options).and_return(double("Options", quiet?: true))
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when update succeeds" do
|
57
|
+
|
58
|
+
before do
|
59
|
+
expect(Bundler::Plumber::Database).to(
|
60
|
+
receive(:update!).with(quiet: true).and_return(true)
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "does not print any output" do
|
65
|
+
expect { subject.update }.to_not output.to_stdout
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when update fails" do
|
70
|
+
|
71
|
+
before do
|
72
|
+
expect(Bundler::Plumber::Database).to(
|
73
|
+
receive(:update!).with(quiet: true).and_return(false)
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "prints failure message" do
|
78
|
+
expect do
|
79
|
+
begin
|
80
|
+
subject.update
|
81
|
+
rescue SystemExit
|
82
|
+
end
|
83
|
+
end.to output(/Failed updating ruby-mem-advisory-db!/).to_stdout
|
84
|
+
end
|
85
|
+
|
86
|
+
it "exits with error status code" do
|
87
|
+
expect {
|
88
|
+
# Capture output of `update` only to keep spec output clean.
|
89
|
+
# The test regarding specific output is above.
|
90
|
+
expect { subject.update }.to output.to_stdout
|
91
|
+
}.to raise_error(SystemExit) do |error|
|
92
|
+
expect(error.success?).to eq(false)
|
93
|
+
expect(error.status).to eq(1)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'bundler/plumber/database'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
describe Bundler::Plumber::Database do
|
6
|
+
let(:vendored_advisories) do
|
7
|
+
Dir[File.join(Bundler::Plumber::Database::VENDORED_PATH, 'gems/*/*.yml')].sort
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "path" do
|
11
|
+
subject { described_class.path }
|
12
|
+
|
13
|
+
it "it should be a directory" do
|
14
|
+
expect(File.directory?(subject)).to be_truthy
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should prefer the user repo, iff it's as up to date, or more up to date than the vendored one" do
|
18
|
+
Bundler::Plumber::Database.update!(quiet: false)
|
19
|
+
|
20
|
+
Dir.chdir(Bundler::Plumber::Database::USER_PATH) do
|
21
|
+
puts "Timestamp:"
|
22
|
+
system 'git log --pretty="%cd" -1'
|
23
|
+
end
|
24
|
+
|
25
|
+
# As up to date...
|
26
|
+
expect(Bundler::Plumber::Database.path).to eq mocked_user_path
|
27
|
+
|
28
|
+
# More up to date...
|
29
|
+
fake_a_commit_in_the_user_repo
|
30
|
+
expect(Bundler::Plumber::Database.path).to eq mocked_user_path
|
31
|
+
|
32
|
+
roll_user_repo_back(20)
|
33
|
+
expect(Bundler::Plumber::Database.path).to eq Bundler::Plumber::Database::VENDORED_PATH
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "update!" do
|
38
|
+
it "should create the USER_PATH path as needed" do
|
39
|
+
Bundler::Plumber::Database.update!(quiet: false)
|
40
|
+
expect(File.directory?(mocked_user_path)).to be true
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should create the repo, then update it given multple successive calls." do
|
44
|
+
expect_update_to_clone_repo!
|
45
|
+
Bundler::Plumber::Database.update!(quiet: false)
|
46
|
+
expect(File.directory?(mocked_user_path)).to be true
|
47
|
+
|
48
|
+
expect_update_to_update_repo!
|
49
|
+
Bundler::Plumber::Database.update!(quiet: false)
|
50
|
+
expect(File.directory?(mocked_user_path)).to be true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "#initialize" do
|
55
|
+
context "when given no arguments" do
|
56
|
+
subject { described_class.new }
|
57
|
+
|
58
|
+
it "should default path to path" do
|
59
|
+
expect(subject.path).to eq(described_class.path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "when given a directory" do
|
64
|
+
let(:path ) { Dir.tmpdir }
|
65
|
+
|
66
|
+
subject { described_class.new(path) }
|
67
|
+
|
68
|
+
it "should set #path" do
|
69
|
+
expect(subject.path).to eq(path)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when given an invalid directory" do
|
74
|
+
it "should raise an ArgumentError" do
|
75
|
+
expect {
|
76
|
+
described_class.new('/foo/bar/baz')
|
77
|
+
}.to raise_error(ArgumentError)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "#check_gem" do
|
83
|
+
let(:gem) do
|
84
|
+
Gem::Specification.new do |s|
|
85
|
+
s.name = 'celluloid'
|
86
|
+
s.version = '0.16.1'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "when given a block" do
|
91
|
+
it "should yield every advisory affecting the gem" do
|
92
|
+
advisories = []
|
93
|
+
|
94
|
+
subject.check_gem(gem) do |advisory|
|
95
|
+
advisories << advisory
|
96
|
+
end
|
97
|
+
|
98
|
+
expect(advisories).not_to be_empty
|
99
|
+
expect(advisories.all? { |advisory|
|
100
|
+
advisory.kind_of?(Bundler::Plumber::Advisory)
|
101
|
+
}).to be_truthy
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when given no block" do
|
106
|
+
it "should return an Enumerator" do
|
107
|
+
expect(subject.check_gem(gem)).to be_kind_of(Enumerable)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#size" do
|
113
|
+
it { expect(subject.size).to eq vendored_advisories.count }
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "#advisories" do
|
117
|
+
it "should return a list of all advisories." do
|
118
|
+
actual_advisories = Bundler::Plumber::Database.new.
|
119
|
+
advisories.
|
120
|
+
map(&:path).
|
121
|
+
sort
|
122
|
+
|
123
|
+
expect(actual_advisories).to eq vendored_advisories
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "#to_s" do
|
128
|
+
it "should return the Database path" do
|
129
|
+
expect(subject.to_s).to eq(subject.path)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe "#inspect" do
|
134
|
+
it "should produce a Ruby-ish instance descriptor" do
|
135
|
+
expect(Bundler::Plumber::Database.new.inspect).to eq("#<Bundler::Plumber::Database:#{Bundler::Plumber::Database::VENDORED_PATH}>")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "CLI" do
|
4
|
+
include Helpers
|
5
|
+
|
6
|
+
let(:command) do
|
7
|
+
File.expand_path(File.join(File.dirname(__FILE__),'..','bin','bundler-leak'))
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when auditing a bundle with unpatched gems" do
|
11
|
+
let(:bundle) { 'unpatched_gems' }
|
12
|
+
let(:directory) { File.join('spec','bundle', bundle) }
|
13
|
+
|
14
|
+
subject do
|
15
|
+
Dir.chdir(directory) { sh(command, :fail => true) }
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should print a warning" do
|
19
|
+
expect(subject).to include("Vulnerabilities found!")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should print advisory information for the vulnerable gems" do
|
23
|
+
advisory_pattern = /(Name: [^\n]+
|
24
|
+
Version: \d+.\d+.\d+
|
25
|
+
URL: https?:\/\/(www\.)?.+
|
26
|
+
Title: [^\n]*?
|
27
|
+
Solution: remove or disable this gem until a patch is available!)+/
|
28
|
+
|
29
|
+
expect(subject).to match(advisory_pattern)
|
30
|
+
expect(subject).to include("Vulnerabilities found!")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when auditing a secure bundle" do
|
35
|
+
let(:bundle) { 'secure' }
|
36
|
+
let(:directory) { File.join('spec','bundle',bundle) }
|
37
|
+
|
38
|
+
subject do
|
39
|
+
Dir.chdir(directory) { sh(command) }
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should print nothing when everything is fine" do
|
43
|
+
expect(subject.strip).to eq("No vulnerabilities found")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "update" do
|
48
|
+
|
49
|
+
let(:update_command) { "#{command} update" }
|
50
|
+
let(:bundle) { 'secure' }
|
51
|
+
let(:directory) { File.join('spec','bundle',bundle) }
|
52
|
+
|
53
|
+
subject do
|
54
|
+
Dir.chdir(directory) { sh(update_command) }
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when advisories update successfully" do
|
58
|
+
it "should print status" do
|
59
|
+
expect(subject).not_to include("Fail")
|
60
|
+
expect(subject).to include("Updating ruby-mem-advisory-db ...\n")
|
61
|
+
expect(subject).to include("Updated ruby-mem-advisory-db\n")
|
62
|
+
expect(subject.lines.to_a.last).to match(/ruby-mem-advisory-db: \d+ advisories/)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'bundler/plumber/scanner'
|
3
|
+
|
4
|
+
describe Scanner do
|
5
|
+
describe "#scan" do
|
6
|
+
let(:bundle) { 'unpatched_gems' }
|
7
|
+
let(:directory) { File.join('spec','bundle',bundle) }
|
8
|
+
|
9
|
+
subject { described_class.new(directory) }
|
10
|
+
|
11
|
+
it "should yield results" do
|
12
|
+
results = []
|
13
|
+
|
14
|
+
subject.scan { |result| results << result }
|
15
|
+
|
16
|
+
expect(results).not_to be_empty
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when not called with a block" do
|
20
|
+
it "should return an Enumerator" do
|
21
|
+
expect(subject.scan).to be_kind_of(Enumerable)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "when auditing a bundle with unpatched gems" do
|
27
|
+
let(:bundle) { 'unpatched_gems' }
|
28
|
+
let(:directory) { File.join('spec','bundle',bundle) }
|
29
|
+
let(:scanner) { described_class.new(directory) }
|
30
|
+
|
31
|
+
subject { scanner.scan.to_a }
|
32
|
+
|
33
|
+
it "should match unpatched gems to their advisories" do
|
34
|
+
expect(subject.all? { |result|
|
35
|
+
result.advisory.vulnerable?(result.gem.version)
|
36
|
+
}).to be_truthy
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when the :ignore option is given" do
|
40
|
+
subject { scanner.scan(:ignore => ['OSVDB-89026']) }
|
41
|
+
|
42
|
+
it "should ignore the specified advisories" do
|
43
|
+
ids = subject.map { |result| result.advisory.id }
|
44
|
+
|
45
|
+
expect(ids).not_to include('OSVDB-89026')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when auditing a secure bundle" do
|
51
|
+
let(:bundle) { 'secure' }
|
52
|
+
let(:directory) { File.join('spec','bundle',bundle) }
|
53
|
+
let(:scanner) { described_class.new(directory) }
|
54
|
+
|
55
|
+
subject { scanner.scan.to_a }
|
56
|
+
|
57
|
+
it "should print nothing when everything is fine" do
|
58
|
+
expect(subject).to be_empty
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
require 'bundler/plumber/version'
|
6
|
+
require 'bundler/plumber/database'
|
7
|
+
|
8
|
+
module Helpers
|
9
|
+
def sh(command, options={})
|
10
|
+
Bundler.with_clean_env do
|
11
|
+
result = `#{command} 2>&1`
|
12
|
+
raise "FAILED #{command}\n#{result}" if $?.success? == !!options[:fail]
|
13
|
+
result
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def decolorize(string)
|
18
|
+
string.gsub(/\e\[\d+m/, "")
|
19
|
+
end
|
20
|
+
|
21
|
+
def mocked_user_path
|
22
|
+
File.expand_path('../../tmp/ruby-mem-advisory-db', __FILE__)
|
23
|
+
end
|
24
|
+
|
25
|
+
def expect_update_to_clone_repo!
|
26
|
+
expect(Bundler::Plumber::Database).
|
27
|
+
to receive(:system).
|
28
|
+
with('git', 'clone', Bundler::Plumber::Database::VENDORED_PATH, mocked_user_path).
|
29
|
+
and_call_original
|
30
|
+
end
|
31
|
+
|
32
|
+
def expect_update_to_update_repo!
|
33
|
+
expect(Bundler::Plumber::Database).
|
34
|
+
to receive(:system).
|
35
|
+
with('git', 'pull', 'origin', 'master').
|
36
|
+
and_call_original
|
37
|
+
end
|
38
|
+
|
39
|
+
def fake_a_commit_in_the_user_repo
|
40
|
+
Dir.chdir(mocked_user_path) do
|
41
|
+
system 'git', 'commit', '--allow-empty', '-m', 'Dummy commit.'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def roll_user_repo_back(num_commits)
|
46
|
+
Dir.chdir(mocked_user_path) do
|
47
|
+
system 'git', 'reset', '--hard', "HEAD~#{num_commits}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
include Bundler::Plumber
|
53
|
+
|
54
|
+
RSpec.configure do |config|
|
55
|
+
include Helpers
|
56
|
+
|
57
|
+
config.before(:each) do
|
58
|
+
stub_const("Bundler::Plumber::Database::URL", Bundler::Plumber::Database::VENDORED_PATH)
|
59
|
+
stub_const("Bundler::Plumber::Database::USER_PATH", mocked_user_path)
|
60
|
+
FileUtils.rm_rf(mocked_user_path) if File.exist?(mocked_user_path)
|
61
|
+
end
|
62
|
+
end
|