contrast-agent 7.3.1 → 7.4.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.
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