bundler-organization_audit 0.1.0 → 0.1.2
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/Gemfile +2 -2
- data/Gemfile.lock +12 -5
- data/Readme.md +22 -6
- data/bin/bundle-organization-audit +2 -0
- data/bundler-organization_audit.gemspec +7 -2
- data/lib/bundler/organization_audit.rb +11 -7
- data/lib/bundler/organization_audit/repo.rb +38 -12
- data/lib/bundler/organization_audit/version.rb +1 -1
- data/spec/bundler/organization_audit/repo_spec.rb +8 -2
- data/spec/bundler/organization_audit_spec.rb +6 -1
- data/spec/spec_helper.rb +1 -0
- metadata +5 -5
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
|
Binary file
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
GIT
|
|
2
|
+
remote: git://github.com/grosser/bundler-audit.git
|
|
3
|
+
revision: a2d65124650460f525f62c7302629fee4d697413
|
|
4
|
+
branch: ignore-version
|
|
5
|
+
submodules: true
|
|
6
|
+
specs:
|
|
7
|
+
bundler-audit (0.1.3)
|
|
8
|
+
bundler (~> 1.2)
|
|
9
|
+
|
|
1
10
|
PATH
|
|
2
11
|
remote: .
|
|
3
12
|
specs:
|
|
4
|
-
bundler-organization_audit (0.1.
|
|
13
|
+
bundler-organization_audit (0.1.2)
|
|
5
14
|
json
|
|
6
15
|
|
|
7
16
|
GEM
|
|
8
|
-
remote:
|
|
17
|
+
remote: https://rubygems.org/
|
|
9
18
|
specs:
|
|
10
19
|
bump (0.3.9)
|
|
11
|
-
bundler-audit (0.1.2)
|
|
12
|
-
bundler (~> 1.2)
|
|
13
20
|
diff-lcs (1.1.3)
|
|
14
21
|
json (1.7.7)
|
|
15
22
|
rake (10.0.3)
|
|
@@ -27,7 +34,7 @@ PLATFORMS
|
|
|
27
34
|
|
|
28
35
|
DEPENDENCIES
|
|
29
36
|
bump
|
|
30
|
-
bundler-audit
|
|
37
|
+
bundler-audit!
|
|
31
38
|
bundler-organization_audit!
|
|
32
39
|
rake
|
|
33
40
|
rspec (~> 2)
|
data/Readme.md
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
Audit all Gemfiles of a user/organization on Github for unpatched versions
|
|
2
2
|
|
|
3
|
+
# simple
|
|
3
4
|
gem install bundler-organization_audit
|
|
4
5
|
|
|
6
|
+
# if you want --ignore-cve
|
|
7
|
+
git clone git://github.com/grosser/bundler-organization_audit.git
|
|
8
|
+
cd bundler-organization_audit
|
|
9
|
+
bundle update bundler-audit # get new advisories
|
|
10
|
+
bundle exec ./bin/bundle-organization-audit ... options ...
|
|
11
|
+
|
|
5
12
|
Usage
|
|
6
13
|
=====
|
|
7
14
|
|
|
@@ -27,10 +34,10 @@ Title: Rack Rack::Session::Cookie Function Timing Attack Remote Code Execution
|
|
|
27
34
|
Patched Versions: ~> 1.1.6, ~> 1.2.8, ~> 1.3.10, ~> 1.4.5, >= 1.5.2
|
|
28
35
|
|
|
29
36
|
Vulnerable:
|
|
30
|
-
https://github.com/grosser/rails_example_app
|
|
37
|
+
https://github.com/grosser/rails_example_app -- Peter Last Committer <peter@last-commit-email.com>
|
|
31
38
|
```
|
|
32
39
|
|
|
33
|
-
For someone
|
|
40
|
+
For someone else
|
|
34
41
|
```Bash
|
|
35
42
|
bundle-organization-audit --user grosser
|
|
36
43
|
```
|
|
@@ -40,16 +47,20 @@ Ignore gems (ignores repos that have a %{repo}.gemspec)
|
|
|
40
47
|
bundle-organization-audit --ignore-gems
|
|
41
48
|
```
|
|
42
49
|
|
|
43
|
-
|
|
50
|
+
Silent: only show vulnerable repos
|
|
44
51
|
```
|
|
45
52
|
bundle-organization-audit 2>/dev/null
|
|
46
53
|
```
|
|
47
54
|
|
|
48
|
-
|
|
55
|
+
CI: ignore old/unmaintained proejcts, unfixable/unimportant cves and gems
|
|
49
56
|
```
|
|
50
57
|
bundle-organization-audit \
|
|
51
58
|
--ignore https://github.com/xxx/a \
|
|
52
59
|
--ignore https://github.com/xxx/b \
|
|
60
|
+
--ignore-cve 2013-0269@1.5.3 \
|
|
61
|
+
--ignore-cve '2013-0123@~>3.2.10' \
|
|
62
|
+
--ignore-cve 2013-0234 \
|
|
63
|
+
--ignore-gems \
|
|
53
64
|
--organization xxx \
|
|
54
65
|
--token yyy
|
|
55
66
|
```
|
|
@@ -64,8 +75,13 @@ enter your password -> TOKEN
|
|
|
64
75
|
bundle-organization-audit --user your-user --token TOKEN --organization your-organization
|
|
65
76
|
```
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
Related
|
|
79
|
+
=======
|
|
80
|
+
- [holepicker](https://github.com/jsuder/holepicker) does the same check for local projects and running servers
|
|
81
|
+
- [bundler-audit](https://github.com/postmodern/bundler-audit) check a single local project for vulerabilities
|
|
82
|
+
|
|
83
|
+
Development
|
|
84
|
+
===========
|
|
69
85
|
- test private repo fetching via `cp spec/private{.example,}.yml` and filling it out
|
|
70
86
|
|
|
71
87
|
Author
|
|
@@ -12,6 +12,7 @@ end
|
|
|
12
12
|
|
|
13
13
|
options = {
|
|
14
14
|
:ignore => [],
|
|
15
|
+
:ignore_cves => [],
|
|
15
16
|
:user => git_config("github.user")
|
|
16
17
|
}
|
|
17
18
|
OptionParser.new do |opts|
|
|
@@ -27,6 +28,7 @@ BANNER
|
|
|
27
28
|
opts.on("--user USER", "Use user") { |user| options[:user] = user }
|
|
28
29
|
opts.on("--ignore REPO_URL", "Ignore given repo urls (use multiple times)") { |repo_url| options[:ignore] << repo_url }
|
|
29
30
|
opts.on("--ignore-gems", "Ignore repos that have a %{repo}.gemspec") { options[:ignore_gems] = true }
|
|
31
|
+
opts.on("--ignore-cve CVE_NUMBER", "Ignore CVE that you do not want to get warned about just number or number@gem-version") { |cve| options[:ignore_cves] << cve }
|
|
30
32
|
opts.on("--organization ORGANIZATION", "Use user") { |organization| options[:organization] = organization }
|
|
31
33
|
opts.on("-h", "--help", "Show this.") { puts opts; exit }
|
|
32
34
|
opts.on("-v", "--version", "Show Version"){ puts Bundler::OrganizationAudit::VERSION; exit}
|
|
@@ -9,8 +9,13 @@ Gem::Specification.new name, Bundler::OrganizationAudit::VERSION do |s|
|
|
|
9
9
|
s.homepage = "http://github.com/grosser/#{name}"
|
|
10
10
|
s.files = `git ls-files`.split("\n")
|
|
11
11
|
s.license = "MIT"
|
|
12
|
-
|
|
12
|
+
key = File.expand_path("~/.ssh/gem-private_key.pem")
|
|
13
|
+
if File.exist?(key)
|
|
14
|
+
s.signing_key = key
|
|
15
|
+
s.cert_chain = ["gem-public_cert.pem"]
|
|
16
|
+
else
|
|
17
|
+
puts "No signature"
|
|
18
|
+
end
|
|
13
19
|
s.executables = ["bundle-organization-audit"]
|
|
14
|
-
s.cert_chain = ["gem-public_cert.pem"]
|
|
15
20
|
s.add_runtime_dependency "json"
|
|
16
21
|
end
|
|
@@ -6,8 +6,7 @@ module Bundler
|
|
|
6
6
|
module OrganizationAudit
|
|
7
7
|
class << self
|
|
8
8
|
def run(options)
|
|
9
|
-
vulnerable = find_vulnerable(options)
|
|
10
|
-
vulnerable -= (options[:ignore] || [])
|
|
9
|
+
vulnerable = find_vulnerable(options)
|
|
11
10
|
if vulnerable.size == 0
|
|
12
11
|
0
|
|
13
12
|
else
|
|
@@ -19,13 +18,14 @@ module Bundler
|
|
|
19
18
|
|
|
20
19
|
private
|
|
21
20
|
|
|
22
|
-
def download_file(repo, file
|
|
23
|
-
return unless content = repo.content(file
|
|
21
|
+
def download_file(repo, file)
|
|
22
|
+
return unless content = repo.content(file)
|
|
24
23
|
File.open(file, "w") { |f| f.write content }
|
|
25
24
|
end
|
|
26
25
|
|
|
27
26
|
def find_vulnerable(options)
|
|
28
27
|
Repo.all(options).select do |repo|
|
|
28
|
+
next if (options[:ignore] || []).include? repo.url
|
|
29
29
|
audit_repo(repo, options)
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -34,11 +34,15 @@ module Bundler
|
|
|
34
34
|
success = false
|
|
35
35
|
$stderr.puts repo.project
|
|
36
36
|
in_temp_dir do
|
|
37
|
-
if download_file(repo, "Gemfile.lock"
|
|
38
|
-
if options[:ignore_gems] && repo.gem?
|
|
37
|
+
if download_file(repo, "Gemfile.lock")
|
|
38
|
+
if options[:ignore_gems] && repo.gem?
|
|
39
39
|
$stderr.puts "Ignored because it's a gem"
|
|
40
40
|
else
|
|
41
|
-
|
|
41
|
+
command = "bundle-audit"
|
|
42
|
+
if options[:ignore_cves] && options[:ignore_cves].any?
|
|
43
|
+
command << " --ignore #{options[:ignore_cves].map { |cve| "'CVE-#{cve}'" }.join(" ")}"
|
|
44
|
+
end
|
|
45
|
+
success = !sh(command)
|
|
42
46
|
end
|
|
43
47
|
else
|
|
44
48
|
$stderr.puts "No Gemfile.lock found"
|
|
@@ -7,18 +7,22 @@ module Bundler
|
|
|
7
7
|
class Repo
|
|
8
8
|
HOST = "https://api.github.com"
|
|
9
9
|
|
|
10
|
-
def initialize(data)
|
|
10
|
+
def initialize(data, token=nil)
|
|
11
11
|
@data = data
|
|
12
|
+
@token = token
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
def gem?
|
|
15
|
-
!!content("#{project}.gemspec"
|
|
15
|
+
def gem?
|
|
16
|
+
!!content("#{project}.gemspec")
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def url
|
|
19
20
|
api_url.sub("api.", "").sub("/repos", "")
|
|
20
21
|
end
|
|
21
|
-
|
|
22
|
+
|
|
23
|
+
def to_s
|
|
24
|
+
"#{url} -- #{last_commiter}"
|
|
25
|
+
end
|
|
22
26
|
|
|
23
27
|
def project
|
|
24
28
|
api_url.split("/").last
|
|
@@ -34,12 +38,13 @@ module Bundler
|
|
|
34
38
|
end
|
|
35
39
|
url = File.join(HOST, user, "repos")
|
|
36
40
|
|
|
37
|
-
|
|
41
|
+
token = options[:token]
|
|
42
|
+
download_all_pages(url, headers(token)).map { |data| Repo.new(data, token) }
|
|
38
43
|
end
|
|
39
44
|
|
|
40
|
-
def content(file
|
|
45
|
+
def content(file)
|
|
41
46
|
if private?
|
|
42
|
-
download_content_via_api(file
|
|
47
|
+
download_content_via_api(file)
|
|
43
48
|
else
|
|
44
49
|
download_content_via_raw(file)
|
|
45
50
|
end
|
|
@@ -51,13 +56,22 @@ module Bundler
|
|
|
51
56
|
@data["private"]
|
|
52
57
|
end
|
|
53
58
|
|
|
59
|
+
def last_commiter
|
|
60
|
+
response = call_api("commits/#{branch}")
|
|
61
|
+
committer = response["commit"]["committer"]
|
|
62
|
+
"#{committer["name"]} <#{committer["email"]}>"
|
|
63
|
+
end
|
|
64
|
+
|
|
54
65
|
private
|
|
55
66
|
|
|
56
67
|
def self.download_all_pages(url, headers)
|
|
57
68
|
results = []
|
|
58
69
|
page = 1
|
|
59
70
|
loop do
|
|
60
|
-
|
|
71
|
+
response = decorate_errors do
|
|
72
|
+
open("#{url}?page=#{page}", headers).read
|
|
73
|
+
end
|
|
74
|
+
result = JSON.parse(response)
|
|
61
75
|
if result.size == 0
|
|
62
76
|
break
|
|
63
77
|
else
|
|
@@ -81,13 +95,25 @@ module Bundler
|
|
|
81
95
|
end
|
|
82
96
|
|
|
83
97
|
# increases api rate limit
|
|
84
|
-
def download_content_via_api(file
|
|
85
|
-
|
|
86
|
-
content = open(url, self.class.headers(options.fetch(:token))).read
|
|
87
|
-
content = JSON.load(content)["content"]
|
|
98
|
+
def download_content_via_api(file)
|
|
99
|
+
content = call_api("contents/#{file}?branch=#{branch}")["content"]
|
|
88
100
|
Base64.decode64(content)
|
|
89
101
|
end
|
|
90
102
|
|
|
103
|
+
def call_api(path)
|
|
104
|
+
content = self.class.decorate_errors do
|
|
105
|
+
open(File.join(api_url, path), self.class.headers(@token)).read
|
|
106
|
+
end
|
|
107
|
+
JSON.load(content)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def self.decorate_errors
|
|
111
|
+
yield
|
|
112
|
+
rescue OpenURI::HTTPError => e
|
|
113
|
+
e.message << " -- body: " << e.io.read
|
|
114
|
+
raise e
|
|
115
|
+
end
|
|
116
|
+
|
|
91
117
|
# unlimited
|
|
92
118
|
def download_content_via_raw(file)
|
|
93
119
|
open(File.join(raw_url, branch, file)).read
|
|
@@ -28,6 +28,12 @@ describe Bundler::OrganizationAudit::Repo do
|
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
describe "#last_commiter" do
|
|
32
|
+
it "returns nice info" do
|
|
33
|
+
repo.last_commiter.should == "grosser <grosser.michael@gmail.com>"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
31
37
|
describe "#content" do
|
|
32
38
|
it "can download a public file" do
|
|
33
39
|
repo.content("Gemfile.lock").should include('rspec (2')
|
|
@@ -37,9 +43,9 @@ describe Bundler::OrganizationAudit::Repo do
|
|
|
37
43
|
it "can download a private file" do
|
|
38
44
|
url = "https://api.github.com/repos/#{config["organization"]}/#{config["expected_organization"]}"
|
|
39
45
|
repo = Bundler::OrganizationAudit::Repo.new(
|
|
40
|
-
"url" => url, "private" => true
|
|
46
|
+
{"url" => url, "private" => true}, config["token"]
|
|
41
47
|
)
|
|
42
|
-
content = repo.content("Gemfile.lock"
|
|
48
|
+
content = repo.content("Gemfile.lock")
|
|
43
49
|
content.should include('i18n (0.')
|
|
44
50
|
end
|
|
45
51
|
end
|
|
@@ -64,7 +64,7 @@ describe Bundler::OrganizationAudit do
|
|
|
64
64
|
|
|
65
65
|
it "only shows failed projects on stdout" do
|
|
66
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
|
|
67
|
+
result.should == "https://github.com/user-with-unpatched-apps/unpatched -- grosser <grosser.michael@gmail.com>\n"
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
it "ignores projects in --ignore" do
|
|
@@ -72,6 +72,11 @@ describe Bundler::OrganizationAudit do
|
|
|
72
72
|
result.should == ""
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
it "ignores CVEs via --ignore-cve" do
|
|
76
|
+
result = audit("--user user-with-unpatched-apps --ignore-cve 2013-0269 2>/dev/null", :keep_output => true)
|
|
77
|
+
result.should == ""
|
|
78
|
+
end
|
|
79
|
+
|
|
75
80
|
it "shows --version" do
|
|
76
81
|
audit("--version").should include(Bundler::OrganizationAudit::VERSION)
|
|
77
82
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bundler-organization_audit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -36,7 +36,7 @@ cert_chain:
|
|
|
36
36
|
VHNmKzZNYWVud0FNa0FnSGRzd0dzSnp0T25ObkJhM0YKeTBrQ1NXbUs2RCt4
|
|
37
37
|
L1NiZlM2cjdLZTA3TVJxemlKZEI5R3VFMSswY0lSdUZoOEVRK0xONkhYQ0tN
|
|
38
38
|
NXBvbi9HVQp5Y3dNWGZsMAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
|
|
39
|
-
date: 2013-
|
|
39
|
+
date: 2013-03-22 00:00:00.000000000 Z
|
|
40
40
|
dependencies:
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: json
|
|
@@ -92,7 +92,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
92
92
|
version: '0'
|
|
93
93
|
segments:
|
|
94
94
|
- 0
|
|
95
|
-
hash:
|
|
95
|
+
hash: -3883081273091798118
|
|
96
96
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
97
|
none: false
|
|
98
98
|
requirements:
|
|
@@ -101,10 +101,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
101
101
|
version: '0'
|
|
102
102
|
segments:
|
|
103
103
|
- 0
|
|
104
|
-
hash:
|
|
104
|
+
hash: -3883081273091798118
|
|
105
105
|
requirements: []
|
|
106
106
|
rubyforge_project:
|
|
107
|
-
rubygems_version: 1.8.
|
|
107
|
+
rubygems_version: 1.8.25
|
|
108
108
|
signing_key:
|
|
109
109
|
specification_version: 3
|
|
110
110
|
summary: Audit all Gemfiles of a user/organization on github for unpatched versions
|
metadata.gz.sig
CHANGED
|
Binary file
|