Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/bundler/audit/advisory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ class Advisory < Struct.new(:path,
:osvdb,
:ghsa,
:unaffected_versions,
:patched_versions)
:patched_versions,
:gem,
:engine)

#
# Loads the advisory from a YAML file.
Expand Down Expand Up @@ -83,7 +85,9 @@ def self.load(path)
data['osvdb'],
data['ghsa'],
parse_versions[data['unaffected_versions']],
parse_versions[data['patched_versions']]
parse_versions[data['patched_versions']],
data['gem'],
data['engine']
)
end

Expand Down
33 changes: 32 additions & 1 deletion lib/bundler/audit/cli/formats/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ module JSON
# The output stream.
#
def print_report(report,output=$stdout)
hash = report.to_h
hash = prepare_data(report)

if output.tty?
output.puts(::JSON.pretty_generate(hash))
Expand All @@ -56,6 +56,37 @@ def criticality_label(advisory)
else "unknown"
end
end

private

#
# Prepares the data from the report into a hash before it is formatted as JSON.
#
# @param [Report] report
# The results from the {Scanner}.
#
# @return [Hash]
#
def prepare_data(report)
hash = report.to_h
hash[:results].each do |result|
prepare_result(result)
end
hash
end

#
# Prepares a result hash before it is formatted as JSON.
#
# @param [Hash] result
# A result
#
def prepare_result(result)
if (advisory = result[:advisory])
advisory.delete(:gem)
advisory.delete(:engine)
Comment on lines +86 to +87

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was needed to exclude these two new Advisory attributes from the JSON output.

I am avoiding adding new data to the output JSON without good reason, to reduce the risk of breaking anything that depends on this JSON having the same keys it always has.

end
end
end

Formats.register :json, JSON
Expand Down
17 changes: 9 additions & 8 deletions lib/bundler/audit/cli/formats/junit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,32 +78,33 @@ def print_xml_testcase(result)
%{ <failure message="Insecure Source URI found: #{xml(result.source)}" type="Unknown"></failure>},
%{ </testcase>}
)
when Results::UnpatchedGem
else
say_xml(
%{ <testcase id="#{xml(result.gem.name)}" name="#{xml(bundle_title(result))}">},
%{ <testcase id="#{xml(result.vulnerable_name)}" name="#{xml(bundle_title(result))}">},
%{ <failure message="#{xml(result.advisory.title)}" type="#{xml(result.advisory.criticality)}">},
%{ Name: #{xml(result.gem.name)}},
%{ Version: #{xml(result.gem.version)}},
%{ Name: #{xml(result.vulnerable_name)}},
%{ Version: #{xml(result.vulnerable_version)}},
%{ Advisory: #{xml(advisory_ref(result.advisory))}},
%{ Criticality: #{xml(advisory_criticality(result.advisory))}},
%{ URL: #{xml(result.advisory.url)}},
%{ Title: #{xml(result.advisory.title)}},
%{ Solution: #{xml(advisory_solution(result.advisory))}},
%{ Solution: #{xml(advisory_solution(result))}},
%{ </failure>},
%{ </testcase>}
)
end
end

def bundle_title(result)
"#{advisory_criticality(result.advisory).upcase} #{result.gem.name}(#{result.gem.version}) #{result.advisory.title}"
"#{advisory_criticality(result.advisory).upcase} #{result.vulnerable_name}(#{result.vulnerable_version}) #{result.advisory.title}"
end

def advisory_solution(advisory)
def advisory_solution(result)
advisory = result.advisory
unless advisory.patched_versions.empty?
"update to #{advisory.patched_versions.map { |v| "'#{v}'" }.join(', ')}"
else
"remove or disable this gem until a patch is available!"
"remove or disable this #{result.short_type} until a patch is available!"
end
end

Expand Down
13 changes: 7 additions & 6 deletions lib/bundler/audit/cli/formats/text.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ def print_report(report,output=$stdout)
case result
when Results::InsecureSource
print_warning "Insecure Source URI found: #{result.source}"
when Results::UnpatchedGem
print_advisory result.gem, result.advisory
else
print_advisory result
end
end

Expand All @@ -62,13 +62,14 @@ def print_warning(message)
say message, :yellow
end

def print_advisory(gem, advisory)
def print_advisory(result)
say "Name: ", :red
say gem.name
say result.vulnerable_name

say "Version: ", :red
say gem.version
say result.vulnerable_version

advisory = result.advisory
if advisory.cve
say "CVE: ", :red
say advisory.cve_id
Expand Down Expand Up @@ -108,7 +109,7 @@ def print_advisory(gem, advisory)
say advisory.patched_versions.map { |v| "'#{v}'" }.join(', ')
else
say "Solution: ", :red
say "remove or disable this gem until a patch is available!", [:red, :bold]
say "remove or disable this #{result.short_type} until a patch is available!", [:red, :bold]
end

say
Expand Down
54 changes: 43 additions & 11 deletions lib/bundler/audit/database.rb
Original file line number Diff line number Diff line change
Expand Up @@ -271,24 +271,27 @@ def advisories(&block)
end

#
# Enumerates over advisories for the given gem.
# Enumerates over advisories for the given gem or engine.
#
# @param [String] name
# The gem name to lookup.
# The gem or engine name to lookup.
#
# @param [String] dir
# The 'gems' or 'rubies' subdirectory. Use 'gems' for gem advisories or 'rubies' for engine advisories.
#
# @yield [advisory]
# If a block is given, each advisory for the given gem will be yielded.
# If a block is given, each advisory for the given gem or engine will be yielded.
#
# @yieldparam [Advisory] advisory
# An advisory for the given gem.
# An advisory for the given gem or engine.
#
# @return [Enumerator]
# If no block is given, an Enumerator will be returned.
#
def advisories_for(name)
def advisories_for(name, dir='gems')
return enum_for(__method__,name) unless block_given?

each_advisory_path_for(name) do |path|
each_advisory_path_for(name, dir) do |path|
yield Advisory.load(path)
end
end
Expand Down Expand Up @@ -319,6 +322,32 @@ def check_gem(gem)
end
end

#
# Verifies whether the ruby version is impacted by any advisories.
#
# @param [Bundler::RubyVersion] ruby_version
# The ruby version to verify.
#
# @yield [advisory]
# If a block is given, it will be passed advisories that impact
# the ruby version.
#
# @yieldparam [Advisory] advisory
# An advisory that impacts the ruby version.
#
# @return [Enumerator]
# If no block is given, an Enumerator will be returned.
#
def check_ruby(ruby_version)
return enum_for(__method__,ruby_version) unless block_given?

advisories_for(ruby_version.engine, 'rubies') do |advisory|
if advisory.vulnerable?(ruby_version.engine_gem_version)
yield advisory
end
end
end

#
# The number of advisories within the database.
#
Expand Down Expand Up @@ -361,23 +390,26 @@ def inspect
# A path to an advisory `.yml` file.
#
def each_advisory_path(&block)
Dir.glob(File.join(@path,'gems','*','*.yml'),&block)
Dir.glob(File.join(@path,'{gems,rubies}','*','*.yml'),&block)
end

#
# Enumerates over the advisories for the given gem.
# Enumerates over the advisories for the given gem or engine.
#
# @param [String] name
# The gem of the gem.
# The name of the gem or engine.
#
# @param [String] dir
# The 'gems' or 'rubies' subdirectory. Use 'gems' for gem advisories or 'rubies' for engine advisories.
#
# @yield [path]
# The given block will be passed each advisory path.
#
# @yieldparam [String] path
# A path to an advisory `.yml` file.
#
def each_advisory_path_for(name,&block)
Dir.glob(File.join(@path,'gems',name,'*.yml'),&block)
def each_advisory_path_for(name, dir='gems', &block)
Dir.glob(File.join(@path,dir,name,'*.yml'),&block)
end

end
Expand Down
16 changes: 12 additions & 4 deletions lib/bundler/audit/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class Report
# @return [Array<Results::UnpatchedGems>]
attr_reader :unpatched_gems

# The unpatched engines results.
#
# @return [Array<Results::UnpatchedEngine>]
attr_reader :unpatched_engines

#
# Initializes the report.
#
Expand All @@ -49,6 +54,7 @@ def initialize(results=[])
@results = []
@insecure_sources = []
@unpatched_gems = []
@unpatched_engines = []

results.each { |result| self << result }
end
Expand All @@ -58,7 +64,7 @@ def initialize(results=[])
#
# @yield [result]
#
# @yieldparam [Results::InsecureSource, Results::UnpatchedGem] result
# @yieldparam [Results::InsecureSource, Results::UnpatchedGem, Results::UnpatchedEngine] result
#
# @return [Enumerator]
#
Expand All @@ -69,7 +75,7 @@ def each(&block)
#
# Appends a result to the report.
#
# @param [InsecureSource, UnpatchedGem] result
# @param [InsecureSource, UnpatchedGem, UnpatchedEngine] result
#
def <<(result)
@results << result
Expand All @@ -79,6 +85,8 @@ def <<(result)
@insecure_sources << result
when Results::UnpatchedGem
@unpatched_gems << result
when Results::UnpatchedEngine
@unpatched_engines << result
end

return self
Expand All @@ -103,14 +111,14 @@ def vulnerable?
def each_advisory
return enum_for(__method__) unless block_given?

@unpatched_gems.each { |result| yield result.advisory }
(@unpatched_gems + @unpatched_engines).each { |result| yield result.advisory }
end

#
# @return [Array<Advisory>]
#
def advisories
@unpatched_gems.map(&:advisory)
each_advisory.to_a
end

#
Expand Down
1 change: 1 addition & 0 deletions lib/bundler/audit/results.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@

require 'bundler/audit/results/insecure_source'
require 'bundler/audit/results/unpatched_gem'
require 'bundler/audit/results/unpatched_engine'
Loading