Skip to content

Commit a0b6b6b

Browse files
committed
Create BundlerAudit module and nest everything else underneath
1 parent e4fb404 commit a0b6b6b

File tree

6 files changed

+170
-161
lines changed

6 files changed

+170
-161
lines changed

bin/bundler-audit

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@ else
1010
engine_config = {}
1111
end
1212

13-
CC::Engine::BundlerAudit.new(
13+
CC::Engine::BundlerAudit::Analyzer.new(
1414
directory: "/code", engine_config: engine_config, io: STDOUT
1515
).run

lib/cc/engine/bundler_audit.rb

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,14 @@
1-
require "json"
21
require "bundler/audit/scanner"
3-
require_relative "./result_decorator"
2+
require "forwardable"
3+
require "json"
4+
require "versionomy"
5+
6+
require "cc/engine/bundler_audit/analyzer"
7+
require "cc/engine/bundler_audit/result"
48

59
module CC
610
module Engine
7-
class BundlerAudit
8-
GemfileLockNotFound = Class.new(StandardError)
9-
10-
def initialize(directory: , io: , engine_config: )
11-
@directory = directory
12-
@engine_config = engine_config
13-
@io = io
14-
end
15-
16-
def run
17-
if gemfile_lock_exists?
18-
Dir.chdir(@directory) do
19-
Bundler::Audit::Scanner.new.scan do |result|
20-
gemfile_lock = File.open(gemfile_lock_path)
21-
decorated = ResultDecorator.new(result, gemfile_lock)
22-
issue = decorated.to_issue
23-
24-
@io.print("#{issue.to_json}\0")
25-
end
26-
end
27-
else
28-
raise GemfileLockNotFound, "No Gemfile.lock found."
29-
end
30-
end
31-
32-
private
33-
34-
def gemfile_lock_exists?
35-
File.exist?(gemfile_lock_path)
36-
end
37-
38-
def gemfile_lock_path
39-
File.join(@directory, "Gemfile.lock")
40-
end
11+
module BundlerAudit
4112
end
4213
end
4314
end
44-
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
module CC
2+
module Engine
3+
module BundlerAudit
4+
class Analyzer
5+
GemfileLockNotFound = Class.new(StandardError)
6+
7+
def initialize(directory: , io: , engine_config: )
8+
@directory = directory
9+
@engine_config = engine_config
10+
@io = io
11+
end
12+
13+
def run
14+
if gemfile_lock_exists?
15+
Dir.chdir(@directory) do
16+
Bundler::Audit::Scanner.new.scan do |vulnerability|
17+
result = Result.new(vulnerability, File.open(gemfile_lock_path))
18+
issue = result.to_issue
19+
20+
@io.print("#{issue.to_json}\0")
21+
end
22+
end
23+
else
24+
raise GemfileLockNotFound, "No Gemfile.lock found."
25+
end
26+
end
27+
28+
private
29+
30+
def gemfile_lock_exists?
31+
File.exist?(gemfile_lock_path)
32+
end
33+
34+
def gemfile_lock_path
35+
File.join(@directory, "Gemfile.lock")
36+
end
37+
end
38+
end
39+
end
40+
end
41+

lib/cc/engine/bundler_audit/result.rb

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
module CC
2+
module Engine
3+
module BundlerAudit
4+
class Result
5+
GEM_REGEX = /^\s*(?<name>\S+) \([\d.]+\)/.freeze
6+
SEVERITIES = {
7+
high: "critical",
8+
medium: "normal",
9+
low: "info",
10+
}.freeze
11+
12+
extend Forwardable
13+
14+
def initialize(result, gemfile_lock)
15+
@gem = result.gem
16+
@advisory = result.advisory
17+
@gemfile_lock = gemfile_lock
18+
end
19+
20+
def to_issue
21+
{
22+
categories: ["Security"],
23+
check_name: "Insecure Dependency",
24+
content: {
25+
body: content_body
26+
},
27+
description: advisory.title,
28+
location: {
29+
path: "Gemfile.lock",
30+
lines: {
31+
begin: line_number,
32+
end: line_number
33+
}
34+
},
35+
remediation_points: remediation_points,
36+
severity: severity,
37+
type: "Issue",
38+
}
39+
end
40+
41+
private
42+
43+
attr_reader :advisory, :gem, :gemfile_lock
44+
45+
def_delegators :gem, :name, :version
46+
def_delegators :advisory, :criticality, :title, :cve, :patched_versions, :url
47+
48+
def content_body
49+
[
50+
"**Advisory**: #{identifier}",
51+
"**Criticality**: #{criticality.capitalize}",
52+
"**URL**: #{url}",
53+
"**Solution**: #{solution}",
54+
].join("\n\n")
55+
end
56+
57+
def line_number
58+
@line_number ||= begin
59+
gemfile_lock.find_index do |line|
60+
(match = GEM_REGEX.match(line)) && match[:name] == name
61+
end + 1
62+
end
63+
end
64+
65+
def remediation_points
66+
if patched_versions.any?
67+
upgrade_versions.map do |upgrade_version|
68+
case
69+
when current_version.major != upgrade_version.major
70+
50_000_000
71+
when current_version.minor != upgrade_version.minor
72+
5_000_000
73+
when current_version.tiny != upgrade_version.tiny
74+
500_000
75+
end
76+
end.min
77+
else
78+
500_000_000
79+
end
80+
end
81+
82+
def severity
83+
SEVERITIES[criticality]
84+
end
85+
86+
def solution
87+
if patched_versions.any?
88+
"upgrade to #{patched_versions.join(', ')}"
89+
else
90+
"remove or disable this gem until a patch is available!"
91+
end
92+
end
93+
94+
def identifier
95+
case
96+
when cve then "CVE-#{cve}"
97+
when osvdb then osvdb
98+
end
99+
end
100+
101+
def current_version
102+
Versionomy.parse(version.to_s)
103+
end
104+
105+
def upgrade_versions
106+
patched_versions.map do |gem_requirement|
107+
requirements = Gem::Requirement.parse(gem_requirement)
108+
unqualified_version = requirements.last
109+
110+
Versionomy.parse(unqualified_version.to_s)
111+
end
112+
end
113+
end
114+
end
115+
end
116+
end

lib/cc/engine/result_decorator.rb

Lines changed: 0 additions & 118 deletions
This file was deleted.

spec/cc/engine/bundler_audit_spec.rb renamed to spec/cc/engine/bundler_audit/analyzer_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
require "spec_helper"
22

3-
module CC::Engine
4-
describe BundlerAudit do
3+
module CC::Engine::BundlerAudit
4+
describe Analyzer do
55
describe "#run" do
66
it "raises an error when no Gemfile.lock exists" do
77
directory = File.join(Dir.pwd, "spec", "fixtures", "no_gemfile_lock")
88
io = StringIO.new
99

10-
expect { BundlerAudit.new(directory: directory, io: io, engine_config: {}).run }
11-
.to raise_error(CC::Engine::BundlerAudit::GemfileLockNotFound)
10+
expect { Analyzer.new(directory: directory, io: io, engine_config: {}).run }
11+
.to raise_error(Analyzer::GemfileLockNotFound)
1212
end
1313

1414
it "emits issues for Gemfile.lock problems" do
1515
io = StringIO.new
1616
directory = File.join(Dir.pwd, "spec", "fixtures", "unpatched_versions")
1717

18-
audit = BundlerAudit.new(directory: directory, io: io, engine_config: {})
18+
audit = Analyzer.new(directory: directory, io: io, engine_config: {})
1919
audit.run
2020

2121
issues = io.string.split("\0").map { |issue| JSON.load(issue) }

0 commit comments

Comments
 (0)