bundler-audit 0.3.1 → 0.7.0
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 +5 -5
- data/.gitignore +3 -1
- data/.travis.yml +13 -4
- data/ChangeLog.md +53 -0
- data/Gemfile +4 -3
- data/README.md +44 -18
- data/Rakefile +13 -21
- data/bin/bundler-audit +3 -0
- data/data/ruby-advisory-db.ts +1 -1
- data/gemspec.yml +4 -3
- data/lib/bundler/audit.rb +1 -1
- data/lib/bundler/audit/advisory.rb +71 -7
- data/lib/bundler/audit/cli.rb +41 -11
- data/lib/bundler/audit/database.rb +29 -7
- data/lib/bundler/audit/scanner.rb +126 -10
- data/lib/bundler/audit/task.rb +31 -0
- data/lib/bundler/audit/version.rb +2 -2
- data/spec/advisory_spec.rb +211 -35
- data/spec/audit_spec.rb +1 -1
- data/spec/bundle/insecure_sources/Gemfile +2 -37
- data/spec/bundle/secure/Gemfile +2 -36
- data/spec/bundle/unpatched_gems/Gemfile +1 -36
- data/spec/cli_spec.rb +126 -0
- data/spec/database_spec.rb +51 -25
- data/spec/integration_spec.rb +35 -13
- data/spec/scanner_spec.rb +11 -10
- data/spec/spec_helper.rb +9 -17
- metadata +38 -121
- data/data/ruby-advisory-db/.gitignore +0 -1
- data/data/ruby-advisory-db/.rspec +0 -1
- data/data/ruby-advisory-db/CONTRIBUTING.md +0 -6
- data/data/ruby-advisory-db/CONTRIBUTORS.md +0 -23
- data/data/ruby-advisory-db/Gemfile +0 -3
- data/data/ruby-advisory-db/LICENSE.txt +0 -5
- data/data/ruby-advisory-db/README.md +0 -82
- data/data/ruby-advisory-db/Rakefile +0 -27
- data/data/ruby-advisory-db/gems/actionmailer/OSVDB-98629.yml +0 -17
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-100524.yml +0 -20
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-100525.yml +0 -21
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-100526.yml +0 -27
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-100527.yml +0 -24
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-100528.yml +0 -22
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-103439.yml +0 -24
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-103440.yml +0 -22
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-79727.yml +0 -26
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-84243.yml +0 -28
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-84513.yml +0 -23
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-84515.yml +0 -26
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-89026.yml +0 -24
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-91452.yml +0 -20
- data/data/ruby-advisory-db/gems/actionpack/OSVDB-91454.yml +0 -23
- data/data/ruby-advisory-db/gems/activerecord/OSVDB-103438.yml +0 -23
- data/data/ruby-advisory-db/gems/activerecord/OSVDB-82403.yml +0 -25
- data/data/ruby-advisory-db/gems/activerecord/OSVDB-82610.yml +0 -24
- data/data/ruby-advisory-db/gems/activerecord/OSVDB-89025.yml +0 -24
- data/data/ruby-advisory-db/gems/activerecord/OSVDB-90072.yml +0 -21
- data/data/ruby-advisory-db/gems/activerecord/OSVDB-90073.yml +0 -23
- data/data/ruby-advisory-db/gems/activerecord/OSVDB-91453.yml +0 -26
- data/data/ruby-advisory-db/gems/activesupport/OSVDB-79726.yml +0 -26
- data/data/ruby-advisory-db/gems/activesupport/OSVDB-84516.yml +0 -23
- data/data/ruby-advisory-db/gems/activesupport/OSVDB-89594.yml +0 -25
- data/data/ruby-advisory-db/gems/activesupport/OSVDB-91451.yml +0 -28
- data/data/ruby-advisory-db/gems/arabic-prawn/OSVDB-104365.yml +0 -15
- data/data/ruby-advisory-db/gems/cocaine/OSVDB-98835.yml +0 -15
- data/data/ruby-advisory-db/gems/command_wrap/OSVDB-91450.yml +0 -10
- data/data/ruby-advisory-db/gems/crack/OSVDB-90742.yml +0 -17
- data/data/ruby-advisory-db/gems/cremefraiche/OSVDB-93395.yml +0 -11
- data/data/ruby-advisory-db/gems/curl/OSVDB-91230.yml +0 -12
- data/data/ruby-advisory-db/gems/devise/OSVDB-89642.yml +0 -20
- data/data/ruby-advisory-db/gems/dragonfly/OSVDB-90647.yml +0 -19
- data/data/ruby-advisory-db/gems/echor/OSVDB-102129.yml +0 -11
- data/data/ruby-advisory-db/gems/echor/OSVDB-102130.yml +0 -10
- data/data/ruby-advisory-db/gems/enum_column3/OSVDB-94679.yml +0 -9
- data/data/ruby-advisory-db/gems/extlib/OSVDB-90740.yml +0 -18
- data/data/ruby-advisory-db/gems/fastreader/OSVDB-91232.yml +0 -12
- data/data/ruby-advisory-db/gems/fileutils/OSVDB-90715.yml +0 -10
- data/data/ruby-advisory-db/gems/fileutils/OSVDB-90716.yml +0 -10
- data/data/ruby-advisory-db/gems/fileutils/OSVDB-90717.yml +0 -10
- data/data/ruby-advisory-db/gems/flash_tool/OSVDB-90829.yml +0 -9
- data/data/ruby-advisory-db/gems/fog-dragonfly/OSVDB-96798.yml +0 -13
- data/data/ruby-advisory-db/gems/ftpd/OSVDB-90784.yml +0 -18
- data/data/ruby-advisory-db/gems/gitlab-grit/OSVDB-99370.yml +0 -14
- data/data/ruby-advisory-db/gems/gtk2/OSVDB-40774.yml +0 -20
- data/data/ruby-advisory-db/gems/httparty/OSVDB-90741.yml +0 -14
- data/data/ruby-advisory-db/gems/i18n/OSVDB-100528.yml +0 -17
- data/data/ruby-advisory-db/gems/json/OSVDB-90074.yml +0 -23
- data/data/ruby-advisory-db/gems/karteek-docsplit/OSVDB-92117.yml +0 -10
- data/data/ruby-advisory-db/gems/kelredd-pruview/OSVDB-92228.yml +0 -10
- data/data/ruby-advisory-db/gems/ldoce/OSVDB-91870.yml +0 -10
- data/data/ruby-advisory-db/gems/loofah/OSVDB-90945.yml +0 -21
- data/data/ruby-advisory-db/gems/mail/OSVDB-70667.yml +0 -21
- data/data/ruby-advisory-db/gems/mail/OSVDB-81631.yml +0 -14
- data/data/ruby-advisory-db/gems/mail/OSVDB-81632.yml +0 -16
- data/data/ruby-advisory-db/gems/md2pdf/OSVDB-92290.yml +0 -10
- data/data/ruby-advisory-db/gems/mini_magick/OSVDB-91231.yml +0 -15
- data/data/ruby-advisory-db/gems/multi_xml/OSVDB-89148.yml +0 -16
- data/data/ruby-advisory-db/gems/newrelic_rpm/OSVDB-90189.yml +0 -17
- data/data/ruby-advisory-db/gems/nokogiri/OSVDB-101179.yml +0 -12
- data/data/ruby-advisory-db/gems/nokogiri/OSVDB-101458.yml +0 -15
- data/data/ruby-advisory-db/gems/nori/OSVDB-90196.yml +0 -19
- data/data/ruby-advisory-db/gems/omniauth-facebook/OSVDB-99693.yml +0 -22
- data/data/ruby-advisory-db/gems/omniauth-facebook/OSVDB-99888.yml +0 -17
- data/data/ruby-advisory-db/gems/omniauth-oauth2/OSVDB-90264.yml +0 -16
- data/data/ruby-advisory-db/gems/paperclip/OSVDB-103151.yml +0 -13
- data/data/ruby-advisory-db/gems/paratrooper-newrelic/OSVDB-101839.yml +0 -12
- data/data/ruby-advisory-db/gems/paratrooper-pingdom/OSVDB-101847.yml +0 -13
- data/data/ruby-advisory-db/gems/pdfkit/OSVDB-90867.yml +0 -11
- data/data/ruby-advisory-db/gems/rack-cache/OSVDB-83077.yml +0 -18
- data/data/ruby-advisory-db/gems/rack/OSVDB-89939.yml +0 -23
- data/data/ruby-advisory-db/gems/rbovirt/OSVDB-104080.yml +0 -20
- data/data/ruby-advisory-db/gems/rdoc/OSVDB-90004.yml +0 -27
- data/data/ruby-advisory-db/gems/redis-namespace/OSVDB-96425.yml +0 -16
- data/data/ruby-advisory-db/gems/rgpg/OSVDB-95948.yml +0 -14
- data/data/ruby-advisory-db/gems/ruby_parser/OSVDB-90561.yml +0 -11
- data/data/ruby-advisory-db/gems/sfpagent/OSVDB-105971.yml +0 -13
- data/data/ruby-advisory-db/gems/sounder/OSVDB-96278.yml +0 -13
- data/data/ruby-advisory-db/gems/spree/OSVDB-91216.yml +0 -11
- data/data/ruby-advisory-db/gems/spree/OSVDB-91217.yml +0 -11
- data/data/ruby-advisory-db/gems/spree/OSVDB-91218.yml +0 -11
- data/data/ruby-advisory-db/gems/spree/OSVDB-91219.yml +0 -11
- data/data/ruby-advisory-db/gems/sprout/OSVDB-100598.yml +0 -14
- data/data/ruby-advisory-db/gems/thumbshooter/OSVDB-91839.yml +0 -10
- data/data/ruby-advisory-db/gems/webbynode/OSVDB-100920.yml +0 -11
- data/data/ruby-advisory-db/gems/wicked/OSVDB-98270.yml +0 -14
- data/data/ruby-advisory-db/gems/will_paginate/OSVDB-101138.yml +0 -15
- data/data/ruby-advisory-db/lib/scrape.rb +0 -87
- data/data/ruby-advisory-db/spec/advisory_example.rb +0 -165
- data/data/ruby-advisory-db/spec/gems_spec.rb +0 -7
- data/data/ruby-advisory-db/spec/spec_helper.rb +0 -1
data/lib/bundler/audit/cli.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (c) 2013-
|
|
2
|
+
# Copyright (c) 2013-2020 Hal Brodigan (postmodern.mod3 at gmail.com)
|
|
3
3
|
#
|
|
4
4
|
# bundler-audit is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -30,10 +30,14 @@ module Bundler
|
|
|
30
30
|
map '--version' => :version
|
|
31
31
|
|
|
32
32
|
desc 'check', 'Checks the Gemfile.lock for insecure dependencies'
|
|
33
|
+
method_option :quiet, :type => :boolean, :aliases => '-q'
|
|
33
34
|
method_option :verbose, :type => :boolean, :aliases => '-v'
|
|
34
35
|
method_option :ignore, :type => :array, :aliases => '-i'
|
|
36
|
+
method_option :update, :type => :boolean, :aliases => '-u'
|
|
35
37
|
|
|
36
38
|
def check
|
|
39
|
+
update if options[:update]
|
|
40
|
+
|
|
37
41
|
scanner = Scanner.new
|
|
38
42
|
vulnerable = false
|
|
39
43
|
|
|
@@ -49,19 +53,36 @@ module Bundler
|
|
|
49
53
|
end
|
|
50
54
|
|
|
51
55
|
if vulnerable
|
|
52
|
-
say "
|
|
56
|
+
say "Vulnerabilities found!", :red
|
|
53
57
|
exit 1
|
|
54
58
|
else
|
|
55
|
-
say
|
|
59
|
+
say("No vulnerabilities found", :green) unless options.quiet?
|
|
56
60
|
end
|
|
57
61
|
end
|
|
58
62
|
|
|
59
63
|
desc 'update', 'Updates the ruby-advisory-db'
|
|
64
|
+
method_option :quiet, :type => :boolean, :aliases => '-q'
|
|
65
|
+
|
|
60
66
|
def update
|
|
61
|
-
say
|
|
67
|
+
say("Updating ruby-advisory-db ...") unless options.quiet?
|
|
62
68
|
|
|
63
|
-
Database.update!
|
|
64
|
-
|
|
69
|
+
case Database.update!(quiet: options.quiet?)
|
|
70
|
+
when true
|
|
71
|
+
say("Updated ruby-advisory-db", :green) unless options.quiet?
|
|
72
|
+
when false
|
|
73
|
+
say "Failed updating ruby-advisory-db!", :red
|
|
74
|
+
exit 1
|
|
75
|
+
when nil
|
|
76
|
+
unless Bundler.git_present?
|
|
77
|
+
say "Git is not installed!", :red
|
|
78
|
+
exit 1
|
|
79
|
+
end
|
|
80
|
+
say "Skipping update", :yellow
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
unless options.quiet?
|
|
84
|
+
puts("ruby-advisory-db: #{Database.new.size} advisories")
|
|
85
|
+
end
|
|
65
86
|
end
|
|
66
87
|
|
|
67
88
|
desc 'version', 'Prints the bundler-audit version'
|
|
@@ -90,14 +111,23 @@ module Bundler
|
|
|
90
111
|
say gem.version
|
|
91
112
|
|
|
92
113
|
say "Advisory: ", :red
|
|
93
|
-
|
|
114
|
+
|
|
115
|
+
if advisory.cve
|
|
116
|
+
say advisory.cve_id
|
|
117
|
+
elsif advisory.osvdb
|
|
118
|
+
say advisory.osvdb_id
|
|
119
|
+
elsif advisory.ghsa
|
|
120
|
+
say advisory.ghsa_id
|
|
121
|
+
end
|
|
94
122
|
|
|
95
123
|
say "Criticality: ", :red
|
|
96
124
|
case advisory.criticality
|
|
97
|
-
when :
|
|
98
|
-
when :
|
|
99
|
-
when :
|
|
100
|
-
|
|
125
|
+
when :none then say "None"
|
|
126
|
+
when :low then say "Low"
|
|
127
|
+
when :medium then say "Medium", :yellow
|
|
128
|
+
when :high then say "High", [:red, :bold]
|
|
129
|
+
when :critical then say "Critical", [:red, :bold]
|
|
130
|
+
else say "Unknown"
|
|
101
131
|
end
|
|
102
132
|
|
|
103
133
|
say "URL: ", :red
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (c) 2013-
|
|
2
|
+
# Copyright (c) 2013-2020 Hal Brodigan (postmodern.mod3 at gmail.com)
|
|
3
3
|
#
|
|
4
4
|
# bundler-audit is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -68,7 +68,7 @@ module Bundler
|
|
|
68
68
|
#
|
|
69
69
|
def self.path
|
|
70
70
|
if File.directory?(USER_PATH)
|
|
71
|
-
t1 = Dir.chdir(USER_PATH) { Time.parse(`git log --pretty="%cd" -1`) }
|
|
71
|
+
t1 = Dir.chdir(USER_PATH) { Time.parse(`git log --date=iso8601 --pretty="%cd" -1`).utc }
|
|
72
72
|
t2 = VENDORED_TIMESTAMP
|
|
73
73
|
|
|
74
74
|
if t1 >= t2 then USER_PATH
|
|
@@ -82,21 +82,43 @@ module Bundler
|
|
|
82
82
|
#
|
|
83
83
|
# Updates the ruby-advisory-db.
|
|
84
84
|
#
|
|
85
|
-
# @
|
|
85
|
+
# @param [Hash] options
|
|
86
|
+
# Additional options.
|
|
87
|
+
#
|
|
88
|
+
# @option options [Boolean] :quiet
|
|
89
|
+
# Specify whether `git` should be `--quiet`.
|
|
90
|
+
#
|
|
91
|
+
# @return [Boolean, nil]
|
|
86
92
|
# Specifies whether the update was successful.
|
|
93
|
+
# A `nil` indicates no update was performed.
|
|
94
|
+
#
|
|
95
|
+
# @raise [ArgumentError]
|
|
96
|
+
# Invalid options were given.
|
|
87
97
|
#
|
|
88
98
|
# @note
|
|
89
99
|
# Requires network access.
|
|
90
100
|
#
|
|
91
101
|
# @since 0.3.0
|
|
92
102
|
#
|
|
93
|
-
def self.update!
|
|
103
|
+
def self.update!(options={})
|
|
104
|
+
unless (options.keys - [:quiet]).empty?
|
|
105
|
+
raise(ArgumentError,"Invalid option(s)")
|
|
106
|
+
end
|
|
107
|
+
|
|
94
108
|
if File.directory?(USER_PATH)
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
if File.directory?(File.join(USER_PATH, ".git"))
|
|
110
|
+
Dir.chdir(USER_PATH) do
|
|
111
|
+
command = %w(git pull --no-rebase)
|
|
112
|
+
command << '--quiet' if options[:quiet]
|
|
113
|
+
command << 'origin' << 'master'
|
|
114
|
+
system *command
|
|
115
|
+
end
|
|
97
116
|
end
|
|
98
117
|
else
|
|
99
|
-
|
|
118
|
+
command = %w(git clone)
|
|
119
|
+
command << '--quiet' if options[:quiet]
|
|
120
|
+
command << URL << USER_PATH
|
|
121
|
+
system *command
|
|
100
122
|
end
|
|
101
123
|
end
|
|
102
124
|
|
|
@@ -2,7 +2,10 @@ require 'bundler'
|
|
|
2
2
|
require 'bundler/audit/database'
|
|
3
3
|
require 'bundler/lockfile_parser'
|
|
4
4
|
|
|
5
|
+
require 'ipaddr'
|
|
6
|
+
require 'resolv'
|
|
5
7
|
require 'set'
|
|
8
|
+
require 'uri'
|
|
6
9
|
|
|
7
10
|
module Bundler
|
|
8
11
|
module Audit
|
|
@@ -33,11 +36,14 @@ module Bundler
|
|
|
33
36
|
# @param [String] root
|
|
34
37
|
# The path to the project root.
|
|
35
38
|
#
|
|
36
|
-
|
|
39
|
+
# @param [String] gemfile_lock
|
|
40
|
+
# Alternative name for the `Gemfile.lock` file.
|
|
41
|
+
#
|
|
42
|
+
def initialize(root=Dir.pwd,gemfile_lock='Gemfile.lock')
|
|
37
43
|
@root = File.expand_path(root)
|
|
38
44
|
@database = Database.new
|
|
39
45
|
@lockfile = LockfileParser.new(
|
|
40
|
-
File.read(File.join(@root,
|
|
46
|
+
File.read(File.join(@root,gemfile_lock))
|
|
41
47
|
)
|
|
42
48
|
end
|
|
43
49
|
|
|
@@ -59,39 +65,149 @@ module Bundler
|
|
|
59
65
|
# @return [Enumerator]
|
|
60
66
|
# If no block is given, an Enumerator will be returned.
|
|
61
67
|
#
|
|
62
|
-
def scan(options={})
|
|
63
|
-
return enum_for(__method__,options) unless
|
|
68
|
+
def scan(options={},&block)
|
|
69
|
+
return enum_for(__method__,options) unless block
|
|
64
70
|
|
|
65
71
|
ignore = Set[]
|
|
66
72
|
ignore += options[:ignore] if options[:ignore]
|
|
67
73
|
|
|
74
|
+
scan_sources(options,&block)
|
|
75
|
+
scan_specs(options,&block)
|
|
76
|
+
|
|
77
|
+
return self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
#
|
|
81
|
+
# Scans the gem sources in the lockfile.
|
|
82
|
+
#
|
|
83
|
+
# @param [Hash] options
|
|
84
|
+
# Additional options.
|
|
85
|
+
#
|
|
86
|
+
# @yield [result]
|
|
87
|
+
# The given block will be passed the results of the scan.
|
|
88
|
+
#
|
|
89
|
+
# @yieldparam [InsecureSource] result
|
|
90
|
+
# A result from the scan.
|
|
91
|
+
#
|
|
92
|
+
# @return [Enumerator]
|
|
93
|
+
# If no block is given, an Enumerator will be returned.
|
|
94
|
+
#
|
|
95
|
+
# @api semipublic
|
|
96
|
+
#
|
|
97
|
+
# @since 0.4.0
|
|
98
|
+
#
|
|
99
|
+
def scan_sources(options={})
|
|
100
|
+
return enum_for(__method__,options) unless block_given?
|
|
101
|
+
|
|
68
102
|
@lockfile.sources.map do |source|
|
|
69
103
|
case source
|
|
70
104
|
when Source::Git
|
|
71
105
|
case source.uri
|
|
72
106
|
when /^git:/, /^http:/
|
|
73
|
-
|
|
107
|
+
unless internal_source?(source.uri)
|
|
108
|
+
yield InsecureSource.new(source.uri)
|
|
109
|
+
end
|
|
74
110
|
end
|
|
75
111
|
when Source::Rubygems
|
|
76
112
|
source.remotes.each do |uri|
|
|
77
|
-
if uri.scheme == 'http'
|
|
113
|
+
if (uri.scheme == 'http' && !internal_source?(uri))
|
|
78
114
|
yield InsecureSource.new(uri.to_s)
|
|
79
115
|
end
|
|
80
116
|
end
|
|
81
117
|
end
|
|
82
118
|
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
#
|
|
122
|
+
# Scans the gem sources in the lockfile.
|
|
123
|
+
#
|
|
124
|
+
# @param [Hash] options
|
|
125
|
+
# Additional options.
|
|
126
|
+
#
|
|
127
|
+
# @option options [Array<String>] :ignore
|
|
128
|
+
# The advisories to ignore.
|
|
129
|
+
#
|
|
130
|
+
# @yield [result]
|
|
131
|
+
# The given block will be passed the results of the scan.
|
|
132
|
+
#
|
|
133
|
+
# @yieldparam [UnpatchedGem] result
|
|
134
|
+
# A result from the scan.
|
|
135
|
+
#
|
|
136
|
+
# @return [Enumerator]
|
|
137
|
+
# If no block is given, an Enumerator will be returned.
|
|
138
|
+
#
|
|
139
|
+
# @api semipublic
|
|
140
|
+
#
|
|
141
|
+
# @since 0.4.0
|
|
142
|
+
#
|
|
143
|
+
def scan_specs(options={})
|
|
144
|
+
return enum_for(__method__,options) unless block_given?
|
|
145
|
+
|
|
146
|
+
ignore = Set[]
|
|
147
|
+
ignore += options[:ignore] if options[:ignore]
|
|
83
148
|
|
|
84
149
|
@lockfile.specs.each do |gem|
|
|
85
150
|
@database.check_gem(gem) do |advisory|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
151
|
+
is_ignored = ignore.intersect?(advisory.identifiers.to_set)
|
|
152
|
+
next if is_ignored
|
|
153
|
+
|
|
154
|
+
yield UnpatchedGem.new(gem,advisory)
|
|
89
155
|
end
|
|
90
156
|
end
|
|
157
|
+
end
|
|
91
158
|
|
|
92
|
-
|
|
159
|
+
private
|
|
160
|
+
|
|
161
|
+
#
|
|
162
|
+
# Determines whether a source is internal.
|
|
163
|
+
#
|
|
164
|
+
# @param [URI, String] uri
|
|
165
|
+
# The URI.
|
|
166
|
+
#
|
|
167
|
+
# @return [Boolean]
|
|
168
|
+
#
|
|
169
|
+
def internal_source?(uri)
|
|
170
|
+
uri = URI.parse(uri.to_s)
|
|
171
|
+
|
|
172
|
+
internal_host?(uri.host) if uri.host
|
|
93
173
|
end
|
|
94
174
|
|
|
175
|
+
#
|
|
176
|
+
# Determines whether a host is internal.
|
|
177
|
+
#
|
|
178
|
+
# @param [String] host
|
|
179
|
+
# The hostname.
|
|
180
|
+
#
|
|
181
|
+
# @return [Boolean]
|
|
182
|
+
#
|
|
183
|
+
def internal_host?(host)
|
|
184
|
+
Resolv.getaddresses(host).all? { |ip| internal_ip?(ip) }
|
|
185
|
+
rescue URI::Error
|
|
186
|
+
false
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# List of internal IP address ranges.
|
|
190
|
+
#
|
|
191
|
+
# @see https://tools.ietf.org/html/rfc1918#section-3
|
|
192
|
+
# @see https://tools.ietf.org/html/rfc4193#section-8
|
|
193
|
+
INTERNAL_SUBNETS = %w[
|
|
194
|
+
10.0.0.0/8
|
|
195
|
+
172.16.0.0/12
|
|
196
|
+
192.168.0.0/16
|
|
197
|
+
fc00::/7
|
|
198
|
+
].map(&IPAddr.method(:new))
|
|
199
|
+
|
|
200
|
+
#
|
|
201
|
+
# Determines whether an IP is internal.
|
|
202
|
+
#
|
|
203
|
+
# @param [String] ip
|
|
204
|
+
# The IPv4/IPv6 address.
|
|
205
|
+
#
|
|
206
|
+
# @return [Boolean]
|
|
207
|
+
#
|
|
208
|
+
def internal_ip?(ip)
|
|
209
|
+
INTERNAL_SUBNETS.any? { |subnet| subnet.include?(ip) }
|
|
210
|
+
end
|
|
95
211
|
end
|
|
96
212
|
end
|
|
97
213
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'rake/tasklib'
|
|
2
|
+
|
|
3
|
+
module Bundler
|
|
4
|
+
module Audit
|
|
5
|
+
class Task < Rake::TaskLib
|
|
6
|
+
#
|
|
7
|
+
# Initializes the task.
|
|
8
|
+
#
|
|
9
|
+
def initialize
|
|
10
|
+
define
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# Defines the `bundle:audit` task.
|
|
17
|
+
#
|
|
18
|
+
def define
|
|
19
|
+
namespace :bundle do
|
|
20
|
+
desc 'Updates the ruby-advisory-db then runs bundle-audit'
|
|
21
|
+
task :audit do
|
|
22
|
+
require 'bundler/audit/cli'
|
|
23
|
+
%w(update check).each do |command|
|
|
24
|
+
Bundler::Audit::CLI.start [command]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (c) 2013-
|
|
2
|
+
# Copyright (c) 2013-2020 Hal Brodigan (postmodern.mod3 at gmail.com)
|
|
3
3
|
#
|
|
4
4
|
# bundler-audit is free software: you can redistribute it and/or modify
|
|
5
5
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
module Bundler
|
|
19
19
|
module Audit
|
|
20
20
|
# bundler-audit version
|
|
21
|
-
VERSION = '0.
|
|
21
|
+
VERSION = '0.7.0'
|
|
22
22
|
end
|
|
23
23
|
end
|
data/spec/advisory_spec.rb
CHANGED
|
@@ -5,7 +5,7 @@ require 'bundler/audit/advisory'
|
|
|
5
5
|
describe Bundler::Audit::Advisory do
|
|
6
6
|
let(:root) { Bundler::Audit::Database::VENDORED_PATH }
|
|
7
7
|
let(:gem) { 'actionpack' }
|
|
8
|
-
let(:id) { '
|
|
8
|
+
let(:id) { 'CVE-2012-3424' }
|
|
9
9
|
let(:path) { File.join(root,'gems',gem,"#{id}.yml") }
|
|
10
10
|
let(:an_unaffected_version) do
|
|
11
11
|
Bundler::Audit::Advisory.load(path).unaffected_versions.map { |version_rule|
|
|
@@ -24,16 +24,45 @@ describe Bundler::Audit::Advisory do
|
|
|
24
24
|
}.flatten.first
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
subject { described_class.load(path) }
|
|
28
|
+
|
|
27
29
|
describe "load" do
|
|
28
30
|
let(:data) { YAML.load_file(path) }
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
describe '#id' do
|
|
33
|
+
subject { super().id }
|
|
34
|
+
it { is_expected.to eq(id) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
describe '#url' do
|
|
38
|
+
subject { super().url }
|
|
39
|
+
it { is_expected.to eq(data['url']) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe '#title' do
|
|
43
|
+
subject { super().title }
|
|
44
|
+
it { is_expected.to eq(data['title']) }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '#date' do
|
|
48
|
+
subject { super().date }
|
|
49
|
+
it { is_expected.to eq(data['date']) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
describe '#cvss_v2' do
|
|
53
|
+
subject { super().cvss_v2 }
|
|
54
|
+
it { is_expected.to eq(data['cvss_v2']) }
|
|
55
|
+
end
|
|
31
56
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
57
|
+
describe '#cvss_v3' do
|
|
58
|
+
subject { super().cvss_v3 }
|
|
59
|
+
it { is_expected.to eq(data['cvss_v3']) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe '#description' do
|
|
63
|
+
subject { super().description }
|
|
64
|
+
it { is_expected.to eq(data['description']) }
|
|
65
|
+
end
|
|
37
66
|
|
|
38
67
|
context "YAML data not representing a hash" do
|
|
39
68
|
it "should raise an exception" do
|
|
@@ -48,45 +77,196 @@ describe Bundler::Audit::Advisory do
|
|
|
48
77
|
subject { described_class.load(path).patched_versions }
|
|
49
78
|
|
|
50
79
|
it "should all be Gem::Requirement objects" do
|
|
51
|
-
subject.all? { |version|
|
|
52
|
-
version.
|
|
53
|
-
}.
|
|
80
|
+
expect(subject.all? { |version|
|
|
81
|
+
expect(version).to be_kind_of(Gem::Requirement)
|
|
82
|
+
}).to be_truthy
|
|
54
83
|
end
|
|
55
84
|
|
|
56
85
|
it "should parse the versions" do
|
|
57
|
-
subject.map(&:to_s).
|
|
86
|
+
expect(subject.map(&:to_s)).to eq(data['patched_versions'])
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe "#cve_id" do
|
|
92
|
+
let(:cve) { "2015-1234" }
|
|
93
|
+
|
|
94
|
+
subject do
|
|
95
|
+
described_class.new.tap do |advisory|
|
|
96
|
+
advisory.cve = cve
|
|
58
97
|
end
|
|
59
98
|
end
|
|
99
|
+
|
|
100
|
+
it "should prepend CVE- to the CVE id" do
|
|
101
|
+
expect(subject.cve_id).to be == "CVE-#{cve}"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
context "when cve is nil" do
|
|
105
|
+
subject { described_class.new }
|
|
106
|
+
|
|
107
|
+
it { expect(subject.cve_id).to be_nil }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe "#osvdb_id" do
|
|
112
|
+
let(:osvdb) { "123456" }
|
|
113
|
+
|
|
114
|
+
subject do
|
|
115
|
+
described_class.new.tap do |advisory|
|
|
116
|
+
advisory.osvdb = osvdb
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it "should prepend OSVDB- to the OSVDB id" do
|
|
121
|
+
expect(subject.osvdb_id).to be == "OSVDB-#{osvdb}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
context "when cve is nil" do
|
|
125
|
+
subject { described_class.new }
|
|
126
|
+
|
|
127
|
+
it { expect(subject.osvdb_id).to be_nil }
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
describe "#ghsa_id" do
|
|
132
|
+
let(:ghsa) { "xfhh-rx56-rxcr" }
|
|
133
|
+
|
|
134
|
+
subject do
|
|
135
|
+
described_class.new.tap do |advisory|
|
|
136
|
+
advisory.ghsa = ghsa
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "should prepend GHSA- to the GHSA id" do
|
|
141
|
+
expect(subject.ghsa_id).to be == "GHSA-#{ghsa}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
context "when ghsa is nil" do
|
|
145
|
+
subject { described_class.new }
|
|
146
|
+
|
|
147
|
+
it { expect(subject.ghsa_id).to be_nil }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "#identifiers" do
|
|
152
|
+
it "should include all identifiers if defined" do
|
|
153
|
+
advisory = described_class.new.tap do |advisory|
|
|
154
|
+
advisory.cve = "2018-1234"
|
|
155
|
+
advisory.osvdb = "2019-2345"
|
|
156
|
+
advisory.ghsa = "2020-3456"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
expect(advisory.identifiers).to eq([
|
|
160
|
+
"CVE-2018-1234",
|
|
161
|
+
"OSVDB-2019-2345",
|
|
162
|
+
"GHSA-2020-3456"
|
|
163
|
+
])
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it "should exclude nil identifiers" do
|
|
167
|
+
advisory = described_class.new
|
|
168
|
+
expect(advisory.identifiers).to eq([])
|
|
169
|
+
|
|
170
|
+
advisory = described_class.new.tap do |advisory|
|
|
171
|
+
advisory.cve = "2018-1234"
|
|
172
|
+
end
|
|
173
|
+
expect(advisory.identifiers).to eq(["CVE-2018-1234"])
|
|
174
|
+
|
|
175
|
+
advisory = described_class.new.tap do |advisory|
|
|
176
|
+
advisory.ghsa = "2020-3456"
|
|
177
|
+
end
|
|
178
|
+
expect(advisory.identifiers).to eq(["GHSA-2020-3456"])
|
|
179
|
+
end
|
|
60
180
|
end
|
|
61
181
|
|
|
62
182
|
describe "#criticality" do
|
|
63
|
-
context "when cvss_v2 is between 0.0 and 3.
|
|
64
|
-
|
|
183
|
+
context "when cvss_v2 is between 0.0 and 3.9" do
|
|
184
|
+
subject do
|
|
185
|
+
described_class.new.tap do |advisory|
|
|
186
|
+
advisory.cvss_v2 = 3.9
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it { expect(subject.criticality).to eq(:low) }
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
context "when cvss_v2 is between 4.0 and 6.9" do
|
|
194
|
+
subject do
|
|
195
|
+
described_class.new.tap do |advisory|
|
|
196
|
+
advisory.cvss_v2 = 6.9
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it { expect(subject.criticality).to eq(:medium) }
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
context "when cvss_v2 is between 7.0 and 10.0" do
|
|
204
|
+
subject do
|
|
205
|
+
described_class.new.tap do |advisory|
|
|
206
|
+
advisory.cvss_v2 = 10.0
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it { expect(subject.criticality).to eq(:high) }
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
context "when cvss_v3 is 0.0" do
|
|
214
|
+
subject do
|
|
215
|
+
described_class.new.tap do |advisory|
|
|
216
|
+
advisory.cvss_v3 = 0.0
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it { expect(subject.criticality).to eq(:none) }
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
context "when cvss_v3 is between 0.1 and 3.9" do
|
|
224
|
+
subject do
|
|
225
|
+
described_class.new.tap do |advisory|
|
|
226
|
+
advisory.cvss_v3 = 3.9
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
it { expect(subject.criticality).to eq(:low) }
|
|
231
|
+
end
|
|
65
232
|
|
|
66
|
-
|
|
233
|
+
context "when cvss_v3 is between 4.0 and 6.9" do
|
|
234
|
+
subject do
|
|
235
|
+
described_class.new.tap do |advisory|
|
|
236
|
+
advisory.cvss_v3 = 6.9
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
it { expect(subject.criticality).to eq(:medium) }
|
|
67
241
|
end
|
|
68
242
|
|
|
69
|
-
context "when
|
|
70
|
-
|
|
243
|
+
context "when cvss_v3 is between 7.0 and 8.9" do
|
|
244
|
+
subject do
|
|
245
|
+
described_class.new.tap do |advisory|
|
|
246
|
+
advisory.cvss_v3 = 8.9
|
|
247
|
+
end
|
|
248
|
+
end
|
|
71
249
|
|
|
72
|
-
|
|
250
|
+
it { expect(subject.criticality).to eq(:high) }
|
|
73
251
|
end
|
|
74
252
|
|
|
75
|
-
context "when
|
|
76
|
-
|
|
253
|
+
context "when cvss_v3 is between 9.0 and 10.0" do
|
|
254
|
+
subject do
|
|
255
|
+
described_class.new.tap do |advisory|
|
|
256
|
+
advisory.cvss_v3 = 10.0
|
|
257
|
+
end
|
|
258
|
+
end
|
|
77
259
|
|
|
78
|
-
|
|
260
|
+
it { expect(subject.criticality).to eq(:critical) }
|
|
79
261
|
end
|
|
80
262
|
end
|
|
81
263
|
|
|
82
264
|
describe "#unaffected?" do
|
|
83
|
-
subject { described_class.load(path) }
|
|
84
|
-
|
|
85
265
|
context "when passed a version that matches one unaffected version" do
|
|
86
266
|
let(:version) { Gem::Version.new(an_unaffected_version) }
|
|
87
267
|
|
|
88
268
|
it "should return true" do
|
|
89
|
-
subject.unaffected?(version).
|
|
269
|
+
expect(subject.unaffected?(version)).to be_truthy
|
|
90
270
|
end
|
|
91
271
|
end
|
|
92
272
|
|
|
@@ -94,19 +274,17 @@ describe Bundler::Audit::Advisory do
|
|
|
94
274
|
let(:version) { Gem::Version.new('3.0.9') }
|
|
95
275
|
|
|
96
276
|
it "should return false" do
|
|
97
|
-
subject.unaffected?(version).
|
|
277
|
+
expect(subject.unaffected?(version)).to be_falsey
|
|
98
278
|
end
|
|
99
279
|
end
|
|
100
280
|
end
|
|
101
281
|
|
|
102
282
|
describe "#patched?" do
|
|
103
|
-
subject { described_class.load(path) }
|
|
104
|
-
|
|
105
283
|
context "when passed a version that matches one patched version" do
|
|
106
284
|
let(:version) { Gem::Version.new('3.1.11') }
|
|
107
285
|
|
|
108
286
|
it "should return true" do
|
|
109
|
-
subject.patched?(version).
|
|
287
|
+
expect(subject.patched?(version)).to be_truthy
|
|
110
288
|
end
|
|
111
289
|
end
|
|
112
290
|
|
|
@@ -114,19 +292,17 @@ describe Bundler::Audit::Advisory do
|
|
|
114
292
|
let(:version) { Gem::Version.new('2.9.0') }
|
|
115
293
|
|
|
116
294
|
it "should return false" do
|
|
117
|
-
subject.patched?(version).
|
|
295
|
+
expect(subject.patched?(version)).to be_falsey
|
|
118
296
|
end
|
|
119
297
|
end
|
|
120
298
|
end
|
|
121
299
|
|
|
122
300
|
describe "#vulnerable?" do
|
|
123
|
-
subject { described_class.load(path) }
|
|
124
|
-
|
|
125
301
|
context "when passed a version that matches one patched version" do
|
|
126
302
|
let(:version) { Gem::Version.new('3.1.11') }
|
|
127
303
|
|
|
128
304
|
it "should return false" do
|
|
129
|
-
subject.vulnerable?(version).
|
|
305
|
+
expect(subject.vulnerable?(version)).to be_falsey
|
|
130
306
|
end
|
|
131
307
|
end
|
|
132
308
|
|
|
@@ -134,17 +310,17 @@ describe Bundler::Audit::Advisory do
|
|
|
134
310
|
let(:version) { Gem::Version.new('2.9.0') }
|
|
135
311
|
|
|
136
312
|
it "should return true" do
|
|
137
|
-
subject.vulnerable?(version).
|
|
313
|
+
expect(subject.vulnerable?(version)).to be_truthy
|
|
138
314
|
end
|
|
139
315
|
|
|
140
316
|
context "when unaffected_versions is not empty" do
|
|
141
317
|
subject { described_class.load(path) }
|
|
142
|
-
|
|
318
|
+
|
|
143
319
|
context "when passed a version that matches one unaffected version" do
|
|
144
320
|
let(:version) { Gem::Version.new(an_unaffected_version) }
|
|
145
321
|
|
|
146
322
|
it "should return false" do
|
|
147
|
-
subject.vulnerable?(version).
|
|
323
|
+
expect(subject.vulnerable?(version)).to be_falsey
|
|
148
324
|
end
|
|
149
325
|
end
|
|
150
326
|
|
|
@@ -152,7 +328,7 @@ describe Bundler::Audit::Advisory do
|
|
|
152
328
|
let(:version) { Gem::Version.new('1.2.3') }
|
|
153
329
|
|
|
154
330
|
it "should return true" do
|
|
155
|
-
subject.vulnerable?(version).
|
|
331
|
+
expect(subject.vulnerable?(version)).to be_truthy
|
|
156
332
|
end
|
|
157
333
|
end
|
|
158
334
|
end
|