bundler-organization_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.
- data.tar.gz.sig +0 -0
- data/.gitignore +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +33 -0
- data/Rakefile +6 -0
- data/Readme.md +76 -0
- data/bin/bundle-organization-audit +35 -0
- data/bundler-organization_audit.gemspec +16 -0
- data/gem-public_cert.pem +20 -0
- data/lib/bundler/organization_audit.rb +68 -0
- data/lib/bundler/organization_audit/repo.rb +101 -0
- data/lib/bundler/organization_audit/version.rb +5 -0
- data/spec/bundler/organization_audit/repo_spec.rb +58 -0
- data/spec/bundler/organization_audit_spec.rb +112 -0
- data/spec/private.example.yml +7 -0
- data/spec/spec_helper.rb +1 -0
- metadata +111 -0
- metadata.gz.sig +0 -0
data.tar.gz.sig
ADDED
|
Binary file
|
data/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
spec/private.yml
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
bundler-organization_audit (0.1.0)
|
|
5
|
+
json
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: http://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
bump (0.3.9)
|
|
11
|
+
bundler-audit (0.1.2)
|
|
12
|
+
bundler (~> 1.2)
|
|
13
|
+
diff-lcs (1.1.3)
|
|
14
|
+
json (1.7.7)
|
|
15
|
+
rake (10.0.3)
|
|
16
|
+
rspec (2.12.0)
|
|
17
|
+
rspec-core (~> 2.12.0)
|
|
18
|
+
rspec-expectations (~> 2.12.0)
|
|
19
|
+
rspec-mocks (~> 2.12.0)
|
|
20
|
+
rspec-core (2.12.2)
|
|
21
|
+
rspec-expectations (2.12.1)
|
|
22
|
+
diff-lcs (~> 1.1.3)
|
|
23
|
+
rspec-mocks (2.12.2)
|
|
24
|
+
|
|
25
|
+
PLATFORMS
|
|
26
|
+
ruby
|
|
27
|
+
|
|
28
|
+
DEPENDENCIES
|
|
29
|
+
bump
|
|
30
|
+
bundler-audit
|
|
31
|
+
bundler-organization_audit!
|
|
32
|
+
rake
|
|
33
|
+
rspec (~> 2)
|
data/Rakefile
ADDED
data/Readme.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Audit all Gemfiles of a user/organization on Github for unpatched versions
|
|
2
|
+
|
|
3
|
+
gem install bundler-organization_audit
|
|
4
|
+
|
|
5
|
+
Usage
|
|
6
|
+
=====
|
|
7
|
+
|
|
8
|
+
### Public repos
|
|
9
|
+
For yourself (git config github.user)
|
|
10
|
+
```Bash
|
|
11
|
+
bundle-organization-audit
|
|
12
|
+
parallel
|
|
13
|
+
No Gemfile.lock found
|
|
14
|
+
|
|
15
|
+
parllel_tests
|
|
16
|
+
bundle-audit
|
|
17
|
+
No unpatched versions found
|
|
18
|
+
|
|
19
|
+
rails_example_app
|
|
20
|
+
bundle-audit
|
|
21
|
+
Name: rack
|
|
22
|
+
Version: 1.4.4
|
|
23
|
+
CVE: 2013-0263
|
|
24
|
+
Criticality: High
|
|
25
|
+
URL: http://osvdb.org/show/osvdb/89939
|
|
26
|
+
Title: Rack Rack::Session::Cookie Function Timing Attack Remote Code Execution
|
|
27
|
+
Patched Versions: ~> 1.1.6, ~> 1.2.8, ~> 1.3.10, ~> 1.4.5, >= 1.5.2
|
|
28
|
+
|
|
29
|
+
Vulnerable:
|
|
30
|
+
https://github.com/grosser/rails_example_app
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For someone elese
|
|
34
|
+
```Bash
|
|
35
|
+
bundle-organization-audit --user grosser
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Ignore gems (ignores repos that have a %{repo}.gemspec)
|
|
39
|
+
```Bash
|
|
40
|
+
bundle-organization-audit --ignore-gems
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For pipe -> only show vulnerable repos
|
|
44
|
+
```
|
|
45
|
+
bundle-organization-audit 2>/dev/null
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Use for CI -> ignore old/unmaintained proejcts
|
|
49
|
+
```
|
|
50
|
+
bundle-organization-audit \
|
|
51
|
+
--ignore https://github.com/xxx/a \
|
|
52
|
+
--ignore https://github.com/xxx/b \
|
|
53
|
+
--organization xxx \
|
|
54
|
+
--token yyy
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Private repos
|
|
58
|
+
|
|
59
|
+
```Bash
|
|
60
|
+
# create a token that has access to your repositories
|
|
61
|
+
curl -v -u your-user-name -X POST https://api.github.com/authorizations --data '{"scopes":["repo"]}'
|
|
62
|
+
enter your password -> TOKEN
|
|
63
|
+
|
|
64
|
+
bundle-organization-audit --user your-user --token TOKEN --organization your-organization
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Dev
|
|
68
|
+
===
|
|
69
|
+
- test private repo fetching via `cp spec/private{.example,}.yml` and filling it out
|
|
70
|
+
|
|
71
|
+
Author
|
|
72
|
+
======
|
|
73
|
+
[Michael Grosser](http://grosser.it)<br/>
|
|
74
|
+
michael@grosser.it<br/>
|
|
75
|
+
License: MIT<br/>
|
|
76
|
+
[](https://travis-ci.org/grosser/bundler-organization_audit)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
require "rubygems"
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
|
6
|
+
require "bundler/organization_audit"
|
|
7
|
+
|
|
8
|
+
def git_config(thing)
|
|
9
|
+
result = `git config #{thing}`.strip
|
|
10
|
+
result.empty? ? nil : result
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
options = {
|
|
14
|
+
:ignore => [],
|
|
15
|
+
:user => git_config("github.user")
|
|
16
|
+
}
|
|
17
|
+
OptionParser.new do |opts|
|
|
18
|
+
opts.banner = <<BANNER
|
|
19
|
+
Audit all Gemfiles of a user/organization on github for unpatched versions
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
bundle-organization-audit your-user-name
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
BANNER
|
|
26
|
+
opts.on("--token TOKEN", "Use token") { |token| options[:token] = token }
|
|
27
|
+
opts.on("--user USER", "Use user") { |user| options[:user] = user }
|
|
28
|
+
opts.on("--ignore REPO_URL", "Ignore given repo urls (use multiple times)") { |repo_url| options[:ignore] << repo_url }
|
|
29
|
+
opts.on("--ignore-gems", "Ignore repos that have a %{repo}.gemspec") { options[:ignore_gems] = true }
|
|
30
|
+
opts.on("--organization ORGANIZATION", "Use user") { |organization| options[:organization] = organization }
|
|
31
|
+
opts.on("-h", "--help", "Show this.") { puts opts; exit }
|
|
32
|
+
opts.on("-v", "--version", "Show Version"){ puts Bundler::OrganizationAudit::VERSION; exit}
|
|
33
|
+
end.parse!
|
|
34
|
+
|
|
35
|
+
exit Bundler::OrganizationAudit.run(options)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
|
2
|
+
name = "bundler-organization_audit"
|
|
3
|
+
require "#{name.gsub("-","/")}/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new name, Bundler::OrganizationAudit::VERSION do |s|
|
|
6
|
+
s.summary = s.description = "Audit all Gemfiles of a user/organization on github for unpatched versions"
|
|
7
|
+
s.authors = ["Michael Grosser"]
|
|
8
|
+
s.email = "michael@grosser.it"
|
|
9
|
+
s.homepage = "http://github.com/grosser/#{name}"
|
|
10
|
+
s.files = `git ls-files`.split("\n")
|
|
11
|
+
s.license = "MIT"
|
|
12
|
+
s.signing_key = File.expand_path("~/.ssh/gem-private_key.pem")
|
|
13
|
+
s.executables = ["bundle-organization-audit"]
|
|
14
|
+
s.cert_chain = ["gem-public_cert.pem"]
|
|
15
|
+
s.add_runtime_dependency "json"
|
|
16
|
+
end
|
data/gem-public_cert.pem
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIDMjCCAhqgAwIBAgIBADANBgkqhkiG9w0BAQUFADA/MRAwDgYDVQQDDAdtaWNo
|
|
3
|
+
YWVsMRcwFQYKCZImiZPyLGQBGRYHZ3Jvc3NlcjESMBAGCgmSJomT8ixkARkWAml0
|
|
4
|
+
MB4XDTEzMDIwMzE4MTMxMVoXDTE0MDIwMzE4MTMxMVowPzEQMA4GA1UEAwwHbWlj
|
|
5
|
+
aGFlbDEXMBUGCgmSJomT8ixkARkWB2dyb3NzZXIxEjAQBgoJkiaJk/IsZAEZFgJp
|
|
6
|
+
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMorXo/hgbUq97+kII9H
|
|
7
|
+
MsQcLdC/7wQ1ZP2OshVHPkeP0qH8MBHGg6eYisOX2ubNagF9YTCZWnhrdKrwpLOO
|
|
8
|
+
cPLaZbjUjljJ3cQR3B8Yn1veV5IhG86QseTBjymzJWsLpqJ1UZGpfB9tXcsFtuxO
|
|
9
|
+
6vHvcIHdzvc/OUkICttLbH+1qb6rsHUceqh+JrH4GrsJ5H4hAfIdyS2XMK7YRKbh
|
|
10
|
+
h+IBu6dFWJJByzFsYmV1PDXln3UBmgAt65cmCu4qPfThioCGDzbSJrGDGLmw/pFX
|
|
11
|
+
FPpVCm1zgYSb1v6Qnf3cgXa2f2wYGm17+zAVyIDpwryFru9yF/jJxE38z/DRsd9R
|
|
12
|
+
/88CAwEAAaM5MDcwCQYDVR0TBAIwADAdBgNVHQ4EFgQUsiNnXHtKeMYYcr4yJVmQ
|
|
13
|
+
WONL+IwwCwYDVR0PBAQDAgSwMA0GCSqGSIb3DQEBBQUAA4IBAQAlyN7kKo/NQCQ0
|
|
14
|
+
AOzZLZ3WAePvStkCFIJ53tsv5Kyo4pMAllv+BgPzzBt7qi605mFSL6zBd9uLou+W
|
|
15
|
+
Co3s48p1dy7CjjAfVQdmVNHF3MwXtfC2OEyvSQPi4xKR8iba8wa3xp9LVo1PuLpw
|
|
16
|
+
/6DsrChWw74HfsJN6qJOK684hJeT8lBYAUfiC3wD0owoPSg+XtyAAddisR+KV5Y1
|
|
17
|
+
NmVHuLtQcNTZy+gRht3ahJRMuC6QyLmkTsf+6MaenwAMkAgHdswGsJztOnNnBa3F
|
|
18
|
+
y0kCSWmK6D+x/SbfS6r7Ke07MRqziJdB9GuE1+0cIRuFh8EQ+LN6HXCKM5pon/GU
|
|
19
|
+
ycwMXfl0
|
|
20
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require "bundler/organization_audit/version"
|
|
2
|
+
require "tmpdir"
|
|
3
|
+
require "bundler/organization_audit/repo"
|
|
4
|
+
|
|
5
|
+
module Bundler
|
|
6
|
+
module OrganizationAudit
|
|
7
|
+
class << self
|
|
8
|
+
def run(options)
|
|
9
|
+
vulnerable = find_vulnerable(options).map(&:url)
|
|
10
|
+
vulnerable -= (options[:ignore] || [])
|
|
11
|
+
if vulnerable.size == 0
|
|
12
|
+
0
|
|
13
|
+
else
|
|
14
|
+
$stderr.puts "Vulnerable:"
|
|
15
|
+
puts vulnerable
|
|
16
|
+
1
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def download_file(repo, file, options)
|
|
23
|
+
return unless content = repo.content(file, options)
|
|
24
|
+
File.open(file, "w") { |f| f.write content }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def find_vulnerable(options)
|
|
28
|
+
Repo.all(options).select do |repo|
|
|
29
|
+
audit_repo(repo, options)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def audit_repo(repo, options)
|
|
34
|
+
success = false
|
|
35
|
+
$stderr.puts repo.project
|
|
36
|
+
in_temp_dir do
|
|
37
|
+
if download_file(repo, "Gemfile.lock", options)
|
|
38
|
+
if options[:ignore_gems] && repo.gem?(options)
|
|
39
|
+
$stderr.puts "Ignored because it's a gem"
|
|
40
|
+
else
|
|
41
|
+
success = !sh("bundle-audit")
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
$stderr.puts "No Gemfile.lock found"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
$stderr.puts ""
|
|
48
|
+
success
|
|
49
|
+
rescue Exception => e
|
|
50
|
+
$stderr.puts "Error auditing #{repo.project} (#{e})"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def in_temp_dir(&block)
|
|
54
|
+
Dir.mktmpdir { |dir| Dir.chdir(dir, &block) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def sh(cmd)
|
|
58
|
+
$stderr.puts cmd
|
|
59
|
+
IO.popen(cmd) do |pipe|
|
|
60
|
+
while str = pipe.gets
|
|
61
|
+
$stderr.puts str
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
$?.success?
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
require "open-uri"
|
|
2
|
+
require "json"
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
5
|
+
module Bundler
|
|
6
|
+
module OrganizationAudit
|
|
7
|
+
class Repo
|
|
8
|
+
HOST = "https://api.github.com"
|
|
9
|
+
|
|
10
|
+
def initialize(data)
|
|
11
|
+
@data = data
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def gem?(options={})
|
|
15
|
+
!!content("#{project}.gemspec", options)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def url
|
|
19
|
+
api_url.sub("api.", "").sub("/repos", "")
|
|
20
|
+
end
|
|
21
|
+
alias_method :to_s, :url
|
|
22
|
+
|
|
23
|
+
def project
|
|
24
|
+
api_url.split("/").last
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.all(options)
|
|
28
|
+
user = if options[:organization]
|
|
29
|
+
"orgs/#{options[:organization]}"
|
|
30
|
+
elsif options[:user]
|
|
31
|
+
"users/#{options[:user]}"
|
|
32
|
+
else
|
|
33
|
+
"user"
|
|
34
|
+
end
|
|
35
|
+
url = File.join(HOST, user, "repos")
|
|
36
|
+
|
|
37
|
+
download_all_pages(url, headers(options[:token])).map { |data| Repo.new(data) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def content(file, options={})
|
|
41
|
+
if private?
|
|
42
|
+
download_content_via_api(file, options)
|
|
43
|
+
else
|
|
44
|
+
download_content_via_raw(file)
|
|
45
|
+
end
|
|
46
|
+
rescue OpenURI::HTTPError => e
|
|
47
|
+
raise "Error downloading #{file} from #{url} (#{e})" unless e.message.start_with?("404")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def private?
|
|
51
|
+
@data["private"]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def self.download_all_pages(url, headers)
|
|
57
|
+
results = []
|
|
58
|
+
page = 1
|
|
59
|
+
loop do
|
|
60
|
+
result = JSON.parse(open("#{url}?page=#{page}", headers).read)
|
|
61
|
+
if result.size == 0
|
|
62
|
+
break
|
|
63
|
+
else
|
|
64
|
+
results.concat(result)
|
|
65
|
+
page += 1
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
results
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def branch
|
|
72
|
+
@data["default_branch"] || @data["master_branch"] || "master"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def api_url
|
|
76
|
+
@data["url"]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def raw_url
|
|
80
|
+
url.sub("://", "://raw.")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# increases api rate limit
|
|
84
|
+
def download_content_via_api(file, options)
|
|
85
|
+
url = File.join(api_url, "contents", file, "?ref=#{branch}")
|
|
86
|
+
content = open(url, self.class.headers(options.fetch(:token))).read
|
|
87
|
+
content = JSON.load(content)["content"]
|
|
88
|
+
Base64.decode64(content)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# unlimited
|
|
92
|
+
def download_content_via_raw(file)
|
|
93
|
+
open(File.join(raw_url, branch, file)).read
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.headers(token)
|
|
97
|
+
token ? {"Authorization" => "token #{token}"} : {}
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Bundler::OrganizationAudit::Repo do
|
|
4
|
+
let(:config){ YAML.load_file("spec/private.yml") }
|
|
5
|
+
let(:repo) do
|
|
6
|
+
Bundler::OrganizationAudit::Repo.new(
|
|
7
|
+
"url" => "https://api.github.com/repos/grosser/parallel"
|
|
8
|
+
)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
describe ".all" do
|
|
12
|
+
it "returns the list of public repositories" do
|
|
13
|
+
# use a big account -> make sure pagination works
|
|
14
|
+
list = Bundler::OrganizationAudit::Repo.all(:user => "grosser")
|
|
15
|
+
list.map(&:url).should include("https://github.com/grosser/parallel")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if File.exist?("spec/private.yml")
|
|
19
|
+
it "returns the list of private repositories from a user" do
|
|
20
|
+
list = Bundler::OrganizationAudit::Repo.all(:token => config["token"])
|
|
21
|
+
list.map(&:url).should include("https://github.com/#{config["user"]}/#{config["expected_user"]}")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "returns the list of private repositories from a organization" do
|
|
25
|
+
list = Bundler::OrganizationAudit::Repo.all(:token => config["token"], :organization => config["organization"])
|
|
26
|
+
list.map(&:url).should include("https://github.com/#{config["organization"]}/#{config["expected_organization"]}")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe "#content" do
|
|
32
|
+
it "can download a public file" do
|
|
33
|
+
repo.content("Gemfile.lock").should include('rspec (2')
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if File.exist?("spec/private.yml")
|
|
37
|
+
it "can download a private file" do
|
|
38
|
+
url = "https://api.github.com/repos/#{config["organization"]}/#{config["expected_organization"]}"
|
|
39
|
+
repo = Bundler::OrganizationAudit::Repo.new(
|
|
40
|
+
"url" => url, "private" => true
|
|
41
|
+
)
|
|
42
|
+
content = repo.content("Gemfile.lock", :token => config["token"], :user => config["user"])
|
|
43
|
+
content.should include('i18n (0.')
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe "#gem?" do
|
|
49
|
+
it "is a gem if it has a gemspec" do
|
|
50
|
+
repo.should be_gem
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "is not a gem if it has no gemspec" do
|
|
54
|
+
Bundler::OrganizationAudit::Repo.new("url" => "https://api.github.com/repos/grosser/dotfiles").should_not be_gem
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Bundler::OrganizationAudit do
|
|
4
|
+
it "has a VERSION" do
|
|
5
|
+
Bundler::OrganizationAudit::VERSION.should =~ /^[\.\da-z]+$/
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
describe Bundler::OrganizationAudit do
|
|
9
|
+
let(:repo) do
|
|
10
|
+
Bundler::OrganizationAudit::Repo.new(
|
|
11
|
+
"url" => "https://api.github.com/repos/grosser/parallel"
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe ".audit_repo" do
|
|
16
|
+
it "audits public repos" do
|
|
17
|
+
out = record_out do
|
|
18
|
+
Bundler::OrganizationAudit.send(:audit_repo, repo, {})
|
|
19
|
+
end
|
|
20
|
+
out.strip.should == "parallel\nbundle-audit\nNo unpatched versions found"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "does not audit ignored repos" do
|
|
24
|
+
out = record_out do
|
|
25
|
+
Bundler::OrganizationAudit.send(:audit_repo, repo, :ignore_gems => true)
|
|
26
|
+
end
|
|
27
|
+
out.strip.should == "parallel\nIgnored because it's a gem"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe ".run" do
|
|
32
|
+
before do
|
|
33
|
+
Bundler::OrganizationAudit.stub(:puts)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it "is successful when failed are empty" do
|
|
37
|
+
Bundler::OrganizationAudit.should_receive(:find_vulnerable).and_return([])
|
|
38
|
+
record_out do
|
|
39
|
+
Bundler::OrganizationAudit.run({}).should == 0
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "fails with failed" do
|
|
44
|
+
Bundler::OrganizationAudit.should_receive(:find_vulnerable).and_return([repo])
|
|
45
|
+
record_out do
|
|
46
|
+
Bundler::OrganizationAudit.run({}).should == 1
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "CLI" do
|
|
53
|
+
it "can audit a user" do
|
|
54
|
+
result = audit("--user anamartinez")
|
|
55
|
+
result.should include "I18N-tools\nNo Gemfile.lock found" # did not use audit when not necessary
|
|
56
|
+
result.should include "js-cldr-timezones\nbundle-audit\nNo unpatched versions found" # used audit where necessary
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "can audit a unpatched user" do
|
|
60
|
+
result = audit("--user user-with-unpatched-apps", :fail => true)
|
|
61
|
+
result.should include "unpatched\nbundle-audit\nName: json\nVersion: 1.5.3" # Individual vulnerabilities
|
|
62
|
+
result.should include "Vulnerable:\nhttps://github.com/user-with-unpatched-apps/unpatched" # Summary
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "only shows failed projects on stdout" do
|
|
66
|
+
result = audit("--user user-with-unpatched-apps 2>/dev/null", :fail => true, :keep_output => true)
|
|
67
|
+
result.should == "https://github.com/user-with-unpatched-apps/unpatched\n"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "ignores projects in --ignore" do
|
|
71
|
+
result = audit("--user user-with-unpatched-apps --ignore https://github.com/user-with-unpatched-apps/unpatched 2>/dev/null", :keep_output => true)
|
|
72
|
+
result.should == ""
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it "shows --version" do
|
|
76
|
+
audit("--version").should include(Bundler::OrganizationAudit::VERSION)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "shows --help" do
|
|
80
|
+
audit("--help").should include("Audit all Gemfiles")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def audit(command, options={})
|
|
84
|
+
sh("bin/bundle-organization-audit #{command}", options)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def sh(command, options={})
|
|
88
|
+
result = `#{command} #{"2>&1" unless options[:keep_output]}`
|
|
89
|
+
raise "FAILED #{command}\n#{result}" if $?.success? == !!options[:fail]
|
|
90
|
+
decolorize(result)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def decolorize(string)
|
|
95
|
+
string.gsub(/\e\[\d+m/, "")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def record_out
|
|
99
|
+
recorder = StringIO.new
|
|
100
|
+
$stdout, out = recorder, $stdout
|
|
101
|
+
$stderr, err = recorder, $stderr
|
|
102
|
+
yield
|
|
103
|
+
decolorize(recorder.string)
|
|
104
|
+
ensure
|
|
105
|
+
$stdout = out
|
|
106
|
+
$stderr = err
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def in_temp_dir(&block)
|
|
110
|
+
Dir.mktmpdir { |dir| Dir.chdir(dir, &block) }
|
|
111
|
+
end
|
|
112
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/organization_audit"
|
metadata
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: bundler-organization_audit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Michael Grosser
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain:
|
|
12
|
+
- !binary |-
|
|
13
|
+
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURNakNDQWhxZ0F3SUJB
|
|
14
|
+
Z0lCQURBTkJna3Foa2lHOXcwQkFRVUZBREEvTVJBd0RnWURWUVFEREFkdGFX
|
|
15
|
+
Tm8KWVdWc01SY3dGUVlLQ1pJbWlaUHlMR1FCR1JZSFozSnZjM05sY2pFU01C
|
|
16
|
+
QUdDZ21TSm9tVDhpeGtBUmtXQW1sMApNQjRYRFRFek1ESXdNekU0TVRNeE1W
|
|
17
|
+
b1hEVEUwTURJd016RTRNVE14TVZvd1B6RVFNQTRHQTFVRUF3d0hiV2xqCmFH
|
|
18
|
+
RmxiREVYTUJVR0NnbVNKb21UOGl4a0FSa1dCMmR5YjNOelpYSXhFakFRQmdv
|
|
19
|
+
SmtpYUprL0lzWkFFWkZnSnAKZERDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFB
|
|
20
|
+
RGdnRVBBRENDQVFvQ2dnRUJBTW9yWG8vaGdiVXE5NytrSUk5SApNc1FjTGRD
|
|
21
|
+
Lzd3UTFaUDJPc2hWSFBrZVAwcUg4TUJIR2c2ZVlpc09YMnViTmFnRjlZVENa
|
|
22
|
+
V25ocmRLcndwTE9PCmNQTGFaYmpVamxqSjNjUVIzQjhZbjF2ZVY1SWhHODZR
|
|
23
|
+
c2VUQmp5bXpKV3NMcHFKMVVaR3BmQjl0WGNzRnR1eE8KNnZIdmNJSGR6dmMv
|
|
24
|
+
T1VrSUN0dExiSCsxcWI2cnNIVWNlcWgrSnJINEdyc0o1SDRoQWZJZHlTMlhN
|
|
25
|
+
SzdZUktiaApoK0lCdTZkRldKSkJ5ekZzWW1WMVBEWGxuM1VCbWdBdDY1Y21D
|
|
26
|
+
dTRxUGZUaGlvQ0dEemJTSnJHREdMbXcvcEZYCkZQcFZDbTF6Z1lTYjF2NlFu
|
|
27
|
+
ZjNjZ1hhMmYyd1lHbTE3K3pBVnlJRHB3cnlGcnU5eUYvakp4RTM4ei9EUnNk
|
|
28
|
+
OVIKLzg4Q0F3RUFBYU01TURjd0NRWURWUjBUQkFJd0FEQWRCZ05WSFE0RUZn
|
|
29
|
+
UVVzaU5uWEh0S2VNWVljcjR5SlZtUQpXT05MK0l3d0N3WURWUjBQQkFRREFn
|
|
30
|
+
U3dNQTBHQ1NxR1NJYjNEUUVCQlFVQUE0SUJBUUFseU43a0tvL05RQ1EwCkFP
|
|
31
|
+
elpMWjNXQWVQdlN0a0NGSUo1M3RzdjVLeW80cE1BbGx2K0JnUHp6QnQ3cWk2
|
|
32
|
+
MDVtRlNMNnpCZDl1TG91K1cKQ28zczQ4cDFkeTdDampBZlZRZG1WTkhGM013
|
|
33
|
+
WHRmQzJPRXl2U1FQaTR4S1I4aWJhOHdhM3hwOUxWbzFQdUxwdwovNkRzckNo
|
|
34
|
+
V3c3NEhmc0pONnFKT0s2ODRoSmVUOGxCWUFVZmlDM3dEMG93b1BTZytYdHlB
|
|
35
|
+
QWRkaXNSK0tWNVkxCk5tVkh1THRRY05UWnkrZ1JodDNhaEpSTXVDNlF5TG1r
|
|
36
|
+
VHNmKzZNYWVud0FNa0FnSGRzd0dzSnp0T25ObkJhM0YKeTBrQ1NXbUs2RCt4
|
|
37
|
+
L1NiZlM2cjdLZTA3TVJxemlKZEI5R3VFMSswY0lSdUZoOEVRK0xONkhYQ0tN
|
|
38
|
+
NXBvbi9HVQp5Y3dNWGZsMAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
|
|
39
|
+
date: 2013-02-18 00:00:00.000000000 Z
|
|
40
|
+
dependencies:
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: json
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
none: false
|
|
45
|
+
requirements:
|
|
46
|
+
- - ! '>='
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
type: :runtime
|
|
50
|
+
prerelease: false
|
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
+
none: false
|
|
53
|
+
requirements:
|
|
54
|
+
- - ! '>='
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '0'
|
|
57
|
+
description: Audit all Gemfiles of a user/organization on github for unpatched versions
|
|
58
|
+
email: michael@grosser.it
|
|
59
|
+
executables:
|
|
60
|
+
- bundle-organization-audit
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- .gitignore
|
|
65
|
+
- .travis.yml
|
|
66
|
+
- Gemfile
|
|
67
|
+
- Gemfile.lock
|
|
68
|
+
- Rakefile
|
|
69
|
+
- Readme.md
|
|
70
|
+
- bin/bundle-organization-audit
|
|
71
|
+
- bundler-organization_audit.gemspec
|
|
72
|
+
- gem-public_cert.pem
|
|
73
|
+
- lib/bundler/organization_audit.rb
|
|
74
|
+
- lib/bundler/organization_audit/repo.rb
|
|
75
|
+
- lib/bundler/organization_audit/version.rb
|
|
76
|
+
- spec/bundler/organization_audit/repo_spec.rb
|
|
77
|
+
- spec/bundler/organization_audit_spec.rb
|
|
78
|
+
- spec/private.example.yml
|
|
79
|
+
- spec/spec_helper.rb
|
|
80
|
+
homepage: http://github.com/grosser/bundler-organization_audit
|
|
81
|
+
licenses:
|
|
82
|
+
- MIT
|
|
83
|
+
post_install_message:
|
|
84
|
+
rdoc_options: []
|
|
85
|
+
require_paths:
|
|
86
|
+
- lib
|
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
88
|
+
none: false
|
|
89
|
+
requirements:
|
|
90
|
+
- - ! '>='
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '0'
|
|
93
|
+
segments:
|
|
94
|
+
- 0
|
|
95
|
+
hash: 75415256473976345
|
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
|
+
none: false
|
|
98
|
+
requirements:
|
|
99
|
+
- - ! '>='
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
segments:
|
|
103
|
+
- 0
|
|
104
|
+
hash: 75415256473976345
|
|
105
|
+
requirements: []
|
|
106
|
+
rubyforge_project:
|
|
107
|
+
rubygems_version: 1.8.24
|
|
108
|
+
signing_key:
|
|
109
|
+
specification_version: 3
|
|
110
|
+
summary: Audit all Gemfiles of a user/organization on github for unpatched versions
|
|
111
|
+
test_files: []
|
metadata.gz.sig
ADDED
|
Binary file
|