holepicker 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog.markdown +0 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +18 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.markdown +94 -0
- data/bin/holepicker +53 -0
- data/lib/holepicker/data/data.json +54 -0
- data/lib/holepicker/database.rb +22 -0
- data/lib/holepicker/gem.rb +21 -0
- data/lib/holepicker/offline_database.rb +14 -0
- data/lib/holepicker/online_database.rb +56 -0
- data/lib/holepicker/scanner.rb +128 -0
- data/lib/holepicker/utils.rb +11 -0
- data/lib/holepicker/validator.rb +0 -0
- data/lib/holepicker/version.rb +9 -0
- data/lib/holepicker/vulnerability.rb +66 -0
- data/lib/holepicker.rb +2 -0
- metadata +94 -0
data/Changelog.markdown
ADDED
File without changes
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Jakub Suder
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# HolePicker
|
2
|
+
|
3
|
+
HolePicker is a Ruby gem for quickly checking all your `Gemfile.lock` files for gem versions with known vulnerabilities.
|
4
|
+
|
5
|
+
|
6
|
+
## The story
|
7
|
+
|
8
|
+
The beginning of 2013 was a [really bad time](http://www.kalzumeus.com/2013/01/31/what-the-rails-security-issue-means-for-your-startup/) for the Ruby community. In the first few weeks of the year at least 7 serious security issues were found, and Rails had to be updated 4 times so far because of this. It's probably not the end. It's hard to keep track of all the issues and remember which gem versions are OK and which aren't, especially if you have several older and newer Ruby or Rails projects to maintain. So I wrote this tool in order to help with identifying which gems in your projects' gemfiles need to be updated.
|
9
|
+
|
10
|
+
|
11
|
+
## Details
|
12
|
+
|
13
|
+
The idea is that there is a [JSON file](https://github.com/psionides/holepicker/blob/master/lib/holepicker/data/data.json)\* stored in this repository that lists all the recent security-related updates to popular gems: date of the release, URL of the announcement, and a list of affected gems and updated versions. HolePicker provides a command line tool that **downloads the latest data file from GitHub every time**, scans your `Gemfile.lock` files and checks if they contain vulnerable gem versions.
|
14
|
+
|
15
|
+
The reason I've done it this way is to make it easier to run the checks against the very latest version of the vulnerability list. It's kind of important to be sure that you haven't missed any last minute updates, and it would be annoying to have to check for new gem versions every time you want to run the tool (and you might not even remember to do that).
|
16
|
+
|
17
|
+
If for some reason you don't want to download the JSON file every time, you can use the [`-o` option](#full-option-list). Also, the JSON file specifies the minimum compatible gem version that it can work with, so if new kind of information is added to the file that requires the gem to be updated in order to parse it, the gem will let you know.
|
18
|
+
|
19
|
+
Of course the whole system still relies on me manually adding entries to the JSON file and pushing it to GitHub. I'll try to do that quickly, my trusty [@rails_bot](https://github.com/psionides/rails-retweeter-bot) notifies me pretty quickly when something really bad is happening. If for some reason I don't update the list in time, by all means please send me a pull request.
|
20
|
+
|
21
|
+
(\*) YAML obviously wouldn't be appropriate, if you know what I mean.
|
22
|
+
|
23
|
+
|
24
|
+
## Running the tool
|
25
|
+
|
26
|
+
To install the tool, just run:
|
27
|
+
|
28
|
+
gem install holepicker
|
29
|
+
|
30
|
+
There are two main modes of operation:
|
31
|
+
|
32
|
+
### Scanning projects directly
|
33
|
+
|
34
|
+
This can be used to scan project directories on your development machine:
|
35
|
+
|
36
|
+
holepicker ~/Projects
|
37
|
+
|
38
|
+
You can also scan all apps deployed to a production or demo server; in this case, it's recommended to use the `-c` (`--current`) option in order to skip the old releases in `releases` directories and only scan the `current` directories (I'm assuming you use Capistrano for deployment, because who doesn't?).
|
39
|
+
|
40
|
+
holepicker -c /var/www
|
41
|
+
|
42
|
+
### Scanning Nginx/Apache config directory
|
43
|
+
|
44
|
+
You might have a lot of random apps deployed in the `/var/www` directory, but only some of them currently enabled in the Nginx config files. In this case, you might want to only check the apps that are actually running. To do that, use the `-f` (`--follow-roots`) option and point HolePicker to your HTTP server's config directory. It will find all the `root` or `DocumentRoot` directives and follow the paths to find the gemfiles of enabled apps.
|
45
|
+
|
46
|
+
holepicker -f /etc/nginx/sites-enabled
|
47
|
+
|
48
|
+
|
49
|
+
## Results
|
50
|
+
|
51
|
+
This is more or less what you will get if you run HolePicker in a directory with some old Rails projects:
|
52
|
+
|
53
|
+
![screenshot](http://f.cl.ly/items/1l3C2c2s0r1k3v033B34/Screen%20Shot%202013-02-16%20at%2001.43.39.png)
|
54
|
+
|
55
|
+
|
56
|
+
## Full option list
|
57
|
+
|
58
|
+
`-a`, `--all`
|
59
|
+
|
60
|
+
By default, HolePicker will skip directories like `.git`, `tmp`, `cached-copy` etc. when searching for gemfiles. This option turns this feature off.
|
61
|
+
|
62
|
+
`-c`, `--current`
|
63
|
+
|
64
|
+
Look only for gemfiles that are located directly in a `current` directory.
|
65
|
+
|
66
|
+
`-f`, `--follow-roots`
|
67
|
+
|
68
|
+
Look for `root`/`DocumentRoot` directives in config files at given locations instead of gemfiles directly.
|
69
|
+
|
70
|
+
`-i`, `--ignore gem1,gem2,gem3`
|
71
|
+
|
72
|
+
Ignore the gems passed in the parameter.
|
73
|
+
|
74
|
+
`-o`, `--offline`
|
75
|
+
|
76
|
+
Use an offline copy of the data file - useful if you really need to run the tool, but the network or GitHub is down.
|
77
|
+
|
78
|
+
|
79
|
+
## Similar projects
|
80
|
+
|
81
|
+
The [bundler-audit](https://github.com/postmodern/bundler-audit) project that was also created this week has a similar purpose, but it only uses an offline issue list and it only scans the current project.
|
82
|
+
|
83
|
+
The [gemcanary](https://gemcanary.com/) project might be something similar, but it hasn't been released yet (as of 16.02).
|
84
|
+
|
85
|
+
It might make sense to agree on a shared list of vulnerabilities in the future that these and other projects could share - no point having the same information in a few different places maintained by a few people in parallel.
|
86
|
+
|
87
|
+
|
88
|
+
## Credits & contributing
|
89
|
+
|
90
|
+
Created by [Jakub Suder](http://psionides.eu), licensed under MIT License.
|
91
|
+
|
92
|
+
Any feedback and help is welcome, if you have an idea how to improve this tool, let me know or send me an issue or a pull request.
|
93
|
+
|
94
|
+
And BTW, big thanks to all the smart people that find and fix all these issues - I hope you won't find much more, but please keep looking.
|
data/bin/holepicker
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path('../../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'holepicker'
|
7
|
+
require 'optparse'
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
|
11
|
+
OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: #{File.basename($0)} [options] paths..."
|
13
|
+
|
14
|
+
opts.on("-a", "--all", "Don't skip directories like .git or tmp while looking for gemfiles") do
|
15
|
+
options[:dont_skip] = true
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on("-c", "--current", "Look for gemfiles only in 'current' directories") do
|
19
|
+
options[:current] = true
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on("-f", "--follow-roots", "Follow root/DocumentRoot paths in Nginx/Apache configs to find gemfiles") do
|
23
|
+
options[:follow_roots] = true
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on("-i", "--ignore gem1,gem2,gem3", Array, "Ignore given gems") do |names|
|
27
|
+
options[:ignored_gems] = names
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on("-o", "--offline", "Use an offline copy of the data.json file") do
|
31
|
+
options[:offline] = true
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("-h", "--help", "Display this help") do
|
35
|
+
puts opts
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("-v", "--version", "Print gem version") do
|
40
|
+
puts HolePicker::VERSION
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.parse!
|
45
|
+
end
|
46
|
+
|
47
|
+
if ARGV.empty?
|
48
|
+
abort "Please choose at least one directory to scan for gemfiles."
|
49
|
+
end
|
50
|
+
|
51
|
+
success = HolePicker::Scanner.new(ARGV, options).scan
|
52
|
+
|
53
|
+
exit(success ? 0 : 1)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
{
|
2
|
+
"min_version": "0.1",
|
3
|
+
"vulnerabilities": [
|
4
|
+
{
|
5
|
+
"gems": {
|
6
|
+
"rails": ["3.2.12", "3.1.11", "2.3.17"]
|
7
|
+
},
|
8
|
+
"url": "http://weblog.rubyonrails.org/2013/2/11/SEC-ANN-Rails-3-2-12-3-1-11-and-2-3-17-have-been-released/",
|
9
|
+
"date": "2013-02-11T18:40Z"
|
10
|
+
},
|
11
|
+
{
|
12
|
+
"gems": {
|
13
|
+
"json": ["1.7.7", "1.6.8", "1.5.5"]
|
14
|
+
},
|
15
|
+
"url": "https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-security/4_YvCpLzL58",
|
16
|
+
"date": "2013-02-11T18:26Z"
|
17
|
+
},
|
18
|
+
{
|
19
|
+
"gems": {
|
20
|
+
"rack": ["1.5.2", "1.4.5", "1.3.10", "1.2.8", "1.1.6"]
|
21
|
+
},
|
22
|
+
"url": "http://rack.github.com/",
|
23
|
+
"date": "2013-02-08T03:14Z"
|
24
|
+
},
|
25
|
+
{
|
26
|
+
"gems": {
|
27
|
+
"rails": ["3.0.20", "2.3.16"]
|
28
|
+
},
|
29
|
+
"url": "http://weblog.rubyonrails.org/2013/1/28/Rails-3-0-20-and-2-3-16-have-been-released/",
|
30
|
+
"date": "2013-01-28T21:08Z"
|
31
|
+
},
|
32
|
+
{
|
33
|
+
"gems": {
|
34
|
+
"devise": ["2.2.3", "2.1.3", "2.0.5", "1.5.4"]
|
35
|
+
},
|
36
|
+
"url": "http://blog.plataformatec.com.br/2013/01/security-announcement-devise-v2-2-3-v2-1-3-v2-0-5-and-v1-5-3-released/",
|
37
|
+
"date": "2013-01-28T15:03Z"
|
38
|
+
},
|
39
|
+
{
|
40
|
+
"gems": {
|
41
|
+
"rails": ["3.2.11", "3.1.10", "3.0.19", "2.3.15"]
|
42
|
+
},
|
43
|
+
"url": "http://weblog.rubyonrails.org/2013/1/8/Rails-3-2-11-3-1-10-3-0-19-and-2-3-15-have-been-released/",
|
44
|
+
"date": "2013-01-08T20:26Z"
|
45
|
+
},
|
46
|
+
{
|
47
|
+
"gems": {
|
48
|
+
"rails": ["3.2.10", "3.1.9", "3.0.18", "2.3.15"]
|
49
|
+
},
|
50
|
+
"url": "http://weblog.rubyonrails.org/2013/1/2/Rails-3-2-10--3-1-9--and-3-0-18-have-been-released/",
|
51
|
+
"date": "2013-01-02T21:39Z"
|
52
|
+
}
|
53
|
+
]
|
54
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'holepicker/vulnerability'
|
2
|
+
require 'json'
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
module HolePicker
|
6
|
+
class Database
|
7
|
+
attr_reader :vulnerabilities
|
8
|
+
|
9
|
+
def self.load_from_json_file(data)
|
10
|
+
new(JSON.parse(data))
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(json)
|
14
|
+
@vulnerabilities = json['vulnerabilities'].reverse.map { |v| Vulnerability.new(v) }
|
15
|
+
@min_version = ::Gem::Version.new(json['min_version'])
|
16
|
+
end
|
17
|
+
|
18
|
+
def compatible?
|
19
|
+
HolePicker.version >= @min_version
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
module HolePicker
|
4
|
+
class Gem
|
5
|
+
GEM_LINE_PATTERN = /([\w\-]+) \(([^)]+)\)/
|
6
|
+
|
7
|
+
attr_reader :name, :version
|
8
|
+
|
9
|
+
def initialize(line)
|
10
|
+
result = line.match(GEM_LINE_PATTERN)
|
11
|
+
raise "Invalid gem format: #{line}" unless result
|
12
|
+
|
13
|
+
@name = result[1]
|
14
|
+
@version = ::Gem::Version.new(result[2])
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"#{name} (#{version})"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'holepicker/database'
|
2
|
+
|
3
|
+
module HolePicker
|
4
|
+
class OfflineDatabase < Database
|
5
|
+
OFFLINE_JSON_FILE = File.expand_path('../data/data.json', __FILE__)
|
6
|
+
|
7
|
+
def self.load
|
8
|
+
load_from_json_file(File.read(OFFLINE_JSON_FILE))
|
9
|
+
rescue Exception => e
|
10
|
+
puts "Can't load local data file: #{e}"
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'holepicker/database'
|
2
|
+
require 'holepicker/utils'
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
module HolePicker
|
6
|
+
class OnlineDatabase < Database
|
7
|
+
# TODO temporary link
|
8
|
+
URL='http://pastie.org/pastes/6183429/download?key=qryhowarb9i7hoqqyvy0q'
|
9
|
+
|
10
|
+
def self.load
|
11
|
+
puts "Fetching list of vulnerabilities..."
|
12
|
+
|
13
|
+
load_from_json_file(http_get(URL)).tap do |db|
|
14
|
+
db.check_compatibility
|
15
|
+
db.report_new_vulnerabilities
|
16
|
+
end
|
17
|
+
rescue SystemExit
|
18
|
+
raise
|
19
|
+
rescue Exception => e
|
20
|
+
puts "Can't download latest data file: #{e}"
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.http_get(url)
|
25
|
+
uri = URI(url)
|
26
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
27
|
+
http.use_ssl = url.start_with?('https')
|
28
|
+
|
29
|
+
response = http.get(uri.request_uri)
|
30
|
+
response.body
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_compatibility
|
34
|
+
unless compatible?
|
35
|
+
puts "You need to upgrade holepicker to version #{@min_version} or later."
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def report_new_vulnerabilities
|
41
|
+
new_vulnerabilities = @vulnerabilities.select(&:recent?)
|
42
|
+
count = new_vulnerabilities.length
|
43
|
+
|
44
|
+
if count > 0
|
45
|
+
puts "#{count} new #{Utils.pluralize(count, 'vulnerability')} found in the last " +
|
46
|
+
"#{Vulnerability::NEW_VULNERABILITY_DAYS} days:"
|
47
|
+
|
48
|
+
new_vulnerabilities.each do |v|
|
49
|
+
puts "#{v.day} (#{v.gem_names.join(', ')}): #{v.url}"
|
50
|
+
end
|
51
|
+
|
52
|
+
puts
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'holepicker/gem'
|
2
|
+
require 'holepicker/offline_database'
|
3
|
+
require 'holepicker/online_database'
|
4
|
+
require 'holepicker/utils'
|
5
|
+
require 'rainbow'
|
6
|
+
require 'set'
|
7
|
+
|
8
|
+
module HolePicker
|
9
|
+
class Scanner
|
10
|
+
SKIPPED_DIRECTORIES = ["-name cached-copy", "-path '*/bundle/ruby'", "-name tmp", "-name '.*'"]
|
11
|
+
ROOT_LINE_PATTERN = %r{\b(?:root|DocumentRoot)\s+(.*)/public\b}
|
12
|
+
GEMFILE_GEM_PATTERN = %r(^ {4}[^ ])
|
13
|
+
|
14
|
+
def initialize(paths, options = {})
|
15
|
+
@paths = paths.is_a?(Array) ? paths : [paths]
|
16
|
+
|
17
|
+
@database = options[:offline] ? OfflineDatabase.load : OnlineDatabase.load
|
18
|
+
|
19
|
+
@ignored = options[:ignored_gems] || []
|
20
|
+
@skip = !options[:dont_skip]
|
21
|
+
@current = options[:current]
|
22
|
+
@roots = options[:follow_roots]
|
23
|
+
end
|
24
|
+
|
25
|
+
def scan
|
26
|
+
puts "Looking for gemfiles..."
|
27
|
+
|
28
|
+
@found_vulnerabilities = Set.new
|
29
|
+
@matched_gemfiles = 0
|
30
|
+
@matched_gems = 0
|
31
|
+
|
32
|
+
@paths.each { |p| scan_path(p) }
|
33
|
+
|
34
|
+
print_report
|
35
|
+
|
36
|
+
@matched_gems == 0
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def vulnerabilities_for_gem(gem)
|
43
|
+
@database.vulnerabilities.select { |v| v.gem_vulnerable?(gem) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_gemfiles_in_path(path)
|
47
|
+
skips = SKIPPED_DIRECTORIES.join(" -or ")
|
48
|
+
gemfiles = @current ? "-path '*/current/Gemfile.lock'" : "-name 'Gemfile.lock'"
|
49
|
+
|
50
|
+
command = if @skip
|
51
|
+
"find -L #{path} \\( #{skips} \\) -prune -or #{gemfiles} -print"
|
52
|
+
else
|
53
|
+
"find -L #{path} #{gemfiles}"
|
54
|
+
end
|
55
|
+
|
56
|
+
run_and_read_lines(command)
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_gemfiles_in_configs(path)
|
60
|
+
configs = run_and_read_lines("find -L #{path} -type f -or -type l")
|
61
|
+
configs = select_existing(configs)
|
62
|
+
|
63
|
+
directories = configs.map { |f| File.read(f).scan(ROOT_LINE_PATTERN) }
|
64
|
+
gemfiles = directories.flatten.map { |dir| "#{dir}/Gemfile.lock" }
|
65
|
+
|
66
|
+
select_existing(gemfiles)
|
67
|
+
end
|
68
|
+
|
69
|
+
def read_gemfile(path)
|
70
|
+
File.readlines(path).select { |l| l =~ GEMFILE_GEM_PATTERN }.map { |l| Gem.new(l) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def scan_path(path)
|
74
|
+
gemfiles = @roots ? find_gemfiles_in_configs(path) : find_gemfiles_in_path(path)
|
75
|
+
gemfiles.each { |f| scan_gemfile(f) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def scan_gemfile(path)
|
79
|
+
print "#{path}: "
|
80
|
+
|
81
|
+
gems = read_gemfile(path)
|
82
|
+
gems.delete_if { |g| @ignored.include?(g.name) }
|
83
|
+
|
84
|
+
vulnerable_gems = gems.map { |g| [g, vulnerabilities_for_gem(g)] }
|
85
|
+
vulnerable_gems.delete_if { |g, v| v.empty? }
|
86
|
+
|
87
|
+
count = vulnerable_gems.length
|
88
|
+
|
89
|
+
if count == 0
|
90
|
+
puts "OK"
|
91
|
+
else
|
92
|
+
puts "#{count} vulnerable #{Utils.pluralize(count, 'gem')} found!".color(:red)
|
93
|
+
|
94
|
+
vulnerable_gems.each do |gem, vulnerabilities|
|
95
|
+
puts "- #{gem} [#{vulnerabilities.map(&:tag).join(',')}]"
|
96
|
+
|
97
|
+
@found_vulnerabilities.merge(vulnerabilities)
|
98
|
+
@matched_gems += 1
|
99
|
+
end
|
100
|
+
|
101
|
+
@matched_gemfiles += 1
|
102
|
+
|
103
|
+
puts
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def print_report
|
108
|
+
if @matched_gemfiles == 0
|
109
|
+
puts "No vulnerabilities found."
|
110
|
+
else
|
111
|
+
puts ("#{@matched_gems} vulnerable #{Utils.pluralize(@matched_gems, 'gem')} found in " +
|
112
|
+
"#{@matched_gemfiles} #{Utils.pluralize(@matched_gemfiles, 'gemfile')}!").color(:red) + "\n\n"
|
113
|
+
|
114
|
+
@found_vulnerabilities.sort_by(&:id).each do |v|
|
115
|
+
puts "[#{v.tag}] #{v.day}: #{v.url}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def select_existing(files)
|
121
|
+
files.select { |f| File.exist?(f) }
|
122
|
+
end
|
123
|
+
|
124
|
+
def run_and_read_lines(command)
|
125
|
+
%x(#{command}).lines.map(&:strip)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
File without changes
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module HolePicker
|
5
|
+
class Vulnerability
|
6
|
+
NEW_VULNERABILITY_DAYS = 7
|
7
|
+
NEW_VULNERABILITY_TIME = NEW_VULNERABILITY_DAYS * 86400
|
8
|
+
|
9
|
+
attr_reader :id, :date, :url, :gems
|
10
|
+
|
11
|
+
def self.next_id
|
12
|
+
@@count ||= 0
|
13
|
+
@@count += 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(json)
|
17
|
+
@gems = {}
|
18
|
+
|
19
|
+
json['gems'].each do |name, versions|
|
20
|
+
@gems[name] = versions.map { |v| ::Gem::Version.new(v) }
|
21
|
+
end
|
22
|
+
|
23
|
+
@id = self.class.next_id
|
24
|
+
@url = json['url']
|
25
|
+
@date = Time.parse(json['date'])
|
26
|
+
end
|
27
|
+
|
28
|
+
def day
|
29
|
+
@date.strftime("%Y-%m-%d")
|
30
|
+
end
|
31
|
+
|
32
|
+
def recent?
|
33
|
+
date > Time.now - NEW_VULNERABILITY_TIME
|
34
|
+
end
|
35
|
+
|
36
|
+
def tag
|
37
|
+
"##{@id}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def gem_names
|
41
|
+
@gems.keys
|
42
|
+
end
|
43
|
+
|
44
|
+
def gem_vulnerable?(gem)
|
45
|
+
!gem_safe?(gem)
|
46
|
+
end
|
47
|
+
|
48
|
+
def gem_safe?(gem)
|
49
|
+
fixes = @gems[gem.name]
|
50
|
+
!fixes || fixes.any? { |fix| fix_included?(fix, gem) } || fixes.all? { |fix| gem.version > fix }
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def fix_included?(fix, gem)
|
57
|
+
gem.version == fix || (gem.version > fix && same_level?(fix, gem))
|
58
|
+
end
|
59
|
+
|
60
|
+
def same_level?(fix, gem)
|
61
|
+
segments_count = fix.segments.length
|
62
|
+
|
63
|
+
fix.segments[0...segments_count-1] == gem.version.segments[0...segments_count-1]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/holepicker.rb
ADDED
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: holepicker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jakub Suder
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.7.7
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.7.7
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rainbow
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.1.4
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.1.4
|
46
|
+
description:
|
47
|
+
email: jakub.suder@gmail.com
|
48
|
+
executables:
|
49
|
+
- holepicker
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- MIT-LICENSE.txt
|
54
|
+
- README.markdown
|
55
|
+
- Changelog.markdown
|
56
|
+
- Gemfile
|
57
|
+
- Gemfile.lock
|
58
|
+
- lib/holepicker/data/data.json
|
59
|
+
- lib/holepicker/database.rb
|
60
|
+
- lib/holepicker/gem.rb
|
61
|
+
- lib/holepicker/offline_database.rb
|
62
|
+
- lib/holepicker/online_database.rb
|
63
|
+
- lib/holepicker/scanner.rb
|
64
|
+
- lib/holepicker/utils.rb
|
65
|
+
- lib/holepicker/validator.rb
|
66
|
+
- lib/holepicker/version.rb
|
67
|
+
- lib/holepicker/vulnerability.rb
|
68
|
+
- lib/holepicker.rb
|
69
|
+
- bin/holepicker
|
70
|
+
homepage: http://github.com/psionides/holepicker
|
71
|
+
licenses: []
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 1.8.24
|
91
|
+
signing_key:
|
92
|
+
specification_version: 3
|
93
|
+
summary: A tool for checking gem versions in Gemfile.lock files for known vulnerabilities
|
94
|
+
test_files: []
|