gem_checks 0.0.1
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.
- 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
|