gem_checks 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/gem_checks +37 -0
- data/lib/gem_checks/dependency_parser.rb +17 -0
- data/lib/gem_checks/gem_collection.rb +37 -0
- data/lib/gem_checks/gemnasium_client.rb +52 -0
- data/lib/gem_checks/simple_logger.rb +47 -0
- data/lib/gem_checks/version.rb +3 -0
- data/lib/gem_checks/vulnerable_version_check.rb +17 -0
- data/lib/gem_checks.rb +33 -0
- data/spec/dependency_parser_spec.rb +11 -0
- data/spec/gem_checks_spec.rb +33 -0
- data/spec/gem_collection_spec.rb +33 -0
- data/spec/gemnasium_client_spec.rb +20 -0
- data/spec/simple_logger_spec.rb +20 -0
- data/spec/vulnerable_version_check_spec.rb +26 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 63d6aced5b4e03fb47dee58279f283b68dc66b6e
|
4
|
+
data.tar.gz: 8576824fd844c3f74dced5c3b4e0d0790278eda7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6161980f198a53d83f19e959559e928bab9113f26414102203b629693e12a73d66312314682da45fc467b05494bd1ed705431e71e3f4d3e4f57c0a34e894ff92
|
7
|
+
data.tar.gz: 62683b7e6aeb99cc777ab0c2eec73494b20ccbbfe722ab491288a7d2733a757aea6ddd702092c5c3f651bf0591163680a16ce20c5b37ac54dde6200ccaa9a7b9
|
data/bin/gem_checks
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.expand_path('../../lib', __FILE__)
|
4
|
+
require 'gem_checks'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
ARGV.push('-h') if ARGV.empty?
|
8
|
+
|
9
|
+
options = {}
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
opts.banner = 'Usage: gem_checks /path/to/Gemfile.lock [options]'
|
12
|
+
|
13
|
+
opts.on('-h', '--help', 'Prints this help') do
|
14
|
+
puts opts
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
opts.on('-v', '--verbose', 'Prints verbose logging') do |v|
|
18
|
+
options[:verbose] = v
|
19
|
+
end
|
20
|
+
end.parse!
|
21
|
+
|
22
|
+
# Get file path from the ARGs list
|
23
|
+
file_path = ARGV.first
|
24
|
+
lockfile = File.new(file_path)
|
25
|
+
|
26
|
+
# Setup the client logger
|
27
|
+
client = GemnasiumClient.new
|
28
|
+
if options[:verbose]
|
29
|
+
logger = Logger.new(STDOUT)
|
30
|
+
client.set_logger(logger)
|
31
|
+
end
|
32
|
+
vulnerable_version_check = VulnerableVersionCheck.new(gemnasium_client: client)
|
33
|
+
|
34
|
+
# Evaluate the lockfile
|
35
|
+
GemChecks.new(lockfile: lockfile,
|
36
|
+
vulnerable_version_check: vulnerable_version_check)
|
37
|
+
.display_vulnerable_gems
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class DependencyParser
|
2
|
+
def parse(lockfile)
|
3
|
+
lockfile.map { |line| parse_line(line) }.compact
|
4
|
+
end
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def parse_line(line)
|
9
|
+
(match = line.match(GEM_PARSER)) ? parse_gem(match) : nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse_gem(gem_str)
|
13
|
+
{ gem_name: gem_str[:gem_name], version: gem_str[:version] }
|
14
|
+
end
|
15
|
+
|
16
|
+
GEM_PARSER = /(?<gem_name>([a-zA-Z\-_0-9.])+)\s?\((?<version>(\d+\.?)+)\)/
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
class GemCollection < SimpleDelegator
|
3
|
+
def initialize(collection)
|
4
|
+
@collection = collection
|
5
|
+
super(collection)
|
6
|
+
end
|
7
|
+
|
8
|
+
def display_vulnerable
|
9
|
+
collection.each do |gem|
|
10
|
+
puts format_gem_message(gem)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.wrap(collection)
|
15
|
+
collection.empty? ? EmptyGemCollection.new : new(collection)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :collection
|
21
|
+
|
22
|
+
def format_gem_message(gem)
|
23
|
+
"#{gem[:gem_name]}, version: #{gem[:version]}"
|
24
|
+
end
|
25
|
+
|
26
|
+
class EmptyGemCollection
|
27
|
+
MESSAGE = "\nYou have no vulnerable gems in your project"
|
28
|
+
|
29
|
+
def empty?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def display_vulnerable
|
34
|
+
puts MESSAGE
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'gem_checks/simple_logger'
|
2
|
+
require 'logger'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'open-uri'
|
5
|
+
|
6
|
+
class GemnasiumClient
|
7
|
+
def initialize(logger: SimpleLogger.new)
|
8
|
+
@logger = set_log_level(logger)
|
9
|
+
end
|
10
|
+
|
11
|
+
def vulnerable?(gem_name:, version:)
|
12
|
+
uri = client_url(gem_name, version)
|
13
|
+
log_analysis(gem_name, version)
|
14
|
+
query_gemnasium(uri)
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_logger(logger)
|
18
|
+
@logger = set_log_level(logger)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def log_analysis(gem_name, version)
|
24
|
+
@logger.info("Analysing: #{gem_name}, version: #{version}")
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_log_level(logger)
|
28
|
+
level = ENV.fetch('LOG_LEVEL') { Logger::INFO }.to_i
|
29
|
+
logger.tap do |logger|
|
30
|
+
logger.level = level
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def gem_vulnerable?(doc)
|
35
|
+
doc.css('div.accordion.advisory.affected').count >= 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def query_gemnasium(uri)
|
39
|
+
begin
|
40
|
+
open(uri) do |gemnasium_raw|
|
41
|
+
doc = Nokogiri::HTML(gemnasium_raw)
|
42
|
+
gem_vulnerable?(doc)
|
43
|
+
end
|
44
|
+
rescue OpenURI::HTTPError
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def client_url(gem_name, version)
|
50
|
+
"https://gemnasium.com/gems/#{gem_name}/versions/#{version}"
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class SimpleLogger
|
2
|
+
LOG_LEVELS = [:debug, :info, :warn, :error, :fatal, :unknown]
|
3
|
+
|
4
|
+
def initialize(default_level: :debug)
|
5
|
+
@level = LOG_LEVELS.index(default_level)
|
6
|
+
end
|
7
|
+
|
8
|
+
def debug(*)
|
9
|
+
log_if_level_valid(:debug)
|
10
|
+
end
|
11
|
+
|
12
|
+
def info(*)
|
13
|
+
log_if_level_valid(:info)
|
14
|
+
end
|
15
|
+
|
16
|
+
def warn(*)
|
17
|
+
log_if_level_valid(:warn)
|
18
|
+
end
|
19
|
+
|
20
|
+
def error(*)
|
21
|
+
log_level_if_valid(:error)
|
22
|
+
end
|
23
|
+
|
24
|
+
def fatal(*)
|
25
|
+
log_level_if_valid(:fatal)
|
26
|
+
end
|
27
|
+
|
28
|
+
def unknown(*)
|
29
|
+
log_level_if_valid(:unknown)
|
30
|
+
end
|
31
|
+
|
32
|
+
def level=(level)
|
33
|
+
@level = level
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def log_if_level_valid(level_sym)
|
39
|
+
if @level <= LOG_LEVELS.index(level_sym)
|
40
|
+
log
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def log(*)
|
45
|
+
print '.'
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'gem_checks/gemnasium_client'
|
2
|
+
|
3
|
+
class VulnerableVersionCheck
|
4
|
+
def initialize(gemnasium_client: GemnasiumClient.new)
|
5
|
+
@gemnasium_client = gemnasium_client
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(deps)
|
9
|
+
deps.select { |gem| vulnerable?(gem) }
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def vulnerable?(gem)
|
15
|
+
@gemnasium_client.vulnerable?(gem)
|
16
|
+
end
|
17
|
+
end
|
data/lib/gem_checks.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'gem_checks/dependency_parser'
|
2
|
+
require 'gem_checks/vulnerable_version_check'
|
3
|
+
require 'gem_checks/gem_collection'
|
4
|
+
|
5
|
+
class GemChecks
|
6
|
+
def initialize(dependency_parser: DependencyParser.new,
|
7
|
+
vulnerable_version_check: VulnerableVersionCheck.new,
|
8
|
+
lockfile:)
|
9
|
+
@dependency_parser = dependency_parser
|
10
|
+
@vulnerable_version_check = vulnerable_version_check
|
11
|
+
@lockfile = lockfile
|
12
|
+
end
|
13
|
+
|
14
|
+
def display_vulnerable_gems
|
15
|
+
GemCollection.wrap(evaluate).display_vulnerable
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def evaluate
|
21
|
+
dependencies = parse_dependencies
|
22
|
+
list_vulnerable(dependencies)
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_dependencies
|
26
|
+
@dependency_parser.parse(@lockfile)
|
27
|
+
end
|
28
|
+
|
29
|
+
def list_vulnerable(deps)
|
30
|
+
@vulnerable_version_check.call(deps)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'gem_checks/dependency_parser'
|
3
|
+
|
4
|
+
RSpec.describe DependencyParser do
|
5
|
+
describe '#parse' do
|
6
|
+
it 'parses the file into a list' do
|
7
|
+
lockfile = open_safe_file
|
8
|
+
expect(subject.parse(lockfile)).to eq(safe_parsed_results)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'gem_checks'
|
3
|
+
|
4
|
+
RSpec.describe GemChecks do
|
5
|
+
class MockVulnerableVersionCheck
|
6
|
+
def call(deps)
|
7
|
+
return [] unless deps.include?(vulnerable_gem)
|
8
|
+
[vulnerable_gem]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#display_vulnerable_gems' do
|
13
|
+
context 'with no vulnerabilities in the lockfile' do
|
14
|
+
|
15
|
+
it 'displays the empty collection message' do
|
16
|
+
lockfile = open_safe_file
|
17
|
+
subject = GemChecks.new(vulnerable_version_check: MockVulnerableVersionCheck.new,
|
18
|
+
lockfile: lockfile)
|
19
|
+
expect{ subject.display_vulnerable_gems }.to output(empty_collection_message).to_stdout
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'with one vulnerability in the lockfile' do
|
24
|
+
|
25
|
+
it 'displays the vulnerable gem' do
|
26
|
+
lockfile = open_unsafe_one_vuln_file
|
27
|
+
subject = GemChecks.new(vulnerable_version_check: MockVulnerableVersionCheck.new,
|
28
|
+
lockfile: lockfile)
|
29
|
+
expect{ subject.display_vulnerable_gems }.to output(format_gem_message(vulnerable_gem)).to_stdout
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'gem_checks/gem_collection'
|
3
|
+
|
4
|
+
RSpec.describe GemCollection do
|
5
|
+
describe '.wrap' do
|
6
|
+
it 'delegates to the underlying collection' do
|
7
|
+
expect(GemCollection.wrap([])).to be_empty
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#display_vulnerable' do
|
12
|
+
context 'when the collection is empty' do
|
13
|
+
it 'displays a message that no gems are vulnerable' do
|
14
|
+
assert_wrapped_collection_message(collection: [],
|
15
|
+
message: empty_collection_message)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when the collection is not empty' do
|
20
|
+
it 'displays the vulnerable gems' do
|
21
|
+
assert_wrapped_collection_message(collection: [vulnerable_gem],
|
22
|
+
message: format_gem_message(vulnerable_gem))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def assert_wrapped_collection_message(collection:, message:)
|
30
|
+
collection = GemCollection.wrap(collection)
|
31
|
+
expect{ collection.display_vulnerable }.to output(message).to_stdout
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'gem_checks/gemnasium_client'
|
3
|
+
|
4
|
+
RSpec.describe GemnasiumClient do
|
5
|
+
describe '#vulnerable?', speed: 'slow' do
|
6
|
+
context 'when the gem is not vulnerable' do
|
7
|
+
it 'returns false' do
|
8
|
+
gem = safe_parsed_results.last
|
9
|
+
expect(subject.vulnerable?(gem)).to be_falsey
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when the gem is vulnerable' do
|
14
|
+
it 'returns true' do
|
15
|
+
gem = vulnerable_parsed_results.last
|
16
|
+
expect(subject.vulnerable?(gem)).to be_truthy
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'gem_checks/simple_logger'
|
3
|
+
|
4
|
+
RSpec.describe SimpleLogger do
|
5
|
+
describe '#info' do
|
6
|
+
context 'when the log level is set to info' do
|
7
|
+
it 'prints a "."' do
|
8
|
+
logger = SimpleLogger.new
|
9
|
+
expect { logger.info }.to output('.').to_stdout
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when the log level is set to warn' do
|
14
|
+
it 'does not output anything' do
|
15
|
+
logger = SimpleLogger.new(default_level: :warn)
|
16
|
+
expect { logger.info }.not_to output('.').to_stdout
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'gem_checks/vulnerable_version_check'
|
3
|
+
|
4
|
+
RSpec.describe VulnerableVersionCheck do
|
5
|
+
class MockGemnasiumClient
|
6
|
+
def vulnerable?(gem)
|
7
|
+
gem == vulnerable_gem
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#call' do
|
12
|
+
context 'when there are no vulnerable gems' do
|
13
|
+
it 'returns an empty collection' do
|
14
|
+
subject = VulnerableVersionCheck.new(gemnasium_client: MockGemnasiumClient.new)
|
15
|
+
expect(subject.call(safe_parsed_results)).to be_empty
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when there are vulnerable gems' do
|
20
|
+
it 'returns a collection of vulnerable gems' do
|
21
|
+
subject = VulnerableVersionCheck.new(gemnasium_client: MockGemnasiumClient.new)
|
22
|
+
expect(subject.call(vulnerable_parsed_results)).to include(vulnerable_gem)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gem_checks
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jacob Chae
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-04-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.4'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.4.0
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.4'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.4.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '11.1'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 11.1.0
|
43
|
+
type: :development
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '11.1'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 11.1.0
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: nokogiri
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 1.6.7
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 1.6.7.2
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.6.7
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 1.6.7.2
|
73
|
+
description: Validate your Gemfile.lock against Gemnasium.
|
74
|
+
email: jacob.chae@mobiledefense.com
|
75
|
+
executables:
|
76
|
+
- gem_checks
|
77
|
+
extensions: []
|
78
|
+
extra_rdoc_files: []
|
79
|
+
files:
|
80
|
+
- bin/gem_checks
|
81
|
+
- lib/gem_checks.rb
|
82
|
+
- lib/gem_checks/dependency_parser.rb
|
83
|
+
- lib/gem_checks/gem_collection.rb
|
84
|
+
- lib/gem_checks/gemnasium_client.rb
|
85
|
+
- lib/gem_checks/simple_logger.rb
|
86
|
+
- lib/gem_checks/version.rb
|
87
|
+
- lib/gem_checks/vulnerable_version_check.rb
|
88
|
+
- spec/dependency_parser_spec.rb
|
89
|
+
- spec/gem_checks_spec.rb
|
90
|
+
- spec/gem_collection_spec.rb
|
91
|
+
- spec/gemnasium_client_spec.rb
|
92
|
+
- spec/simple_logger_spec.rb
|
93
|
+
- spec/vulnerable_version_check_spec.rb
|
94
|
+
homepage: https://github.com/mobiledefense/gem_checks
|
95
|
+
licenses:
|
96
|
+
- Apache
|
97
|
+
metadata: {}
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 2.1.0
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.4.5.1
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: Validate your Gemfile.lock against Gemnasium.
|
118
|
+
test_files:
|
119
|
+
- spec/dependency_parser_spec.rb
|
120
|
+
- spec/gem_checks_spec.rb
|
121
|
+
- spec/gem_collection_spec.rb
|
122
|
+
- spec/gemnasium_client_spec.rb
|
123
|
+
- spec/simple_logger_spec.rb
|
124
|
+
- spec/vulnerable_version_check_spec.rb
|