contrast-agent 7.1.0 → 7.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/ext/extconf_common.rb +88 -14
  3. data/lib/contrast/agent/assess/policy/source_method.rb +13 -4
  4. data/lib/contrast/agent/assess/policy/trigger_method.rb +12 -18
  5. data/lib/contrast/agent/excluder/excluder.rb +64 -31
  6. data/lib/contrast/agent/protect/rule/base.rb +4 -6
  7. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker.rb +1 -1
  8. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +2 -2
  9. data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +1 -1
  10. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +1 -1
  11. data/lib/contrast/agent/protect/rule/deserialization/deserialization.rb +2 -2
  12. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +1 -1
  13. data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +1 -1
  14. data/lib/contrast/agent/protect/rule/utils/filters.rb +6 -6
  15. data/lib/contrast/agent/protect/rule/xxe/xxe.rb +1 -1
  16. data/lib/contrast/agent/reporting/client/interface.rb +132 -0
  17. data/lib/contrast/agent/reporting/client/interface_base.rb +27 -0
  18. data/lib/contrast/agent/reporting/connection_status.rb +0 -1
  19. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +6 -4
  20. data/lib/contrast/agent/reporting/reporter.rb +11 -26
  21. data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +1 -1
  22. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +10 -3
  23. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +47 -6
  24. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +40 -31
  25. data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +144 -0
  26. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +35 -13
  27. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_mode.rb +14 -1
  28. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +11 -11
  29. data/lib/contrast/agent/request/request.rb +27 -12
  30. data/lib/contrast/agent/telemetry/base.rb +18 -19
  31. data/lib/contrast/agent/telemetry/exception/obfuscate.rb +97 -0
  32. data/lib/contrast/agent/telemetry/exception.rb +1 -0
  33. data/lib/contrast/agent/version.rb +1 -1
  34. data/lib/contrast/components/config/sources.rb +6 -5
  35. data/lib/contrast/components/settings.rb +9 -0
  36. data/lib/contrast/config/diagnostics/source_config_value.rb +5 -1
  37. data/lib/contrast/config/diagnostics/tools.rb +4 -4
  38. data/lib/contrast/config/validate.rb +2 -2
  39. data/lib/contrast/configuration.rb +11 -19
  40. data/lib/contrast/framework/grape/support.rb +1 -2
  41. data/lib/contrast/framework/manager.rb +17 -8
  42. data/lib/contrast/framework/rack/support.rb +99 -1
  43. data/lib/contrast/framework/rails/support.rb +1 -2
  44. data/lib/contrast/framework/sinatra/support.rb +1 -2
  45. data/lib/contrast/logger/aliased_logging.rb +18 -9
  46. data/lib/contrast/utils/hash_utils.rb +21 -2
  47. data/lib/contrast/utils/request_utils.rb +14 -0
  48. data/resources/assess/policy.json +11 -0
  49. metadata +6 -3
  50. data/lib/contrast/agent/reporting/input_analysis/details/bot_blocker_details.rb +0 -27
@@ -0,0 +1,144 @@
1
+ # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Agent
6
+ module Reporting
7
+ # Module to house the Resend logic, when there is no response from TS.
8
+ module Resend
9
+ # How manny times the client will try to rescue from above error before raising an
10
+ # error to reflect the events.
11
+ #
12
+ RESCUE_ATTEMPTS = 3
13
+ TIMEOUT = 5
14
+ RETRY_ERRORS = [
15
+ Net::OpenTimeout, Net::ReadTimeout, ArgumentError, EOFError, OpenSSL::SSL::SSLError, Resolv::ResolvError,
16
+ Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ESHUTDOWN, Errno::EHOSTDOWN, NameError,
17
+ Errno::EHOSTUNREACH, Errno::EISCONN, Errno::ECONNABORTED, Errno::ENETRESET, Errno::ENETUNREACH, SocketError,
18
+ NameError, NoMethodError, Timeout::Error, RuntimeError
19
+ ].cs__freeze
20
+ RESENDING_MESSAGE = '[Reporter] Resending message...'
21
+ END_RESENDING_MESSAGE = '[Reporter] Reporter tried to resend event and received error:'
22
+
23
+ # This method is mainly used in inside error handling and startup mesasges sending.
24
+ #
25
+ # @param event [Contrast::Agent::Reporting::ReportingEvent] event to resend
26
+ # @param connection [Net::HTTP] open connection
27
+ # @param error [StandardError] error that was raised
28
+ # @return [Net::HTTPResponse, nil] response from TS
29
+ def try_resend event, connection, error
30
+ # The timeout for agent to sleep is set here, we don't need to raise an error,
31
+ # but also 15 min is a long time to wait for example if event is startup message.
32
+ # Override timeout
33
+ if Contrast::Agent::Reporting::ReporterClientUtils::STARTUP_EVENTS.include?(event.cs__class)
34
+ response_handler.suspend_reporting(RESENDING_MESSAGE,
35
+ TIMEOUT,
36
+ error,
37
+ event: event,
38
+ log_error: false,
39
+ startup: true)
40
+ end
41
+ mode.enter_resend_mode
42
+ handle_resend(event, connection)
43
+ end
44
+
45
+ # This method will handle the error and will decide if we need to resend the event
46
+ #
47
+ # @param event [Contrast::Agent::Reporting::ReportingEvent]
48
+ # @param connection [Net::HTTP] open connection
49
+ # @return [Net::HTTPResponse, nil] response from TS
50
+ def handle_resend event, connection
51
+ if sleep?
52
+ logger.debug("[Reporter] sleeping for #{ TIMEOUT } seconds before resending...")
53
+ Thread.current.send(:sleep, timeout)
54
+ wake_up
55
+ end
56
+ response = mode.status == mode.resending ? send_event(event, connection) : nil
57
+ response_success! if response
58
+ response
59
+ end
60
+
61
+ # Handles errors that occurs before the ResponseHandler do not have a response, or response
62
+ # code to hande. This Errors requires different handling, because most of errors here
63
+ # occurs when the response cannot be read, there is open ssl error, or a certain Timeout
64
+ # is reached.
65
+ #
66
+ # @param event [Contrast::Agent::Reporting::ReportingEvent]
67
+ # One of the DTMs valid for the event field of Contrast::Api::Dtm::Message
68
+ # @param error [StandardError]
69
+ # @param connection [Net::HTTP] open connection
70
+ # @return nil [NilClass] to be passed as response
71
+ def handle_response_error event, connection, error
72
+ return end_of_rescue(event, error) if mode.resend.rescue_attempts >= RESCUE_ATTEMPTS
73
+
74
+ if RETRY_ERRORS.include?(error.cs__class)
75
+ mode.resend.increase_rescue_attempts
76
+ try_resend(event, connection, error)
77
+ else
78
+ end_of_rescue(event, error)
79
+ end
80
+ end
81
+
82
+ # End of rescue attempts.
83
+ #
84
+ # @param event [Contrast::Agent::Reporting::ReportingEvent]
85
+ # @param error [StandardError]
86
+ # @return [NilClass]
87
+ def end_of_rescue event, error
88
+ if Contrast::Agent::Reporting::ReporterClientUtils::STARTUP_EVENTS.include?(event.cs__class)
89
+ # Agent didn't send it's startup events, There will be reporting errors, disable reporting.
90
+ disable_reporting(event, error)
91
+ else
92
+ # There is an error in one of the reported messages, log that error and continue.
93
+ logger.error(END_RESENDING_MESSAGE,
94
+ resend_attempts: Contrast::Agent::Reporting::Resend::RESCUE_ATTEMPTS,
95
+ connection_error: error,
96
+ client: Contrast::Agent::Reporting::ReporterClient::SERVICE_NAME,
97
+ event_id: event&.__id__,
98
+ event_type: event&.cs__class&.cs__name)
99
+ end
100
+ end
101
+
102
+ # Disable reporting when there is an error that cannot be handled.
103
+ #
104
+ # @param event [Contrast::Agent::Reporting::ReportingEvent]
105
+ # @param error [StandardError]
106
+ # @return [NilClass]
107
+ def disable_reporting event, error
108
+ logger.error('[Agent] Disabling Reporting...', error: error.message, event_type: event.cs__class&.cs__name)
109
+ Contrast::AGENT.disable!
110
+ end
111
+
112
+ # Methods that will initialize the resending state.
113
+ class Status
114
+ # RESEND_STARTUP_EVENTS = 4
115
+
116
+ def initialize
117
+ @_rescue_attempts = 0
118
+ end
119
+
120
+ # return how many times the client has tried to rescue from error.
121
+ #
122
+ # @return [Integer]
123
+ def rescue_attempts
124
+ @_rescue_attempts ||= 0
125
+ end
126
+
127
+ # Reset Error rescues attempts.
128
+ #
129
+ # @return [Integer]
130
+ def reset_rescue_attempts
131
+ @_rescue_attempts = 0
132
+ end
133
+
134
+ # Increase the Error rescues attempts.
135
+ #
136
+ # @return [Integer]
137
+ def increase_rescue_attempts
138
+ @_rescue_attempts += 1
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
@@ -40,7 +40,7 @@ module Contrast
40
40
  assess_on: ::Contrast::ASSESS.enabled?)
41
41
  response
42
42
  rescue StandardError => e
43
- logger.error('Unable to process response from TeamServer', e)
43
+ logger.error('[Reporter] Unable to process response from TeamServer', e)
44
44
  nil
45
45
  end
46
46
 
@@ -63,11 +63,45 @@ module Contrast
63
63
  @_mode ||= Contrast::Agent::Reporting::ResponseHandlerMode.new
64
64
  end
65
65
 
66
+ # Puts the reporting service to sleep
67
+ def put_to_sleep
68
+ @_sleep = true
69
+ end
70
+
66
71
  # Wakes the reporting service
67
72
  def wake_up
68
73
  @_sleep = false
69
74
  end
70
75
 
76
+ # Suspend the reporter and try again in time. If not set
77
+ # the timeout is set to 15 min As default:
78
+ #
79
+ # @param message [String] Message to log.
80
+ # @param timeout [Integer,nil] The timeout to wait and retry after.
81
+ # @param error_message [String, nil] Error message if any received.
82
+ # @param event [Contrast::Agent::Reporting::ReportingEvent, nil] The event sent to TeamServer if set
83
+ # @param log_error [Boolean] Whether to log the error or not.
84
+ # @param startup [Boolean] Whether this is a startup error or not.
85
+ def suspend_reporting message, timeout, error_message, event: nil, log_error: true, startup: false
86
+ @_timeout = timeout || Contrast::Agent::Reporting::ResponseHandler::TIMEOUT
87
+ if log_error
88
+ log_error_msg(message,
89
+ timeout: @_timeout,
90
+ error_message: error_message || 'none',
91
+ event_id: event.nil? ? 'none' : event&.__id__,
92
+ event_type: event.nil? ? 'none' : event&.cs__class&.cs__name)
93
+ end
94
+ if startup
95
+ logger.info(message,
96
+ connection_error: error_message,
97
+ client: Contrast::Agent::Reporting::ReporterClient::SERVICE_NAME,
98
+ event_id: event&.__id__,
99
+ timeout: timeout,
100
+ event_type: event&.cs__class&.cs__name)
101
+ end
102
+ put_to_sleep
103
+ end
104
+
71
105
  private
72
106
 
73
107
  # Handles the errors code received from TS and takes appropriate action.
@@ -93,18 +127,6 @@ module Contrast
93
127
  end
94
128
  end
95
129
 
96
- # Suspend the reporter and try again in time. If not set
97
- # the timeout is set to 15 min As default:
98
- #
99
- # @param message [String] Message to log.
100
- # @param timeout [Integer,nil] The timeout to wait and retry after.
101
- # @param error_message [String, nil] Error message if any received.
102
- def suspend_reporting message, timeout, error_message
103
- @_timeout = timeout || Contrast::Agent::Reporting::ResponseHandler::TIMEOUT
104
- log_error_msg(message, timeout: @_timeout, error_message: error_message || 'none')
105
- @_sleep = true
106
- end
107
-
108
130
  # Log what we've received.
109
131
  #
110
132
  # @param message [String] Message to log
@@ -1,6 +1,8 @@
1
1
  # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'contrast/agent/reporting/reporting_utilities/resend'
5
+
4
6
  module Contrast
5
7
  module Agent
6
8
  module Reporting
@@ -44,6 +46,17 @@ module Contrast
44
46
  @_status ||= running
45
47
  end
46
48
 
49
+ # Resend Status tracker
50
+ #
51
+ # @return [Contrast::Agent::Reporting::Resend::Status]
52
+ def resend
53
+ @_resend ||= Contrast::Agent::Reporting::Resend::Status.new
54
+ end
55
+
56
+ def enter_resend_mode
57
+ @_status = resending
58
+ end
59
+
47
60
  # Set current mode.
48
61
  #
49
62
  # @return [Symbol] Reporter's client and Response Handler
@@ -54,7 +67,7 @@ module Contrast
54
67
 
55
68
  # Reset mode
56
69
  #
57
- def reset_mode
70
+ def enter_run_mode
58
71
  @_status = running
59
72
  end
60
73
  end
@@ -54,6 +54,16 @@ module Contrast
54
54
  @_last_application_modified
55
55
  end
56
56
 
57
+ # Cease reporting about this application
58
+ #
59
+ # @param message [String] Message to log
60
+ # @param info_hash [Hash] information about the context to log.
61
+ def stop_reporting message, info_hash
62
+ Contrast::Agent.reporter&.stop!
63
+ log_error_msg(message, info_hash)
64
+ ::Contrast::AGENT.disable!
65
+ end
66
+
57
67
  private
58
68
 
59
69
  # check if response code is valid before analyze it
@@ -127,7 +137,7 @@ module Contrast
127
137
  # log, suspend, disable:
128
138
  if mode == @_mode.running
129
139
  log_error_msg(message,
130
- response: response.__id__,
140
+ response_id: response.__id__,
131
141
  request: Contrast::Agent::REQUEST_TRACKER.current&.request&.type,
132
142
  error_message: error_message || 'none',
133
143
  auth_error: auth_error || 'none')
@@ -176,16 +186,6 @@ module Contrast
176
186
  end
177
187
  end
178
188
 
179
- # Cease reporting about this application
180
- #
181
- # @param message [String] Message to log
182
- # @param info_hash [Hash] information about the context to log.
183
- def stop_reporting message, info_hash
184
- Contrast::Agent.reporter&.stop!
185
- log_error_msg(message, info_hash)
186
- ::Contrast::AGENT.disable!
187
- end
188
-
189
189
  # Applies the settings from the TS response
190
190
  #
191
191
  # @param response [Contrast::Agent::Reporting::Response]
@@ -10,6 +10,7 @@ require 'contrast/utils/hash_digest'
10
10
  require 'contrast/components/logger'
11
11
  require 'contrast/components/scope'
12
12
  require 'contrast/utils/request_utils'
13
+ require 'contrast/utils/duck_utils'
13
14
 
14
15
  module Contrast
15
16
  module Agent
@@ -24,12 +25,7 @@ module Contrast
24
25
 
25
26
  extend Forwardable
26
27
 
27
- INNER_REST_TOKEN = %r{/\d+/}.cs__freeze
28
- LAST_REST_TOKEN = %r{/\d+$}.cs__freeze
29
- INNER_NUMBER_MARKER = '/{n}/'
30
- LAST_NUMBER_MARKER = '/{n}'
31
- STATIC_SUFFIXES = /\.(?:js|css|jpeg|jpg|gif|png|ico|woff|svg|pdf|eot|ttf|jar)$/i.cs__freeze
32
- MEDIA_TYPE_MARKERS = %w[image/ text/css text/javascript].cs__freeze
28
+ EMPTY_PATH = '/'
33
29
 
34
30
  # @return [Rack::Request] The passed to the Agent RackRequest to be wrapped.
35
31
  attr_reader :rack_request
@@ -75,12 +71,31 @@ module Contrast
75
71
  def normalized_uri
76
72
  @_normalized_uri ||= begin
77
73
  path = rack_request.path_info || rack_request.path.to_s
78
- path = '/' if path.empty?
79
-
80
- uri = path.split(Contrast::Utils::ObjectShare::SEMICOLON)[0] # remove ;jsessionid
81
- uri = uri.split(Contrast::Utils::ObjectShare::QUESTION_MARK)[0] # remove ?query_string=
82
- uri.gsub(INNER_REST_TOKEN, INNER_NUMBER_MARKER) # replace interior tokens
83
- uri.gsub(LAST_REST_TOKEN, LAST_NUMBER_MARKER) # replace last token
74
+ path = EMPTY_PATH if Contrast::Utils::DuckUtils.empty_duck?(path)
75
+
76
+ # /foo/bar;jsessionid=123 => /foo/bar
77
+ uri = path.split(Contrast::Utils::ObjectShare::SEMICOLON)[0]
78
+ # /foo/bar?query_string=123 => /foo/bar
79
+ uri = uri.split(Contrast::Utils::ObjectShare::QUESTION_MARK)[0]
80
+
81
+ # Replace with tokens:
82
+ # NUM_ => '/{n}/'
83
+ # ID_ => '{ID}'
84
+ #
85
+ # replace UUIDs: /123e4567-e89b-42d3-a456-556642440000/ => /{ID}/
86
+ uri.gsub!(UUID_PATTERN, ID_)
87
+ # replace hash patterns: /6f1ed002ab5595859014ebf0951522d9/ => /{ID}/
88
+ uri.gsub!(HASH_PATTERN, ID_)
89
+ # replace windows SID: /S-1-5-21-1843332746-572796286-2118856591-1000/ => /{ID}/
90
+ uri.gsub!(WIN_PATTERN, ID_)
91
+ # replace interior number tokens: /123/ => /{n}/
92
+ uri.gsub!(NUM_PATTERN, NUM_)
93
+ # replace last number tokens: /123 => /{n}
94
+ uri.gsub!(END_PATTERN, NUM_[0..-2])
95
+ uri
96
+ rescue StandardError => e
97
+ logger.error('error normalizing uri', error: e, backtrace: e.backtrace)
98
+ EMPTY_PATH
84
99
  end
85
100
  end
86
101
 
@@ -8,6 +8,7 @@ require 'contrast/agent/thread/worker_thread'
8
8
  require 'contrast/agent/telemetry/telemetry'
9
9
  require 'contrast/agent/telemetry/exception'
10
10
  require 'contrast/utils/job_servers_running'
11
+ require 'contrast/agent/reporting/client/interface'
11
12
 
12
13
  module Contrast
13
14
  module Agent
@@ -96,11 +97,7 @@ module Contrast
96
97
  end
97
98
 
98
99
  def client
99
- @_client ||= Contrast::Agent::Telemetry::Client.new
100
- end
101
-
102
- def connection
103
- @_connection ||= client.initialize_connection(URL)
100
+ @_client ||= Contrast::Agent::Reporting::Telemetry::Interface.new
104
101
  end
105
102
 
106
103
  def attempt_to_start?
@@ -148,16 +145,24 @@ module Contrast
148
145
  Contrast::TELEMETRY_EXCEPTIONS&.clear
149
146
  end
150
147
 
151
- def request_with_response event
152
- client.handle_response(client.send_request(event, connection))
153
- end
154
-
155
148
  private
156
149
 
157
150
  def queue
158
151
  @_queue ||= Queue.new
159
152
  end
160
153
 
154
+ # Starts sending Telemetry messages.
155
+ def process_event event
156
+ logger.debug('[Telemetry] This is the current processed event', event)
157
+ if (sleep_time = client.request_with_response(event))
158
+ return sleep(sleep_time)
159
+ end
160
+
161
+ logger.debug('[Telemetry] Retrying to process event', event)
162
+ retry_sleep_time = client.request_with_response(event)
163
+ sleep(retry_sleep_time) unless retry_sleep_time.nil?
164
+ end
165
+
161
166
  # It is recommended that implementations send a single payload of general metrics every 3 hours, starting from
162
167
  # implementation startup. This returns a thread configured to do so.
163
168
  #
@@ -165,22 +170,16 @@ module Contrast
165
170
  def create_thread
166
171
  Contrast::Agent::Thread.new do
167
172
  loop do
168
- next unless client && connection
173
+ next unless client.connected?
169
174
  break unless attempt_to_start?
170
175
 
171
176
  # Start pushing exceptions to queue for reporting.
172
- Contrast::TELEMETRY_EXCEPTIONS.each_value { |value| queue << value }
173
- Contrast::TELEMETRY_EXCEPTIONS.clear
177
+ Contrast::TELEMETRY_EXCEPTIONS&.each_value { |value| queue << value }
178
+ Contrast::TELEMETRY_EXCEPTIONS&.clear
174
179
  until queue.empty?
175
180
  event = queue.pop
176
181
  begin
177
- logger.debug('[Telemetry] This is the current processed event', event)
178
- if (sleep_time = request_with_response(event))
179
- sleep(sleep_time)
180
- logger.debug('[Telemetry] Retrying to process event', event)
181
- retry_sleep_time = request_with_response(event)
182
- sleep(retry_sleep_time) unless retry_sleep_time.nil?
183
- end
182
+ process_event(event)
184
183
  rescue StandardError => e
185
184
  logger.error('[Telemetry] Could not send message to service from telemetry queue.', e)
186
185
  stop!
@@ -0,0 +1,97 @@
1
+ # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/utils/duck_utils'
5
+ require 'contrast/utils/object_share'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Telemetry
10
+ module Exception
11
+ # Obfuscate sensitive user data before building exception.
12
+ module Obfuscate
13
+ CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'.cs__freeze
14
+ # This will generate different chars for each agent startup but will be constant
15
+ # for current run and will make identical obfuscation to be compared if given type
16
+ # is the same.
17
+ CYPHER = CHARS.chars.shuffle.join.cs__freeze
18
+ VERSION_MATCH = '[^0-9].-'
19
+
20
+ # List of known places after witch a user name might appear:
21
+ KNOWN_DIRS = %w[app application project projects git github users home user].cs__freeze
22
+
23
+ class << self
24
+ # Returns paths for known gems.
25
+ #
26
+ # @return [Array<Regexp>] known paths
27
+ def known_paths
28
+ @_known_paths ||= KNOWN_DIRS.map do |name|
29
+ to_regexp(name)
30
+ end
31
+ end
32
+
33
+ # Obfuscate a type and replace it with random characters.
34
+ #
35
+ # @param path [String] the StackFrame type to obfuscate
36
+ # @return [String] obfuscated type
37
+ def obfuscate_path path
38
+ return path if Contrast::Utils::DuckUtils.empty_duck?(path)
39
+
40
+ cypher(path)
41
+ end
42
+
43
+ private
44
+
45
+ # Transforms string to regexp
46
+ #
47
+ # @param string [String]
48
+ def to_regexp string
49
+ /#{ string }/
50
+ end
51
+
52
+ # Add cypher to path to make it obscure, but unique enough for
53
+ # comparisons. Mutates original or duplicate if frozen string.
54
+ #
55
+ # @param string [String] string to be transformed.
56
+ # @return [String]
57
+ def cypher string
58
+ cypher = string.cs__frozen? ? string.dup : string
59
+ dirs = cypher.split(Contrast::Utils::ObjectShare::SLASH)
60
+ return string unless dirs.cs__is_a?(Array)
61
+
62
+ dirs.each_with_index do |name, idx|
63
+ next if Contrast::Utils::DuckUtils.empty_duck?(name)
64
+ next unless match_known(known_paths,
65
+ name.tr(VERSION_MATCH, Contrast::Utils::ObjectShare::EMPTY_STRING).downcase)
66
+
67
+ obscure(name)
68
+ # obscure username (next dir in line)
69
+ obscure(dirs[idx + 1]) if dirs[idx + 1]
70
+ end
71
+ cypher = dirs.join(Contrast::Utils::ObjectShare::SLASH)
72
+ return cypher if cypher
73
+
74
+ Contrast::Utils::ObjectShare::EMPTY_STRING
75
+ rescue StandardError
76
+ Contrast::Utils::ObjectShare::EMPTY_STRING
77
+ end
78
+
79
+ # @param known [Array<Regexp>] Array of regexp to match against
80
+ # @param type [String] type to check
81
+ def match_known known, type
82
+ known.any? { |regexp| type =~ regexp }
83
+ end
84
+
85
+ # Replaces chars in name.
86
+ #
87
+ # @param [string] name
88
+ # @return [String, nil]
89
+ def obscure name
90
+ name.to_s.tr!(CHARS, CYPHER)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -17,3 +17,4 @@ require 'contrast/agent/telemetry/exception/stack_frame'
17
17
  require 'contrast/agent/telemetry/exception/message_exception'
18
18
  require 'contrast/agent/telemetry/exception/message'
19
19
  require 'contrast/agent/telemetry/exception/event'
20
+ require 'contrast/agent/telemetry/exception/obfuscate'
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '7.1.0'
6
+ VERSION = '7.2.0'
7
7
  end
8
8
  end
@@ -11,14 +11,15 @@ module Contrast
11
11
  # This component encapsulates storing the source for each entry in the config,
12
12
  # so that we can report on where the value was set from.
13
13
  class Sources
14
- ENVIRONMENT_VARIABLE = 'ENV'
15
- COMMAND_LINE = 'CLI'
16
- CONTRAST_UI = 'ContrastUI'
17
- DEFAULT_VALUE = 'Default'
14
+ ENVIRONMENT_VARIABLE = 'ENVIRONMENT_VARIABLE'
15
+ COMMAND_LINE = 'COMMAND_LINE'
16
+ CONTRAST_UI = 'CONTRAST_UI'
17
+ DEFAULT_VALUE = 'DEFAULT_VALUE'
18
+ APP_CONFIGURATION_FILE = 'USER_CONFIGURATION_FILE'
18
19
  # Order matters for the Configurations files. This is read when Agent starts up and will always go
19
20
  # through the YAML as priority.
20
21
  # Do not change the order!
21
- APP_CONFIGURATION_FILE = %w[YAML YML].cs__freeze
22
+ APP_CONFIGURATION_EXTENSIONS = %w[yaml yml].cs__freeze
22
23
 
23
24
  # @return [Hash]
24
25
  attr_reader :data
@@ -5,6 +5,7 @@ require 'contrast/agent/excluder/excluder'
5
5
  require 'contrast/agent/reporting/settings/sensitive_data_masking'
6
6
  require 'contrast/components/config'
7
7
  require 'contrast/components/logger'
8
+ require 'contrast/utils/duck_utils'
8
9
 
9
10
  module Contrast
10
11
  module Components
@@ -219,6 +220,14 @@ module Contrast
219
220
  exclusions.input_exclusions.each do |exclusion|
220
221
  matchers << Contrast::Agent::ExclusionMatcher.new(exclusion)
221
222
  end
223
+ # Do not populate the matchers unless we have any. There are certain checks in
224
+ # SourceMethod that will safe-guard return if there are no exclusions received.
225
+ # The matching operation is expensive, and the excluder calls are made for each
226
+ # source, and we do not want to check for exclusions if they are empty. This is
227
+ # probably redundant as all exclusions default to empty, but will save useless
228
+ # new object creation at very least.
229
+ return if Contrast::Utils::DuckUtils.empty_duck?(matchers)
230
+
222
231
  @excluder = Contrast::Agent::Excluder.new(matchers)
223
232
  end
224
233
 
@@ -41,7 +41,11 @@ module Contrast
41
41
  # @param source [String] name of the source file yaml | yml
42
42
  # @return [Array<String>, nil]
43
43
  def assign_filename source
44
- Contrast::Components::Config::Sources::APP_CONFIGURATION_FILE.each do |type|
44
+ Contrast::Components::Config::Sources::APP_CONFIGURATION_EXTENSIONS.each do |type|
45
+ # We use the source to transfer the file's name from the mapping of the extensions.
46
+ # This is done b/c the user_configuration file has a second field to be filled,
47
+ # and other sources don't. Transfer the source as filename and set the default value
48
+ # for it later when we find the sources.
45
49
  instance_variable_set(:@filename, source) if source.include?(".#{ type.downcase }")
46
50
  end
47
51
  end
@@ -164,10 +164,10 @@ module Contrast
164
164
  # For files we keep the whole path as source.
165
165
  source = Contrast::CONFIG.sources.get(new_effective_value.canonical_name)
166
166
  new_effective_value.assign_filename(source)
167
- new_source = if source.include?(Contrast::Config::LocalSourceValue::YAML_EXT)
168
- Contrast::Components::Config::Sources::APP_CONFIGURATION_FILE[0]
169
- elsif source.include?(Contrast::Config::LocalSourceValue::YML_EXT)
170
- Contrast::Components::Config::Sources::APP_CONFIGURATION_FILE[1]
167
+ new_source = if source.include?(Contrast::Config::LocalSourceValue::YAML_EXT) ||
168
+ source.include?(Contrast::Config::LocalSourceValue::YML_EXT)
169
+
170
+ Contrast::Components::Config::Sources::APP_CONFIGURATION_FILE
171
171
  else
172
172
  Contrast::Components::Config::Sources::DEFAULT_VALUE
173
173
  end
@@ -108,9 +108,9 @@ module Contrast
108
108
 
109
109
  def test_connection reporter
110
110
  puts(" Connection failed #{ FAIL }") unless reporter
111
- connection = reporter.connection
111
+ connection = reporter.client.send(:reporting_connection)
112
112
  abort("Failed to Initialize Connection please check error logs for details #{ FAIL } ") unless connection
113
- abort('Failed to Start Client please check error logs for details') unless reporter.client.startup!(connection)
113
+ abort('Failed to Start Client please check error logs for details') unless reporter.client.startup
114
114
  last_response = reporter.client.response_handler.last_response_code
115
115
  if last_response.include?('40')
116
116
  puts(" Last response code: #{ last_response } #{ FAIL }")