contrast-agent 7.3.1 → 7.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__scope/cs__scope.c +76 -7
  3. data/ext/cs__scope/cs__scope.h +4 -0
  4. data/lib/contrast/agent/inventory/policy/datastores.rb +0 -3
  5. data/lib/contrast/agent/protect/rule/base.rb +5 -1
  6. data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +17 -5
  7. data/lib/contrast/agent/protect/rule/input_classification/base.rb +7 -2
  8. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +1 -1
  9. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +8 -1
  10. data/lib/contrast/agent/protect/rule/sqli/sqli.rb +8 -1
  11. data/lib/contrast/agent/protect/state.rb +110 -0
  12. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +4 -10
  13. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +11 -12
  14. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +6 -29
  15. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -2
  16. data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +2 -2
  17. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +2 -0
  18. data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -0
  19. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +4 -0
  20. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +4 -2
  21. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +2 -0
  22. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +9 -5
  23. data/lib/contrast/agent/reporting/reporting_events/reportable_hash.rb +30 -6
  24. data/lib/contrast/agent/reporting/reporting_utilities/ng_response_extractor.rb +15 -2
  25. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -1
  26. data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +1 -1
  27. data/lib/contrast/agent/reporting/reporting_utilities/response.rb +0 -2
  28. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +4 -5
  29. data/lib/contrast/agent/reporting/settings/protect.rb +61 -18
  30. data/lib/contrast/agent/reporting/settings/sampling.rb +5 -4
  31. data/lib/contrast/agent/reporting/settings/server_features.rb +2 -0
  32. data/lib/contrast/agent/version.rb +1 -1
  33. data/lib/contrast/components/agent.rb +3 -5
  34. data/lib/contrast/components/api.rb +3 -3
  35. data/lib/contrast/components/assess_rules.rb +1 -2
  36. data/lib/contrast/components/base.rb +1 -2
  37. data/lib/contrast/components/config/sources.rb +23 -0
  38. data/lib/contrast/components/logger.rb +19 -0
  39. data/lib/contrast/components/protect.rb +69 -15
  40. data/lib/contrast/components/sampling.rb +5 -12
  41. data/lib/contrast/components/security_logger.rb +17 -0
  42. data/lib/contrast/components/settings.rb +114 -70
  43. data/lib/contrast/config/certification_configuration.rb +1 -1
  44. data/lib/contrast/config/configuration_files.rb +0 -2
  45. data/lib/contrast/config/diagnostics/config.rb +3 -3
  46. data/lib/contrast/config/diagnostics/effective_config.rb +1 -1
  47. data/lib/contrast/config/diagnostics/environment_variables.rb +21 -11
  48. data/lib/contrast/config/diagnostics/monitor.rb +1 -1
  49. data/lib/contrast/config/diagnostics/singleton_tools.rb +170 -0
  50. data/lib/contrast/config/diagnostics/source_config_value.rb +14 -9
  51. data/lib/contrast/config/diagnostics/tools.rb +23 -84
  52. data/lib/contrast/config/request_audit_configuration.rb +1 -1
  53. data/lib/contrast/config/server_configuration.rb +3 -15
  54. data/lib/contrast/configuration.rb +5 -2
  55. data/lib/contrast/framework/manager.rb +4 -3
  56. data/lib/contrast/framework/manager_extend.rb +3 -1
  57. data/lib/contrast/framework/rack/support.rb +11 -2
  58. data/lib/contrast/utils/log_utils.rb +1 -1
  59. data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +0 -3
  60. data/lib/contrast/utils/request_utils.rb +1 -1
  61. data/lib/contrast/utils/timer.rb +1 -1
  62. data/lib/contrast.rb +1 -1
  63. metadata +4 -2
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'contrast/utils/object_share'
5
5
  require 'contrast/config/diagnostics/effective_config_value'
6
+ require 'contrast/config/diagnostics/singleton_tools'
6
7
  require 'contrast/utils/duck_utils'
7
8
 
8
9
  module Contrast
@@ -11,79 +12,11 @@ module Contrast
11
12
  # Diagnostics tools to be included in config components.
12
13
  module Tools
13
14
  CHECK = 'd'
14
- CONTRAST_MARK = 'CONTRAST_'
15
- class << self
16
- # Creates new config instances for each read config entry from the flat generated configs.
17
- #
18
- # @param flats [Array] of flatten configs produced by #flatten_settings
19
- # @param source [Boolean] flag to set the desired value class, it may be a effective or source value.
20
- # @param cli [Boolean] flag to check if the value comes from cli.
21
- # @return [Array<Contrast::Config::Diagnostics::SourceConfigValue>]
22
- def to_config_values flats, source: false, cli: false
23
- config_value_klass = if source
24
- Contrast::Config::Diagnostics::SourceConfigValue
25
- else
26
- Contrast::Config::Diagnostics::EffectiveConfigValue
27
- end
28
- settings = []
29
- flats.each do |entry|
30
- entry.each do |key, value|
31
- efc_value = config_value_klass.new.tap do |config_value|
32
- config_value.canonical_name = Contrast::Utils::ObjectShare::CONTRAST_DOT + key unless cli
33
- if cli && key.to_s.include?(CONTRAST_MARK)
34
- config_value.canonical_name = key.gsub(Contrast::Utils::ObjectShare::DOUBLE_UNDERSCORE,
35
- Contrast::Utils::ObjectShare::PERIOD).downcase
36
- end
37
- config_value.key = key
38
- config_value.value = value_to_s(value)
39
- end
40
- settings << efc_value if efc_value
41
- end
42
- end
43
- settings
44
- end
45
-
46
- # Flattens out the read settings from file, env or contrast ui.
47
- # example: {"agent.polling.server_settings_ms"=>"50000"}
48
- #
49
- # If cli is set we avoid adding the path and additional '.' to the key.
50
- #
51
- # @param data [Hash, nil]
52
- # @param path [String] where to look for settings.
53
- # @param config [Hash] symbolized config to fetch keys from.
54
- # @param cli [Boolean] does the config come from cli.
55
- def flatten_settings data, path = [], config: Contrast::CONFIG.config.loaded_config, cli: false
56
- return [] unless data
57
-
58
- data.each_with_object([]) do |(k, v), entries|
59
- if v.cs__is_a?(Hash)
60
- entries.concat(flatten_settings(v, path.dup.append(k.to_sym)))
61
- else
62
- entries << { k.to_s => config.dig(*path, k).to_s } if cli
63
- entries << { "#{ path.join('.') }.#{ k }" => config.dig(*path, k).to_s } unless cli
64
- end
65
- end.flatten # rubocop:disable Style/MethodCalledOnDoEndBlock
66
- end
67
15
 
68
- # Recursively converts each value to string.
69
- #
70
- # @param value [Hash, nil]
71
- def value_to_s value
72
- return if value.nil?
73
- return value if value.cs__is_a?(String)
74
-
75
- value&.each_with_object({}) do |(k, v), m| # rubocop:disable Style/HashTransformValues
76
- m[k] = if v.cs__is_a?(Hash)
77
- value_to_s(v)
78
- elsif v.cs__is_a?(Array)
79
- v.map(&:to_s)
80
- else
81
- v.to_s
82
- end
83
- end
84
- end
85
- end
16
+ extend Contrast::Config::Diagnostics::SingletonTools
86
17
 
18
+ # TODO: RUBY-2113 deprecate name_prefix
19
+ #
87
20
  # Converts current configuration from array of values to effective config values class and appends them to
88
21
  # EffectiveConfig class. Must be used inside Config Components only.
89
22
  #
@@ -91,13 +24,15 @@ module Contrast
91
24
  # @param config_values [] array of the names of values.
92
25
  # @param canonical_prefix [String] starting of the path to config => api.proxy...
93
26
  # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url
94
- def add_effective_config_values effective_config, config_values, canonical_prefix, name_prefix
27
+ def add_effective_config_values(effective_config,
28
+ config_values,
29
+ canonical_prefix,
30
+ name_prefix = canonical_prefix)
95
31
  return if config_values.to_s.empty?
96
32
 
97
33
  config_values.each do |config_value_name|
98
34
  Contrast::Config::Diagnostics::EffectiveConfigValue.new.tap do |new_effective_value|
99
- next if Contrast::Utils::DuckUtils.empty_duck?((config_value = send(config_value_name.to_sym)))
100
-
35
+ config_value = send(config_value_name.to_sym)
101
36
  fill_effective_value(new_effective_value, config_value, config_value_name, canonical_prefix, name_prefix)
102
37
  effective_config.values << new_effective_value
103
38
  rescue StandardError => e
@@ -107,6 +42,8 @@ module Contrast
107
42
  end
108
43
  end
109
44
 
45
+ # TODO: RUBY-2113 deprecate name_prefix
46
+ #
110
47
  # Converts current configuration from single value to effective config values class and appends them to
111
48
  # EffectiveConfig class. Must be used inside Config Components only.
112
49
  #
@@ -115,10 +52,12 @@ module Contrast
115
52
  # @param config_value [String, Boolean] value of the config.
116
53
  # @param canonical_prefix [String] starting of the path to config => api.proxy...
117
54
  # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url
118
- def add_single_effective_value effective_config, config_name, config_value, canonical_prefix, name_prefix
55
+ def add_single_effective_value(effective_config,
56
+ config_name,
57
+ config_value,
58
+ canonical_prefix,
59
+ name_prefix = canonical_prefix)
119
60
  Contrast::Config::Diagnostics::EffectiveConfigValue.new.tap do |new_effective_value|
120
- break if Contrast::Utils::DuckUtils.empty_duck?(config_value)
121
-
122
61
  fill_effective_value(new_effective_value, config_value, config_name, canonical_prefix, name_prefix)
123
62
  effective_config.values << new_effective_value
124
63
  rescue StandardError => e
@@ -139,7 +78,11 @@ module Contrast
139
78
  # @return filled_new_effective_config [Contrast::Config::Diagnostics::EffectiveConfigValue]
140
79
  def fill_effective_value new_effective_value, config_value, config_value_name, canonical_prefix, name_prefix
141
80
  find_source(new_effective_value, canonical_prefix, assign_name(config_value_name), name_prefix)
142
- new_effective_value.value = config_value
81
+ if Contrast::Config::Diagnostics::SingletonTools::API_CREDENTIALS.include?(config_value_name.to_s)
82
+ new_effective_value.value = Contrast::Configuration::EFFECTIVE_REDACTED
83
+ return new_effective_value
84
+ end
85
+ new_effective_value.value = config_value.cs__is_a?(Array) ? config_value.join(',') : config_value.to_s
143
86
  new_effective_value
144
87
  end
145
88
 
@@ -174,14 +117,10 @@ module Contrast
174
117
  # For files we keep the whole path as source.
175
118
  source = Contrast::CONFIG.sources.get(new_effective_value.canonical_name)
176
119
  new_effective_value.assign_filename(source)
177
- new_source = if source.include?(Contrast::Config::LocalSourceValue::YAML_EXT) ||
178
- source.include?(Contrast::Config::LocalSourceValue::YML_EXT)
179
-
120
+ new_source = if Contrast::CONFIG.sources.configuration_file_source?(new_effective_value.canonical_name)
180
121
  Contrast::Components::Config::Sources::APP_CONFIGURATION_FILE
181
- else
182
- Contrast::Components::Config::Sources::DEFAULT_VALUE
183
122
  end
184
- new_effective_value.source = new_source
123
+ new_effective_value.source = new_source || source
185
124
  new_effective_value
186
125
  end
187
126
 
@@ -50,7 +50,7 @@ module Contrast
50
50
  #
51
51
  # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
52
52
  def to_effective_config effective_config
53
- add_effective_config_values(effective_config, CONFIG_VALUES, CANON_NAME, "#{ CONTRAST }.#{ CANON_NAME }")
53
+ add_effective_config_values(effective_config, CONFIG_VALUES, CANON_NAME)
54
54
  end
55
55
  end
56
56
  end
@@ -54,21 +54,9 @@ module Contrast
54
54
  # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
55
55
  def to_effective_config effective_config
56
56
  super
57
- add_single_effective_value(effective_config,
58
- 'type',
59
- Contrast::APP_CONTEXT.server_type,
60
- CANON_NAME,
61
- "#{ CONTRAST }.#{ CANON_NAME }")
62
- add_single_effective_value(effective_config,
63
- 'name',
64
- Contrast::APP_CONTEXT.server_name,
65
- CANON_NAME,
66
- "#{ CONTRAST }.#{ CANON_NAME }")
67
- add_single_effective_value(effective_config,
68
- 'path',
69
- Contrast::APP_CONTEXT.server_path,
70
- CANON_NAME,
71
- "#{ CONTRAST }.#{ CANON_NAME }")
57
+ add_single_effective_value(effective_config, 'type', Contrast::APP_CONTEXT.server_type, CANON_NAME)
58
+ add_single_effective_value(effective_config, 'name', Contrast::APP_CONTEXT.server_name, CANON_NAME)
59
+ add_single_effective_value(effective_config, 'path', Contrast::APP_CONTEXT.server_path, CANON_NAME)
72
60
  end
73
61
  end
74
62
  end
@@ -63,6 +63,7 @@ module Contrast
63
63
  CONFIG_BASE_PATHS = %w[./ config/ /etc/contrast/ruby/ /etc/contrast/ /etc/].cs__freeze
64
64
  KEYS_TO_REDACT = %i[api_key url service_key user_name].cs__freeze
65
65
  REDACTED = '**REDACTED**'
66
+ EFFECTIVE_REDACTED = '****'
66
67
 
67
68
  DEPRECATED_PROPERTIES = %w[
68
69
  CONTRAST__AGENT__SERVICE__ENABLE CONTRAST__AGENT__SERVICE__LOGGER__LEVEL
@@ -146,8 +147,10 @@ module Contrast
146
147
 
147
148
  paths = []
148
149
  # Environment paths takes precedence here. Look first through them.
149
- paths << ENV['CONTRAST_CONFIG_PATH'] if ENV['CONTRAST_CONFIG_PATH']
150
- paths << ENV['CONTRAST_SECURITY_CONFIG'] if ENV['CONTRAST_SECURITY_CONFIG']
150
+ config_path = ENV.fetch('CONTRAST_CONFIG_PATH', nil)
151
+ security_path = ENV.fetch('CONTRAST_SECURITY_CONFIG', nil)
152
+ paths << config_path if config_path
153
+ paths << security_path if security_path
151
154
 
152
155
  extensions.each do |ext|
153
156
  places = CONFIG_BASE_PATHS.product(["#{ basename }.#{ ext }"])
@@ -29,6 +29,7 @@ module Contrast
29
29
  ].cs__freeze
30
30
 
31
31
  def initialize
32
+ @_frameworks = []
32
33
  return if Contrast::AGENT.disabled? || Contrast::Utils::JobServersRunning.job_servers_running?
33
34
 
34
35
  @_frameworks = SUPPORTED_FRAMEWORKS.map do |framework_klass|
@@ -90,9 +91,7 @@ module Contrast
90
91
  # this particular Request
91
92
  # @return [::Rack::Request] either a rack request or subclass thereof.
92
93
  def retrieve_request env
93
- if Contrast::Utils::DuckUtils.empty_duck?(@_frameworks)
94
- return Contrast::Framework::Rack::Support.retrieve_request(env)
95
- end
94
+ return Contrast::Framework::Rack::Support.retrieve_request(env) if @_frameworks.empty?
96
95
 
97
96
  framework = @_frameworks[0]
98
97
 
@@ -115,6 +114,8 @@ module Contrast
115
114
  # @return [Boolean] true if at least one framework is streaming the response; false if none are streaming
116
115
  def streaming? env
117
116
  result = false
117
+ return result if @_frameworks.empty?
118
+
118
119
  @_frameworks.each do |framework|
119
120
  result = framework.streaming?(env)
120
121
  break if result
@@ -29,7 +29,7 @@ module Contrast
29
29
  # @param method_name [Symbol] the method to call on each FrameworkSupport class
30
30
  # @return [Array]
31
31
  def data_for_all_frameworks method_name
32
- @_frameworks.flat_map { |framework| framework.send(method_name) }.
32
+ @_frameworks&.flat_map { |framework| framework.send(method_name) }&.
33
33
  compact
34
34
  end
35
35
 
@@ -39,6 +39,8 @@ module Contrast
39
39
  # @return [Object] - Determined by method to be invoked
40
40
  def first_framework_result method_name, default_value
41
41
  result = nil
42
+ return default_value if @_frameworks.empty?
43
+
42
44
  @_frameworks.each do |framework|
43
45
  result = framework.send(method_name)
44
46
  break if result
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'contrast/framework/base_support'
5
5
  require 'contrast/framework/rack/patch/support'
6
+ require 'contrast/utils/duck_utils'
6
7
 
7
8
  module Contrast
8
9
  module Framework
@@ -10,6 +11,9 @@ module Contrast
10
11
  # Used when Rack is present to define framework specific behavior. For
11
12
  # now, the only part of this implemented is the Patch Support.
12
13
  module Support
14
+ RACK_REQUEST_PATH = 'REQUEST_PATH'
15
+ RACK_SERVER_NAME = 'SERVER_NAME'
16
+
13
17
  extend Contrast::Framework::BaseSupport
14
18
  extend Contrast::Framework::Rack::Patch::Support
15
19
  class << self
@@ -74,8 +78,13 @@ module Contrast
74
78
  def current_route_coverage request, _controller = nil, full_route = nil
75
79
  method = request.env[::Rack::REQUEST_METHOD] # GET, PUT, POST, etc...
76
80
 
77
- full_route ||= request.env[::Rack::PATH_INFO]
78
- return unless full_route && method
81
+ full_route ||= request.env.fetch(::Rack::PATH_INFO, nil)
82
+ full_route = request.env.fetch(RACK_REQUEST_PATH, nil) if Contrast::Utils::DuckUtils.empty_duck?(full_route)
83
+ return unless method
84
+
85
+ # If we are here and have method but the route is "" we might be expecting the home page.
86
+ full_route = '/' if Contrast::Utils::DuckUtils.empty_duck?(full_route) &&
87
+ request.env.fetch(RACK_SERVER_NAME, nil)
79
88
 
80
89
  route_coverage = Contrast::Agent::Reporting::RouteCoverage.new
81
90
  # We might not have controller, or even if there is defined one, it could not bare the name of the
@@ -12,7 +12,7 @@ module Contrast
12
12
  # Method utility used by Contrast::Logger::log
13
13
  module LogUtils
14
14
  DEFAULT_NAME = 'contrast.log'
15
- DEFAULT_LEVEL = ::Ougai::Logging::Severity::INFO
15
+ DEFAULT_LEVEL = 'INFO'
16
16
  VALID_LEVELS = ::Ougai::Logging::Severity::SEV_LABEL
17
17
  STDOUT_STR = 'STDOUT'
18
18
  STDERR_STR = 'STDERR'
@@ -23,9 +23,6 @@ module Contrast
23
23
  return unless activity
24
24
  return if activity.defend.attackers.empty?
25
25
 
26
- activity_batch.query_count += activity.query_count
27
- activity_batch.routes << activity.routes
28
- activity_batch.routes.flatten!
29
26
  merge_attackers(activity)
30
27
  activity_batch.attach_inventory(activity.inventory) unless activity.inventory.empty?
31
28
  end
@@ -12,7 +12,7 @@ module Contrast
12
12
  NUM_PATTERN = %r{/\d+/}.cs__freeze
13
13
  END_PATTERN = %r{/\d+$}.cs__freeze
14
14
  STATIC_SUFFIXES = /\.(?:js|css|jpeg|jpg|gif|png|ico|woff|svg|pdf|eot|ttf|jar)$/i.cs__freeze
15
- UUID_PATTERN = Regexp.new('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}').cs__freeze # rubocop:disable Metrics/LineLength
15
+ UUID_PATTERN = Regexp.new('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}').cs__freeze # rubocop:disable Layout/LineLength
16
16
  # Regular expression to match any type of hash pattern that is 16 bytes like uuid with no
17
17
  # slashes, md5, sha1, sha256, etc
18
18
  HASH_PATTERN = Regexp.new('([a-fA-F0-9]{2}){16,}').cs__freeze
@@ -29,7 +29,7 @@ module Contrast
29
29
  #
30
30
  # @return[String]
31
31
  def self.time_now
32
- Time.now.utc.iso8601(7)
32
+ Time.now.utc.iso8601(2)
33
33
  end
34
34
 
35
35
  # Converts time given in ms format form TS to HttpDate.
data/lib/contrast.rb CHANGED
@@ -75,7 +75,7 @@ module Contrast # :nodoc:
75
75
  API = CONFIG.api
76
76
  SETTINGS = Contrast::Components::Settings::Interface.new
77
77
  ASSESS = CONFIG.assess
78
- PROTECT = Contrast::Components::Protect::Interface.new
78
+ PROTECT = CONFIG.protect
79
79
  INVENTORY = CONFIG.inventory
80
80
  AGENT = CONFIG.agent
81
81
  RUBY_INTERFACE = AGENT.ruby
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contrast-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.3.1
4
+ version: 7.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - galen.palmer@contrastsecurity.com
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: exe
15
15
  cert_chain: []
16
- date: 2023-08-04 00:00:00.000000000 Z
16
+ date: 2023-09-12 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: bundler
@@ -1058,6 +1058,7 @@ files:
1058
1058
  - lib/contrast/agent/protect/rule/xss/xss.rb
1059
1059
  - lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb
1060
1060
  - lib/contrast/agent/protect/rule/xxe/xxe.rb
1061
+ - lib/contrast/agent/protect/state.rb
1061
1062
  - lib/contrast/agent/reactions/disable_reaction.rb
1062
1063
  - lib/contrast/agent/reporting/attack_result/attack_result.rb
1063
1064
  - lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb
@@ -1253,6 +1254,7 @@ files:
1253
1254
  - lib/contrast/config/diagnostics/effective_config_value.rb
1254
1255
  - lib/contrast/config/diagnostics/environment_variables.rb
1255
1256
  - lib/contrast/config/diagnostics/monitor.rb
1257
+ - lib/contrast/config/diagnostics/singleton_tools.rb
1256
1258
  - lib/contrast/config/diagnostics/source_config_value.rb
1257
1259
  - lib/contrast/config/diagnostics/tools.rb
1258
1260
  - lib/contrast/config/diagnostics/user_configuration_file.rb