dobby 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +16 -0
  3. data/.rubocop.yml +30 -0
  4. data/.rubocop_todo.yml +42 -0
  5. data/.travis.yml +12 -0
  6. data/.yardopts +2 -0
  7. data/CHANGELOG.md +8 -0
  8. data/CONTRIBUTING.md +60 -0
  9. data/Gemfile +8 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +103 -0
  12. data/Rakefile +8 -0
  13. data/bin/console +7 -0
  14. data/bin/setup +8 -0
  15. data/config/default.yml +8 -0
  16. data/dobby.gemspec +58 -0
  17. data/lib/dobby.rb +51 -0
  18. data/lib/dobby/builtins.rb +17 -0
  19. data/lib/dobby/cli.rb +64 -0
  20. data/lib/dobby/configuration.rb +58 -0
  21. data/lib/dobby/database.rb +62 -0
  22. data/lib/dobby/defect.rb +74 -0
  23. data/lib/dobby/dpkg.rb +21 -0
  24. data/lib/dobby/error.rb +6 -0
  25. data/lib/dobby/flag_manager.rb +67 -0
  26. data/lib/dobby/flags.yml +8 -0
  27. data/lib/dobby/formatter/abstract_formatter.rb +25 -0
  28. data/lib/dobby/formatter/colorizable.rb +41 -0
  29. data/lib/dobby/formatter/formatter_set.rb +79 -0
  30. data/lib/dobby/formatter/json_formatter.rb +42 -0
  31. data/lib/dobby/formatter/simple_formatter.rb +54 -0
  32. data/lib/dobby/options.rb +149 -0
  33. data/lib/dobby/package.rb +156 -0
  34. data/lib/dobby/package_source/abstract_package_source.rb +17 -0
  35. data/lib/dobby/package_source/dpkg_status_file.rb +85 -0
  36. data/lib/dobby/runner.rb +152 -0
  37. data/lib/dobby/scanner.rb +128 -0
  38. data/lib/dobby/severity.rb +66 -0
  39. data/lib/dobby/strategy.rb +168 -0
  40. data/lib/dobby/update_response.rb +19 -0
  41. data/lib/dobby/version.rb +24 -0
  42. data/lib/dobby/vuln_source/abstract_vuln_source.rb +26 -0
  43. data/lib/dobby/vuln_source/debian.rb +166 -0
  44. data/lib/dobby/vuln_source/ubuntu.rb +229 -0
  45. metadata +45 -1
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ # The Strategy provides base functionality for defining how Dobby takes in
5
+ # the data it needs to do its job. Each strategy should include this mixin
6
+ # to ensure compatibility with the rest of the library.
7
+ #
8
+ # In particular, the mixin provides a DSL for configuring strategies and
9
+ # standardizes some strategy behavior (such as #inspect).
10
+ #
11
+ # Much of this borrowed from Omniauth::Strategy
12
+ # https://github.com/omniauth/omniauth/blob/master/lib/omniauth/strategy.rb
13
+ #
14
+ # @abstract Include as a mixin to implement a Strategy compatible with the
15
+ # library
16
+ module Strategy
17
+ class Options < Hashie::Mash; end
18
+
19
+ def self.included(base)
20
+ Dobby.strategies << base
21
+
22
+ base.extend ClassMethods
23
+ base.class_eval do
24
+ option :setup, false
25
+ option :test_mode, false
26
+ end
27
+ end
28
+
29
+ # Extensible configuration for implementers.
30
+ module ClassMethods
31
+ # An inherited set of default options set at the class-level
32
+ # for each strategy.
33
+ #
34
+ # @return [Options]
35
+ def default_options
36
+ existing = begin
37
+ superclass.default_options
38
+ rescue StandardError
39
+ {}
40
+ end
41
+ @default_options ||= Dobby::Strategy::Options.new(existing)
42
+ end
43
+
44
+ # This allows for more declarative subclassing of strategies by allowing
45
+ # default options to be set using a simple configure call.
46
+ #
47
+ # @param options [Hash] If supplied, these will be the default options
48
+ # (deep-merged into the superclass's default options).
49
+ # @yield [Options] The options Mash that allows you to set your defaults
50
+ # as you'd like.
51
+ #
52
+ # @example Using a yield to configure the default options.
53
+ #
54
+ # class MyStrategy
55
+ # include Dobby::Strategy
56
+ #
57
+ # configure do |c|
58
+ # c.foo = 'bar'
59
+ # end
60
+ # end
61
+ #
62
+ # @example Using a hash to configure the default options.
63
+ #
64
+ # class MyStrategy
65
+ # include Dobby::Strategy
66
+ # configure foo: 'bar'
67
+ def configure(options = nil)
68
+ if block_given?
69
+ yield default_options
70
+ else
71
+ default_options.deep_merge!(options)
72
+ end
73
+ end
74
+
75
+ # Directly declare a default option for your class. This is a useful from
76
+ # a documentation perspective as it provides a simple line-by-line analysis
77
+ # of the kinds of options your strategy provides by default.
78
+ #
79
+ # @param name [Symbol] The key of the default option in your configuration hash.
80
+ # @param value [Object] The value your object defaults to. Nil if not provided.
81
+ #
82
+ # @example
83
+ #
84
+ # class MyStrategy
85
+ # include Dobby::Strategy
86
+ #
87
+ # option :foo, 'bar'
88
+ # end
89
+ def option(name, value = nil)
90
+ default_options[name] = value
91
+ end
92
+
93
+ # Sets (and retrieves) option key names for initializer arguments to be
94
+ # recorded as. This takes care of 90% of the use cases for overriding
95
+ # the initializer in Dobby Strategies. Dobby::Options will also use
96
+ # this, via #cli_options, to configure any command line options.
97
+ def args(args = nil)
98
+ if args
99
+ @args = Array(args)
100
+ return
101
+ end
102
+ existing = begin
103
+ superclass.args
104
+ rescue StandardError
105
+ []
106
+ end
107
+ (instance_variable_defined?(:@args) && @args) || existing
108
+ end
109
+
110
+ # By default, all args are automatically built out as k/v CLI options. For
111
+ # more advanced behavior, override cli_options in the implementing class.
112
+ # The return of this method is passed directly to Dobby::Options#options
113
+ def cli_options
114
+ args.map { |arg| "--#{arg.to_s.tr('_', '-')} VALUE" }
115
+ end
116
+ end
117
+
118
+ attr_reader :options
119
+
120
+ # Initialize the strategy, creating an [Options] hash if the last
121
+ # argument is a Hash.
122
+ #
123
+ # @param args [Hash]
124
+ #
125
+ # @yield [Options]
126
+ def initialize(*args)
127
+ @options = self.class.default_options.dup
128
+ options.deep_merge!(args.pop) if args.last.is_a?(Hash)
129
+ options[:name] ||= self.class.to_s.split('::').last.downcase
130
+
131
+ self.class.args.each do |arg|
132
+ break if args.empty?
133
+
134
+ options[arg] = args.shift
135
+ end
136
+
137
+ raise ArgumentError, "Received too many arguments. #{args.inspect}" unless args.empty?
138
+
139
+ setup
140
+
141
+ yield options if block_given?
142
+ end
143
+
144
+ # Callback placeholder so that 'super' during initialize is unnecessary in
145
+ # implementing classes. This is the other 10% of the use case for overriding
146
+ # initialize.
147
+ def setup; end
148
+
149
+ # @return [String]
150
+ def inspect
151
+ "#<#{self.class}>"
152
+ end
153
+
154
+ # Access to the Dobby logger, automatically prefixed with the
155
+ # strategy's name.
156
+ #
157
+ # @param level [Symbol] syslog level
158
+ # @param message [String]
159
+ #
160
+ # @example
161
+ # log :fatal, 'This is a fatal error.'
162
+ # log :error, 'This is an error.'
163
+ # log :warn, 'This is a warning.'
164
+ def log(level, message)
165
+ Dobby.logger.send(level, "(#{self.class.name}) #{message}")
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ # A generic response format.
5
+ class UpdateResponse
6
+ attr_reader :content
7
+ # @param changed [Boolean]
8
+ # @param content [Hash]
9
+ def initialize(changed, content = nil)
10
+ @changed = changed
11
+ @content = content
12
+ end
13
+
14
+ # @return [Boolean]
15
+ def changed?
16
+ @changed == true
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ # Dobby version information.
5
+ module Version
6
+ STRING = '0.1.2'
7
+ MSG = '%<version>s (AptPkg %<aptpkg_version>s Apt %<apt_version>s '\
8
+ 'libapt %<libapt_version>s) running on %<linux_version>s '\
9
+ '%<ruby_engine>s %<ruby_version>s %<ruby_platform>s'
10
+
11
+ def self.version(debug = false)
12
+ if debug
13
+ format(MSG, version: STRING, aptpkg_version: Debian::AptPkg::VERSION,
14
+ apt_version: Debian::AptPkg::APT_VERSION,
15
+ libapt_version: Debian::AptPkg::LIBAPT_PKG_VERSION,
16
+ linux_version: Etc.uname[:version],
17
+ ruby_engine: RUBY_ENGINE, ruby_version: RUBY_VERSION,
18
+ ruby_platform: RUBY_PLATFORM)
19
+ else
20
+ STRING
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ module VulnSource
5
+ # @abstract Subclass and override {#update} and #{clean} to implement a
6
+ # custom Database source.
7
+ class AbstractVulnSource
8
+ include Dobby::Strategy
9
+
10
+ # Retrieve a source database (if necessary) and parse it.
11
+ #
12
+ # @return [UpdateResponse]
13
+ def update
14
+ raise NotImplementedError
15
+ end
16
+
17
+ # Instruct a strategy to clean up after itself, removing any files it
18
+ # may have created.
19
+ #
20
+ # @return [void]
21
+ def clean
22
+ raise NotImplementedError
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ module VulnSource
5
+ # Vulnerability database source for Debian systems. This uses the JSON file
6
+ # provided by the Debian Security Tracker as its' remote source.
7
+ class Debian < AbstractVulnSource
8
+ DEFAULT_RELEASE = 'jessie'
9
+
10
+ args %i[releases cve_url_prefix dst_json_uri dst_local_file]
11
+
12
+ option :test_mode, false
13
+ option :releases, [DEFAULT_RELEASE]
14
+
15
+ option :dst_json_uri, 'https://security-tracker.debian.org/tracker/data/json'
16
+ option :cve_url_prefix, 'https://security-tracker.debian.org/tracker/'
17
+
18
+ # rubocop:disable Layout/AlignArray
19
+ def self.cli_options
20
+ [
21
+ ['--releases ONE,TWO', 'Limit the packages returned by a VulnSource to',
22
+ 'these releases. Default varies with selected',
23
+ 'VulnSource.'],
24
+ ['--dst-json-uri URI', 'VulnSource::Debian -- specify a URI to the',
25
+ "Debian Security Tracker's JSON file."],
26
+ ['--dst-local-file PATH', 'VulnSource::Debian -- If provided, read from',
27
+ 'the specified file instead of requesting the',
28
+ 'DST json file from a remote.'],
29
+ ['--cve-url-prefix URI', 'URI prefix used for building CVE links.']
30
+ ]
31
+ end
32
+ # rubocop:enable Layout/AlignArray
33
+
34
+ # Map of DST-provided urgencies to a common severity format
35
+ URGENCY_MAP = Hash.new(Severity::Unknown).merge(
36
+ 'not-yet-assigned' => Severity::Unknown,
37
+ 'end-of-life' => Severity::Negligible,
38
+ 'unimportant' => Severity::Negligible,
39
+ 'low' => Severity::Low,
40
+ 'low*' => Severity::Low,
41
+ 'low**' => Severity::Low,
42
+ 'medium' => Severity::Medium,
43
+ 'medium*' => Severity::Medium,
44
+ 'medium**' => Severity::Medium,
45
+ 'high' => Severity::High,
46
+ 'high*' => Severity::High,
47
+ 'high**' => Severity::Critical
48
+ )
49
+
50
+ # Unable to retrieve or load a dobby database
51
+ class NoDataError < Error; end
52
+
53
+ # Received a non-200 response from the Security Tracker
54
+ class BadResponseError < Error
55
+ attr_accessor :curl
56
+
57
+ def initialize(curl)
58
+ url = curl.url
59
+ url_path_only = url =~ /\A([^?]*)\?/ ? Regexp.last_match(1) : url
60
+ super("Bad response code (#{curl.response_code.to_i} for #{url_path_only})")
61
+ @curl = curl
62
+ end
63
+
64
+ # @return [Hash{resp_code=>Integer, url=>String, resp=>String}]
65
+ def context
66
+ { resp_code: @curl.response_code, url: @curl.url, resp: @curl.body_str }
67
+ end
68
+ end
69
+ ###
70
+
71
+ # Initialize callback.
72
+ def setup
73
+ @last_hash = nil
74
+ end
75
+
76
+ # Provide an UpdateResponse sourced from the Debian Security Tracker's
77
+ # JSON. If the SHA256 of the returned JSON matches the last attempt,
78
+ # UpdateResponse.changed? will be false. Otherwise, UpdateResponse.content
79
+ # will be a Hash{package_name=>Array<Defect>}
80
+ #
81
+ # @return [UpdateResponse]
82
+ def update
83
+ data = fetch_from_remote(options.dst_json_uri)
84
+
85
+ hash = Digest::SHA256.hexdigest(data)
86
+ return UpdateResponse.new(false) if hash == @last_hash
87
+
88
+ debian_vulns = Oj.load(data)
89
+
90
+ vuln_entries = Hash.new { |h, k| h[k] = [] }
91
+ debian_vulns.each do |package, vulns|
92
+ vulns.each do |identifier, vuln|
93
+ # If a permanent ID has not been assigned to the vuln, skip it
94
+ next unless identifier.start_with?('CVE-', 'OVE-')
95
+
96
+ severity = Severity::Unknown
97
+ fixed_versions = []
98
+
99
+ vuln['releases'].each do |release, info|
100
+ next unless options.releases.include?(release)
101
+
102
+ version = choose_version(info['fixed_version'], info['status'])
103
+ next unless version
104
+
105
+ # For a given Defect, it may have differing severities across
106
+ # different Debian releases. Set the severity of the Defect to
107
+ # the highest value.
108
+ new_severity = URGENCY_MAP[info['urgency']]
109
+ severity = new_severity if severity < new_severity
110
+
111
+ fixed_versions << Package.new(
112
+ name: package,
113
+ version: version,
114
+ release: release
115
+ )
116
+ end
117
+
118
+ vuln_entries[package] << Defect.new(
119
+ identifier: identifier,
120
+ description: vuln['description'],
121
+ severity: severity,
122
+ fixed_in: fixed_versions,
123
+ link: options.cve_url_prefix + identifier
124
+ )
125
+ end
126
+ end
127
+ @last_hash = hash
128
+ UpdateResponse.new(true, vuln_entries)
129
+ end
130
+
131
+ # Given a 'fixed in' version and a defect status, determine what version
132
+ # represents the status for comparison.
133
+ #
134
+ # @param fixed [String] version that the vuln src says a defect is fixed in
135
+ # @param status [String] the current status of the defect
136
+ #
137
+ # @return [String] version string
138
+ def choose_version(fixed, status)
139
+ return unless status
140
+ return Package::MIN_VERSION if fixed == '0'
141
+ return Package::MAX_VERSION if status == 'open'
142
+ return fixed if status == 'resolved'
143
+
144
+ nil
145
+ end
146
+
147
+ # Retrieve the DST json file
148
+ #
149
+ # @param url [String]
150
+ #
151
+ # @return [String]
152
+ #
153
+ # @raise [BadResponseError] if url returns something other than 200
154
+ # @raise [NoDataError] if url returns no data
155
+ def fetch_from_remote(url)
156
+ return File.read(options.dst_local_file) if options.dst_local_file
157
+
158
+ curl = Curl::Easy.perform(url)
159
+ raise BadResponseError, curl unless curl.response_code.to_i == 200
160
+ raise NoDataError unless curl.body_str
161
+
162
+ curl.body_str
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dobby
4
+ module VulnSource
5
+ # Vulnerability source for Ubuntu systems. This class uses the Ubuntu CVE
6
+ # Tracker as its' remote source by checking out the bazaar repository.
7
+ #
8
+ # @note This requires bazaar to be installed at /usr/bin/bzar unless
9
+ # configured with a different path via the bzr option.
10
+ class Ubuntu < AbstractVulnSource
11
+ DEFAULT_RELEASE = 'xenial'
12
+ args %i[releases cve_url_prefix bzr_bin bzr_repo tracker_repo]
13
+
14
+ option :test_mode, false
15
+ option :releases, [DEFAULT_RELEASE]
16
+
17
+ option :bzr_bin, '/usr/bin/bzr'
18
+ option :cve_url_prefix, 'http://people.ubuntu.com/~ubuntu-security/cve/'
19
+ option :tracker_repo, 'https://launchpad.net/ubuntu-cve-tracker'
20
+
21
+ # rubocop:disable Layout/AlignArray
22
+ def self.cli_options
23
+ [
24
+ ['--releases ONE,TWO', 'Limit the packages returned by a VulnSource to',
25
+ 'these releases. Default vaires with selected',
26
+ 'VulnSource.'],
27
+ ['--bzr-bin PATH', 'VulnSource::Ubuntu - Path to the "bzr" binary.'],
28
+ # ['--bzr-repo PATH', 'Path to the Ubuntu Security bazaar repo on the',
29
+ # 'local system.'],
30
+ ['--tracker-repo URI', 'VulnSource::Ubuntu - Path to the security tracker',
31
+ 'bazaar repository remote.'],
32
+ ['--cve-url-prefix URL', 'URI prefix used for building CVE links.']
33
+ ]
34
+ end
35
+
36
+ # rubocop:enable Layout/AlignArray
37
+ # Map of Canonical-provided urgencies to a common severity format
38
+ URGENCY_MAP = Hash.new(Severity::Unknown).merge(
39
+ 'untriaged' => Severity::Unknown,
40
+ 'negligible' => Severity::Negligible,
41
+ 'low' => Severity::Low,
42
+ 'medium' => Severity::Medium,
43
+ 'high' => Severity::High,
44
+ 'critical' => Severity::Critical
45
+ )
46
+
47
+ # An array of defect states that we are interested in. Skips e.g. ignored/DNE
48
+ RELEVANT_STATUSES = %w[needed active deferred not-affected released].freeze
49
+
50
+ # Line prefixes which indicate the end of a defect description
51
+ DESC_STOP_FIELDS = %w[
52
+ Ubuntu-Description:
53
+ Priority:
54
+ Discovered-By:
55
+ Notes:
56
+ Bugs:
57
+ Assigned-to:
58
+ ].freeze
59
+
60
+ # A hash with DeepMerge
61
+ class VulnerabilityHash < Hash
62
+ include Hashie::Extensions::DeepMerge
63
+ end
64
+
65
+ def setup
66
+ @last_revno = nil
67
+ end
68
+
69
+ # Provide an UpdateReponse sourced from Canoncial's Ubuntu CVE Tracker
70
+ # repository. This is a bazaar repository, and thus this strategy depends
71
+ # on the bzr binary being available. The strategy will avoid descending
72
+ # the repository if the repo's revno matches a previous revno.
73
+ #
74
+ # @return [UpdateResponse]
75
+ def update
76
+ branch_or_pull
77
+ revno = bzr_revno
78
+ return UpdateResponse.new(false) if revno == @last_revno
79
+
80
+ vuln_entries = VulnerabilityHash.new
81
+ modified_entries.each do |file|
82
+ data = parse_ubuntu_cve_file(File.readlines(file))
83
+ vuln_entries.deep_merge!(data)
84
+ end
85
+ @last_revno = revno
86
+ UpdateResponse.new(true, vuln_entries)
87
+ end
88
+
89
+ # Delete the bzr repository
90
+ def clean
91
+ Dir.rmdir(options.local_repo_path)
92
+ end
93
+
94
+ private
95
+
96
+ # Determine whether the repo needs to be branched (because it doesn't
97
+ # exist) or pulled (because it already exists), and then do that.
98
+ #
99
+ # @return [Boolean]
100
+ def branch_or_pull
101
+ if Dir.exist?(options.local_repo_path)
102
+ pull(options.local_repo_path)
103
+ else
104
+ branch(options.local_repo_path)
105
+ end
106
+ end
107
+
108
+ def branch(path)
109
+ FileUtils.mkdir_p path
110
+ Dir.chdir(path) do
111
+ return system(options.bzr_bin.to_s, 'branch', '--use-existing-dir',
112
+ options.tracker_repo.to_s, '.')
113
+ end
114
+ end
115
+
116
+ def pull(path)
117
+ Dir.chdir(path) do
118
+ return system(options.bzr_bin.to_s, 'pull', '--overwrite')
119
+ end
120
+ end
121
+
122
+ # Retrieve bazaar revision number
123
+ #
124
+ # @return [String]
125
+ def bzr_revno
126
+ stdout, = Open3.capture2(options.bzr_bin, 'revno', options.local_repo_path)
127
+ stdout.strip
128
+ end
129
+
130
+ # Returns a list of all interesting files in the bazaar repository.
131
+ #
132
+ # @note The library cannot currently support differential updates :(
133
+ #
134
+ # @return [Array<String>]
135
+ def modified_entries
136
+ search = File.join(options.local_repo_path, '{active,retired}', '**', 'CVE*')
137
+ Dir.glob(search)
138
+ end
139
+
140
+ def parse_ubuntu_cve_file(file_lines)
141
+ entries = Hash.new { |h, k| h[k] = {} }
142
+ fixed_versions = Hash.new { |h, k| h[k] = [] }
143
+ severity = Severity::Unknown
144
+
145
+ identifier = description = link = nil
146
+ more = false
147
+
148
+ file_lines.each do |line|
149
+ line.chomp!
150
+ next if line.start_with?('#') || line.empty?
151
+
152
+ if line.start_with?('Candidate:')
153
+ identifier = line.split[1]
154
+ link = options.cve_url_prefix + identifier
155
+ next
156
+ elsif line.start_with?('Priority:')
157
+ severity = URGENCY_MAP[line.split[1]]
158
+ next
159
+ elsif line.start_with?('Description:')
160
+ more = true
161
+ check = line.split(' ', 2)
162
+ description = check[1] if check.count > 1
163
+ next
164
+ elsif more
165
+ if line.start_with?(*DESC_STOP_FIELDS)
166
+ description.strip!
167
+ more = false
168
+ else
169
+ description = description + ' ' + line.strip
170
+ end
171
+ next
172
+ end
173
+
174
+ # Separate release, package status and version information out of a
175
+ # defect detail line.
176
+ #
177
+ # Example line -
178
+ # xenial_linux: released (4.4.0-81.104)
179
+
180
+ # rubocop:disable Metrics/LineLength
181
+ next unless /(?<release>.*)_(?<package>.*): (?<status>[^\s]*)( \(+(?<note>[^()]*)\)+)?/ =~ line
182
+ # rubocop:enable Metrics/LineLength
183
+
184
+ next unless RELEVANT_STATUSES.include?(status)
185
+
186
+ release = release.split('/')[0]
187
+ next unless options.releases.include?(release)
188
+
189
+ version = choose_version(note, status)
190
+ fixed_versions[package] << Package.new(
191
+ name: package,
192
+ version: version,
193
+ release: release
194
+ )
195
+ end
196
+
197
+ fixed_versions.each do |package, versions|
198
+ entries[package] = Defect.new(
199
+ identifier: identifier,
200
+ description: description,
201
+ severity: severity,
202
+ fixed_in: versions,
203
+ link: link
204
+ )
205
+ end
206
+ entries
207
+ end
208
+
209
+ # Given a 'fixed in' version and a defect status, determine what version
210
+ # represents the status for comparison.
211
+ #
212
+ # If status is released, this simply returns the fixed argument.
213
+ # If status is not-affected, {Package::MIN_VERSION} is returned.
214
+ # If status is some other value, {Package::MAX_VERSION} is returned and the
215
+ # defect is considered to apply to all versions.
216
+ #
217
+ # @param fixed [String] version that the vuln src says a defect is fixed in
218
+ # @param status [String] the current status of the defect
219
+ #
220
+ # @return [String] version string
221
+ def choose_version(fixed, status)
222
+ return fixed if status == 'released'
223
+ return Package::MIN_VERSION if status == 'not-affected'
224
+
225
+ Package::MAX_VERSION
226
+ end
227
+ end
228
+ end
229
+ end