contrast-agent 7.2.0 → 7.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/assess/policy/policy_node.rb +25 -6
  3. data/lib/contrast/agent/assess/policy/propagator/response.rb +64 -0
  4. data/lib/contrast/agent/assess/policy/propagator.rb +1 -0
  5. data/lib/contrast/agent/assess/policy/source_method.rb +5 -0
  6. data/lib/contrast/agent/assess/rule/response/body_rule.rb +22 -7
  7. data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +4 -1
  8. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +62 -23
  9. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +37 -4
  10. data/lib/contrast/agent/protect/rule/base.rb +5 -1
  11. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +27 -11
  12. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +0 -1
  13. data/lib/contrast/agent/protect/rule/cmdi/cmdi_input_classification.rb +2 -2
  14. data/lib/contrast/agent/protect/rule/input_classification/base.rb +191 -0
  15. data/lib/contrast/agent/protect/rule/input_classification/base64_statistic.rb +71 -0
  16. data/lib/contrast/agent/protect/rule/input_classification/cached_result.rb +37 -0
  17. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +109 -0
  18. data/lib/contrast/agent/protect/rule/input_classification/encoding_rates.rb +47 -0
  19. data/lib/contrast/agent/protect/rule/input_classification/extendable.rb +80 -0
  20. data/lib/contrast/agent/protect/rule/input_classification/lru_cache.rb +198 -0
  21. data/lib/contrast/agent/protect/rule/input_classification/match_rates.rb +66 -0
  22. data/lib/contrast/agent/protect/rule/input_classification/rates.rb +53 -0
  23. data/lib/contrast/agent/protect/rule/input_classification/statistics.rb +115 -0
  24. data/lib/contrast/agent/protect/rule/input_classification/utils.rb +23 -0
  25. data/lib/contrast/agent/protect/rule/no_sqli/no_sqli_input_classification.rb +17 -7
  26. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +18 -15
  27. data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +2 -2
  28. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +18 -15
  29. data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +19 -17
  30. data/lib/contrast/agent/reporting/attack_result/attack_result.rb +6 -0
  31. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +2 -7
  32. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +11 -0
  33. data/lib/contrast/agent/reporting/input_analysis/input_type.rb +33 -1
  34. data/lib/contrast/agent/reporting/masker/masker_utils.rb +1 -1
  35. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +1 -0
  36. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -0
  37. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -1
  38. data/lib/contrast/agent/telemetry/base.rb +28 -2
  39. data/lib/contrast/agent/telemetry/base64_hash.rb +55 -0
  40. data/lib/contrast/agent/telemetry/cache_hash.rb +55 -0
  41. data/lib/contrast/agent/telemetry/client.rb +10 -2
  42. data/lib/contrast/agent/telemetry/exception/obfuscate.rb +4 -3
  43. data/lib/contrast/agent/telemetry/{hash.rb → exception_hash.rb} +1 -1
  44. data/lib/contrast/agent/telemetry/identifier.rb +13 -26
  45. data/lib/contrast/agent/telemetry/input_analysis_cache_event.rb +27 -0
  46. data/lib/contrast/agent/telemetry/input_analysis_encoding_event.rb +26 -0
  47. data/lib/contrast/agent/telemetry/input_analysis_event.rb +91 -0
  48. data/lib/contrast/agent/telemetry/metric_event.rb +12 -0
  49. data/lib/contrast/agent/telemetry/startup_metrics_event.rb +0 -8
  50. data/lib/contrast/agent/version.rb +1 -1
  51. data/lib/contrast/components/assess.rb +33 -6
  52. data/lib/contrast/components/base.rb +4 -2
  53. data/lib/contrast/components/config.rb +6 -6
  54. data/lib/contrast/components/protect.rb +11 -1
  55. data/lib/contrast/components/sampling.rb +15 -10
  56. data/lib/contrast/config/diagnostics/command_line.rb +2 -2
  57. data/lib/contrast/config/diagnostics/environment_variables.rb +5 -2
  58. data/lib/contrast/config/diagnostics/tools.rb +15 -5
  59. data/lib/contrast/config/yaml_file.rb +8 -0
  60. data/lib/contrast/configuration.rb +61 -29
  61. data/lib/contrast/framework/rails/support.rb +3 -0
  62. data/lib/contrast/logger/application.rb +3 -3
  63. data/lib/contrast/utils/assess/event_limit_utils.rb +13 -13
  64. data/lib/contrast/utils/assess/propagation_method_utils.rb +2 -0
  65. data/lib/contrast/utils/metrics_hash.rb +1 -1
  66. data/lib/contrast/utils/object_share.rb +2 -1
  67. data/lib/contrast/utils/os.rb +1 -9
  68. data/lib/contrast/utils/response_utils.rb +12 -0
  69. data/lib/contrast/utils/timer.rb +2 -0
  70. data/lib/contrast.rb +9 -2
  71. data/resources/assess/policy.json +80 -3
  72. data/ruby-agent.gemspec +1 -1
  73. metadata +22 -6
  74. data/lib/contrast/utils/input_classification_base.rb +0 -169
@@ -1,7 +1,7 @@
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/utils/input_classification_base'
4
+ require 'contrast/agent/protect/rule/input_classification/base'
5
5
 
6
6
  module Contrast
7
7
  module Agent
@@ -13,28 +13,32 @@ module Contrast
13
13
  REFLECTED_XSS_MATCH = 'reflected-xss-input-tracing-v1'.cs__freeze
14
14
  WORTHWATCHING_MATCH = 'xss-worth-watching-v2'.cs__freeze
15
15
  class << self
16
- include InputClassificationBase
16
+ include Contrast::Agent::Protect::Rule::InputClassification::Base
17
17
 
18
18
  private
19
19
 
20
- # This methods checks if input is tagged WORTHWATCHING or IGNORE matches value with it's
21
- # key if needed and Creates new isntance of InputAnalysisResult.
20
+ # Creates new instance of AgentLib evaluation result with direct call to AgentLib.
22
21
  #
23
- # @param request [Contrast::Agent::Request] the current request context.
24
22
  # @param rule_id [String] The name of the Protect Rule.
25
23
  # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
26
24
  # @param value [String, Array<String>] the value of the input.
27
- #
28
- # @return res [Contrast::Agent::Reporting::InputAnalysisResult]
29
- def create_new_input_result request, rule_id, input_type, value
30
- return unless Contrast::AGENT_LIB
31
-
32
- input_eval = Contrast::AGENT_LIB.eval_input(value,
33
- convert_input_type(input_type),
34
- Contrast::AGENT_LIB.rule_set[rule_id],
35
- Contrast::AGENT_LIB.
36
- eval_option[:PREFER_WORTH_WATCHING])
25
+ def build_input_eval rule_id, input_type, value
26
+ Contrast::AGENT_LIB.eval_input(value,
27
+ Contrast::Agent::Protect::Rule::InputClassification::Base.
28
+ convert_input_type(input_type),
29
+ Contrast::AGENT_LIB.rule_set[rule_id],
30
+ Contrast::AGENT_LIB.
31
+ eval_option[:PREFER_WORTH_WATCHING])
32
+ end
37
33
 
34
+ # Creates specific result from the AgentLib evaluation.
35
+ #
36
+ # @param rule_id [String] The name of the Protect Rule.
37
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
38
+ # @param value [String, Array<String>] the value of the input.
39
+ # @param request [Contrast::Agent::Request] the current request context.
40
+ # @param input_eval [Contrast::AgentLib::EvalResult] the result of the input evaluation.
41
+ def build_ia_result rule_id, input_type, value, request, input_eval
38
42
  score = input_eval&.score || 0
39
43
  ia_result = new_ia_result(rule_id, input_type, request.path, value)
40
44
  if score >= THRESHOLD
@@ -46,8 +50,6 @@ module Contrast
46
50
  else
47
51
  ia_result.score_level = IGNORE
48
52
  end
49
-
50
- add_needed_key(request, ia_result, input_type, value)
51
53
  ia_result
52
54
  end
53
55
  end
@@ -5,6 +5,7 @@ require 'contrast/utils/object_share'
5
5
  require 'contrast/utils/timer'
6
6
  require 'contrast/agent/reporting/attack_result/response_type'
7
7
  require 'contrast/agent/reporting/attack_result/rasp_rule_sample'
8
+ require 'contrast/utils/duck_utils'
8
9
 
9
10
  module Contrast
10
11
  module Agent
@@ -65,6 +66,11 @@ module Contrast
65
66
  def details= protect_details
66
67
  @_details = protect_details if protect_details.is_a?(Contrast::Agent::Reporting::Details::ProtectRuleDetails)
67
68
  end
69
+
70
+ def empty?
71
+ Contrast::Utils::DuckUtils.empty_duck?(samples) || Contrast::Utils::DuckUtils.empty_duck?(rule_id) ||
72
+ response == ::Contrast::Agent::Reporting::ResponseType::NO_ACTION
73
+ end
68
74
  end
69
75
  end
70
76
  end
@@ -9,13 +9,8 @@ module Contrast
9
9
  module Reporting
10
10
  # This class will do ia analysis for our protect rules
11
11
  class InputAnalysis
12
- def inputs
13
- @_inputs
14
- end
15
-
16
- def inputs= extracted_inputs
17
- @_inputs = extracted_inputs
18
- end
12
+ # @return [Hash] Stored request inputs for this context.
13
+ attr_accessor :inputs
19
14
 
20
15
  def triggered_rules
21
16
  @_triggered_rules ||= []
@@ -61,6 +61,17 @@ module Contrast
61
61
  @_key = key if key.is_a?(String)
62
62
  end
63
63
 
64
+ # @param key_type [Symbol]
65
+ # @return @_key [String]
66
+ def key_type= key_type
67
+ @_key_type = key_type if INPUT_TYPE.to_a.include?(key_type)
68
+ end
69
+
70
+ # @return @_key [String]
71
+ def key_type
72
+ @_key_type ||= INPUT_TYPE::UNDEFINED_TYPE
73
+ end
74
+
64
75
  # @return value [String]
65
76
  def value
66
77
  @_value ||= Contrast::Utils::ObjectShare::EMPTY_STRING
@@ -30,13 +30,45 @@ module Contrast
30
30
  UNKNOWN = :UNKNOWN.cs__freeze
31
31
 
32
32
  class << self
33
+ # @return
33
34
  def to_a
34
- [
35
+ @_to_a ||= [
35
36
  UNDEFINED_TYPE, BODY, COOKIE_NAME, COOKIE_VALUE, HEADER, PARAMETER_NAME, PARAMETER_VALUE,
36
37
  QUERYSTRING, URI, SOCKET, JSON_VALUE, JSON_ARRAYED_VALUE, MULTIPART_CONTENT_TYPE, MULTIPART_VALUE,
37
38
  MULTIPART_FIELD_NAME, MULTIPART_NAME, XML_VALUE, DWR_VALUE, METHOD, REQUEST, URL_PARAMETER, UNKNOWN
38
39
  ]
39
40
  end
41
+
42
+ # This is a hash of the input types and their corresponding values.
43
+ #
44
+ # @return [Hash]
45
+
46
+ def to_hash
47
+ {
48
+ UNDEFINED_TYPE: '1',
49
+ BODY: '2',
50
+ COOKIE_NAME: '3',
51
+ COOKIE_VALUE: '4',
52
+ HEADER: '5',
53
+ PARAMETER_NAME: '6',
54
+ PARAMETER_VALUE: '7',
55
+ QUERYSTRING: '8',
56
+ URI: '9',
57
+ SOCKET: '10',
58
+ JSON_VALUE: '11',
59
+ JSON_ARRAYED_VALUE: '12',
60
+ MULTIPART_CONTENT_TYPE: '13',
61
+ MULTIPART_VALUE: '14',
62
+ MULTIPART_FIELD_NAME: '15',
63
+ MULTIPART_NAME: '16',
64
+ XML_VALUE: '17',
65
+ DWR_VALUE: '18',
66
+ METHOD: '19',
67
+ REQUEST: '20',
68
+ URL_PARAMETER: '21',
69
+ UNKNOWN: '22'
70
+ }
71
+ end
40
72
  end
41
73
  end
42
74
  end
@@ -23,7 +23,7 @@ module Contrast
23
23
  hash = URI.decode_www_form(query).to_h
24
24
  mask_with_dictionary(results, hash)
25
25
  # Restore to string form.
26
- hash.each { |k, v| masked += "#{ k }=#{ v }&" }
26
+ hash.each { |k, v| masked += "#{ k }#{ EQUALS }#{ v }#{ AMPERSAND }" }
27
27
  query = masked
28
28
  query.chomp!(masked[-1])
29
29
  end
@@ -40,6 +40,7 @@ module Contrast
40
40
  # @param attack_result [Contrast::Agent::Reporting::AttackResult]
41
41
  def attach_data attack_result
42
42
  return unless attack_result&.cs__is_a?(Contrast::Agent::Reporting::AttackResult)
43
+ return if attack_result&.empty?
43
44
 
44
45
  attacker_activity = Contrast::Agent::Reporting::ApplicationDefendAttackerActivity.new(ia_request: @request)
45
46
  attacker_activity.attach_data(attack_result)
@@ -5,6 +5,7 @@ require 'contrast/components/logger'
5
5
  require 'contrast/utils/object_share'
6
6
  require 'contrast/utils/duck_utils'
7
7
  require 'contrast/agent/reporting/reporting_events/reportable_hash'
8
+ require 'contrast/agent/reporting/attack_result/response_type'
8
9
  require 'contrast/agent/reporting/reporting_events/application_defend_attack_activity'
9
10
 
10
11
  module Contrast
@@ -118,7 +118,7 @@ module Contrast
118
118
  mode.resend.reset_rescue_attempts
119
119
  findings_to_return.each do |index|
120
120
  preflight_message = event.messages[index.to_i]
121
- corresponding_finding = Contrast::Agent::Reporting::ReportingStorage.delete(preflight_message.data)
121
+ corresponding_finding = Contrast::Agent::Reporting::ReportingStorage.delete(preflight_message&.data)
122
122
  next unless corresponding_finding
123
123
 
124
124
  send_event(corresponding_finding, connection)
@@ -143,6 +143,8 @@ module Contrast
143
143
  super
144
144
  delete_queue!
145
145
  Contrast::TELEMETRY_EXCEPTIONS&.clear
146
+ Contrast::TELEMETRY_IA_CACHE&.clear
147
+ Contrast::TELEMETRY_BASE64_HASH&.clear
146
148
  end
147
149
 
148
150
  private
@@ -174,8 +176,7 @@ module Contrast
174
176
  break unless attempt_to_start?
175
177
 
176
178
  # Start pushing exceptions to queue for reporting.
177
- Contrast::TELEMETRY_EXCEPTIONS&.each_value { |value| queue << value }
178
- Contrast::TELEMETRY_EXCEPTIONS&.clear
179
+ gather_telemetry_events
179
180
  until queue.empty?
180
181
  event = queue.pop
181
182
  begin
@@ -189,6 +190,31 @@ module Contrast
189
190
  end
190
191
  end
191
192
  end
193
+
194
+ # Fills the queue with events that were not able to be sent previously.
195
+ def gather_telemetry_events
196
+ gather_exceptions
197
+ gather_encoding_events
198
+ gather_ia_cache_events
199
+ end
200
+
201
+ # Retrieves the exceptions that were accumulated.
202
+ def gather_exceptions
203
+ Contrast::TELEMETRY_EXCEPTIONS&.each_value { |value| queue << value }
204
+ Contrast::TELEMETRY_EXCEPTIONS&.clear
205
+ end
206
+
207
+ # Retrieves the base64 encoded events that were accumulated.
208
+ def gather_encoding_events
209
+ Contrast::TELEMETRY_BASE64_HASH&.each_value { |values| values.each { |event| queue << event } }
210
+ Contrast::TELEMETRY_BASE64_HASH&.clear
211
+ end
212
+
213
+ # Retrieves the IA cache events that were accumulated.
214
+ def gather_ia_cache_events
215
+ Contrast::TELEMETRY_IA_CACHE&.each_value { |values| values.each { |event| queue << event } }
216
+ Contrast::TELEMETRY_IA_CACHE&.clear
217
+ end
192
218
  end
193
219
  end
194
220
  end
@@ -0,0 +1,55 @@
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/agent/telemetry/input_analysis_encoding_event'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Telemetry
9
+ # This hash will store the telemetry data for the Protect InputAnalysis cache.
10
+ class Base64Hash < Hash
11
+ include Contrast::Components::Logger::InstanceMethods
12
+ # Set per request:
13
+ HASH_SIZE_LIMIT = 100
14
+
15
+ # Wrapper to set a value in this Telemetry::Hash only if the provided value is of the data_type for this
16
+ # Telemetry::CacheHash or the hash has not reached its limit for unique keys.
17
+ # Saves Array of reportable events.
18
+ #
19
+ # @param key [Object] the key to which to associate the value
20
+ # @param events [array<Object>]
21
+ # @return [Object, nil] echo back out the value as the Hash#[]= method does, or nil if not of the expected
22
+ # data_type
23
+ def []= key, events
24
+ # If telemetry is not running, do not add more as we want to avoid a memory leak.
25
+ return unless Contrast::Agent.telemetry_queue&.running?
26
+ # If the Hash is full, do not add more as we want to avoid consuming all application resources.
27
+ return if at_limit?
28
+ # If the given value is of unexpected type, do not add it to avoid issues later where type is assumed.
29
+ return unless valid_event?(events)
30
+
31
+ super(key, events)
32
+ end
33
+
34
+ # Determine if hash has reached exception event limit.
35
+ #
36
+ # @return [Boolean]
37
+ def at_limit?
38
+ unless length < HASH_SIZE_LIMIT
39
+ logger.debug("[Telemetry] Number of IA base64 events exceeds limit of #{ HASH_SIZE_LIMIT }")
40
+ return true
41
+ end
42
+ false
43
+ end
44
+
45
+ private
46
+
47
+ # Checks to see if the given object is a valid event.
48
+ # @param events [Contrast::Agent::Telemetry::InputAnalysisEncodingEvent]
49
+ def valid_event? events
50
+ events&.all?(Contrast::Agent::Telemetry::InputAnalysisEncodingEvent)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,55 @@
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/agent/telemetry/input_analysis_cache_event'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Telemetry
9
+ # This hash will store the telemetry data for the Protect InputAnalysis cache.
10
+ class CacheHash < Hash
11
+ include Contrast::Components::Logger::InstanceMethods
12
+ # Set per request:
13
+ HASH_SIZE_LIMIT = 100
14
+
15
+ # Wrapper to set a value in this Telemetry::Hash only if the provided value is of the data_type for this
16
+ # Telemetry::CacheHash or the hash has not reached its limit for unique keys.
17
+ # Saves Array of reportable events.
18
+ #
19
+ # @param key [Object] the key to which to associate the value
20
+ # @param events [array<Object>]
21
+ # @return [Object, nil] echo back out the value as the Hash#[]= method does, or nil if not of the expected
22
+ # data_type
23
+ def []= key, events
24
+ # If telemetry is not running, do not add more as we want to avoid a memory leak.
25
+ return unless Contrast::Agent.telemetry_queue&.running?
26
+ # If the Hash is full, do not add more as we want to avoid consuming all application resources.
27
+ return if at_limit?
28
+ # If the given value is of unexpected type, do not add it to avoid issues later where type is assumed.
29
+ return unless valid_event?(events)
30
+
31
+ super(key, events)
32
+ end
33
+
34
+ # Determine if hash has reached exception event limit.
35
+ #
36
+ # @return [Boolean]
37
+ def at_limit?
38
+ unless length < HASH_SIZE_LIMIT
39
+ logger.debug("[Telemetry] Number of IA cache events exceeds limit of #{ HASH_SIZE_LIMIT }")
40
+ return true
41
+ end
42
+ false
43
+ end
44
+
45
+ private
46
+
47
+ # Checks to see if the given object is a valid event.
48
+ # @param event [Contrast::Agent::Telemetry::InputAnalysisCacheEvent]
49
+ def valid_event? events
50
+ events&.all?(Contrast::Agent::Telemetry::InputAnalysisCacheEvent)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -65,6 +65,7 @@ module Contrast
65
65
  else
66
66
  60
67
67
  end
68
+ logger.debug('[Telemetry] received response.', response_code: status_code)
68
69
  ready_after if status_code == 429
69
70
  end
70
71
 
@@ -76,6 +77,8 @@ module Contrast
76
77
  return true if event.cs__is_a?(Contrast::Agent::Telemetry::Event)
77
78
  return true if event.cs__is_a?(Contrast::Agent::Telemetry::StartupMetricsEvent)
78
79
  return true if event.cs__is_a?(Contrast::Agent::Telemetry::Exception::Event)
80
+ return true if event.cs__is_a?(Contrast::Agent::Telemetry::InputAnalysisCacheEvent)
81
+ return true if event.cs__is_a?(Contrast::Agent::Telemetry::InputAnalysisEncodingEvent)
79
82
 
80
83
  false
81
84
  end
@@ -93,12 +96,17 @@ module Contrast
93
96
  "#{ Contrast::Agent::Telemetry::Base::URL }#{ endpoint }#{ path }"
94
97
  end
95
98
 
96
- # Helper Method to get json representation of Telemetry Event data, handles error on to_json
99
+ # Helper Method to get json representation of Telemetry Event data, handles error on to_json.
100
+ # Generating bodies for exceptions and startup metrics is different.
97
101
  #
98
102
  # @param event [Contrast::Agent::Telemetry::Event, Array<Contrast::Agent::Telemetry::Exception::Event>]
99
103
  # @return [String] - JSON
100
104
  def get_event_json event
101
- Array(event.to_controlled_hash).to_json
105
+ if event.cs__is_a?(Contrast::Agent::Telemetry::Exception::Event)
106
+ return Array(event.to_controlled_hash).to_json
107
+ end
108
+
109
+ [event.to_controlled_hash].to_json
102
110
  rescue Exception => e # rubocop:disable Lint/RescueException
103
111
  logger.error('[Telemetry] Unable to convert TelemetryEvent to JSON string', e, hsh)
104
112
  raise(e)
@@ -16,9 +16,10 @@ module Contrast
16
16
  # is the same.
17
17
  CYPHER = CHARS.chars.shuffle.join.cs__freeze
18
18
  VERSION_MATCH = '[^0-9].-'
19
+ RUBY_EXT = /\.(?:rb|gemspec)$/i
19
20
 
20
21
  # 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
+ KNOWN_DIRS = %w[app application lib project projects git github users home user].cs__freeze
22
23
 
23
24
  class << self
24
25
  # Returns paths for known gems.
@@ -65,8 +66,8 @@ module Contrast
65
66
  name.tr(VERSION_MATCH, Contrast::Utils::ObjectShare::EMPTY_STRING).downcase)
66
67
 
67
68
  obscure(name)
68
- # obscure username (next dir in line)
69
- obscure(dirs[idx + 1]) if dirs[idx + 1]
69
+ # obscure username (next dir in line), skip if it's a file name.
70
+ obscure(dirs[idx + 1]) if dirs[idx + 1] && (dirs[idx + 1] !~ RUBY_EXT)
70
71
  end
71
72
  cypher = dirs.join(Contrast::Utils::ObjectShare::SLASH)
72
73
  return cypher if cypher
@@ -10,7 +10,7 @@ module Contrast
10
10
  # This is the Telemetry::Hash, which will store Contrast::Agent::Telemetry::Exception::Event, so we can push
11
11
  # freely, without worrying about validating the event before that. Telemetry::Hash has a max size of events,
12
12
  # default is 10 events
13
- class Hash < Hash
13
+ class ExceptionHash < Hash
14
14
  include Contrast::Components::Logger::InstanceMethods
15
15
  HASH_SIZE_LIMIT = 10
16
16
 
@@ -12,7 +12,7 @@ module Contrast
12
12
  # Gets info about the instrumented application required to build unique identifiers,
13
13
  # used in the agent's Telemetry.
14
14
  module Identifier
15
- MAC_REGEX = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.cs__freeze
15
+ MAC_REGEXP = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.cs__freeze
16
16
  LINUX_OS_REG = /hwaddr=.*?(([A-F0-9]{2}:){5}[A-F0-9]{2})/im.cs__freeze
17
17
  MAC_OS_PRIMARY = 'en0'.cs__freeze
18
18
  LINUX_PRIMARY = 'enp'.cs__freeze
@@ -87,7 +87,7 @@ module Contrast
87
87
  mac = retrieve_mac(addr)
88
88
  next unless mac
89
89
 
90
- result = mac if mac&.match?(MAC_REGEX)
90
+ result = mac if mac.match?(MAC_REGEXP)
91
91
  break if result
92
92
  end
93
93
  result
@@ -118,33 +118,20 @@ module Contrast
118
118
  nil
119
119
  end
120
120
 
121
- # Returns array of network interfaces.
122
- # This is OS dependent search.
121
+ # Returns array of network interfaces belonging to the expected pfamily of this OS.
123
122
  #
124
- # @return interfaces [Array] Returns an array of interface addresses.
125
- # Socket::Ifaddr - represents a result of getifaddrs().
123
+ # @return interfaces [Array<Socket::Ifaddr>]
126
124
  def interfaces
127
- @_interfaces ||= []
128
-
129
- return @_interfaces unless @_interfaces.empty?
125
+ @_interfaces ||= Socket.getifaddrs.select { |interface| interface.addr&.pfamily == check_family }
126
+ end
130
127
 
131
- arr = Socket.getifaddrs
132
- idx = 0
133
- check_family = 0
134
- while idx < arr.length
135
- # We need only network adapters MACs. Checking for pfamily of every socket address:
136
- # 18 for Mac OS and 17 for Linux.
137
- # family should be an address family such as: :INET, :INET6, :UNIX, etc.
138
- check_family = 18 if Contrast::Utils::OS.mac?
139
- check_family = 17 if Contrast::Utils::OS.linux?
140
- if arr[idx].addr.pfamily != check_family
141
- idx += 1
142
- next
143
- end
144
- @_interfaces << arr[idx]
145
- idx += 1
146
- end
147
- @_interfaces
128
+ # We need only network adapters MACs. Checking for pfamily of every socket address:
129
+ # 18 for Mac OS and 17 for Linux. Family should be an address family such as: :INET, :INET6, :UNIX, etc.
130
+ # It corresponds to the Addrinfo.pfamily value.
131
+ #
132
+ # @return [Integer]
133
+ def check_family
134
+ @_check_family ||= Contrast::Utils::OS.mac? ? 18 : 17
148
135
  end
149
136
  end
150
137
  end
@@ -0,0 +1,27 @@
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/agent/telemetry/input_analysis_event'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Telemetry
9
+ # Event to report all gather information from the Input Analysis Cache statistics, hits and misses.
10
+ class InputAnalysisCacheEvent < Contrast::Agent::Telemetry::InputAnalysisEvent
11
+ NAME = 'InputAnalysis cache event'
12
+ PATH = '/protect_input_analysis_cache'
13
+
14
+ private
15
+
16
+ # Creates the tags for the event
17
+ #
18
+ # @param rule_id [String]
19
+ # @param match_rates [Contrast::Agent::Protect::Rule::InputClassification::MatchRates]
20
+ def add_tags rule_id, match_rates
21
+ super(rule_id, match_rates)
22
+ @tags['score_level'] = match_rates&.score_level.dup&.to_s || NOT_APPLICABLE
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
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/agent/telemetry/input_analysis_event'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Telemetry
9
+ # Event to report all gather information from the Input Analysis Cache statistics, hits and misses.
10
+ class InputAnalysisEncodingEvent < Contrast::Agent::Telemetry::InputAnalysisEvent
11
+ NAME = 'InputAnalysis encoding event'
12
+ PATH = '/protect_input_analysis_encoding'
13
+
14
+ private
15
+
16
+ # Creates the tags for the event
17
+ #
18
+ # @param _rule_id [String]
19
+ # @param encode_rates [Contrast::Agent::Protect::Rule::InputClassification::EncodingRates]
20
+ def add_tags _rule_id, encode_rates
21
+ super(nil, encode_rates)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,91 @@
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/metrics_hash'
5
+ require 'contrast/agent/telemetry/metric_event'
6
+ require 'contrast/agent/version'
7
+ require 'contrast/components/logger'
8
+
9
+ module Contrast
10
+ module Agent
11
+ module Telemetry
12
+ # Event to report all gather information from the Input Analysis metrics.
13
+ class InputAnalysisEvent < Contrast::Agent::Telemetry::MetricEvent
14
+ include Contrast::Components::Logger::InstanceMethods
15
+ NOT_APPLICABLE = 'n/a'
16
+
17
+ attr_reader :fields
18
+
19
+ # Override the name for any derived classes
20
+ NAME = 'InputAnalysis event'
21
+ # Override the path for any derived classes
22
+ PATH = '/protect_input_analysis'
23
+
24
+ # @param rule_id [String] the rule name.
25
+ # @param rates [Contrast::Agent::Protect::Rule::InputClassification::Rates] base class for all rates.
26
+ def initialize rule_id, rates
27
+ super()
28
+ @fields = MetricsHash.new(Integer)
29
+ add_tags(rule_id, rates)
30
+ generate_fields(rates)
31
+ end
32
+
33
+ # Returns the name of the event.
34
+ #
35
+ # @return [String]
36
+ def name
37
+ cs__class::NAME
38
+ end
39
+
40
+ # Returns the path to report the event.
41
+ #
42
+ # @return [String]
43
+ def path
44
+ cs__class::PATH
45
+ end
46
+
47
+ # Override the empty check for any derived classes if needed.
48
+ def empty?
49
+ super && Contrast::Utils::DuckUtils.empty_duck?(@tags)
50
+ end
51
+
52
+ private
53
+
54
+ # Creates the tags for the event. Overrides the base class to add the rule_id and match_rate.
55
+ #
56
+ # @param rule_id [String, nil]
57
+ # @param rates [Contrast::Agent::Protect::Rule::InputClassification::Rates]
58
+ def add_tags rule_id, rates
59
+ return if Contrast::Utils::DuckUtils.empty_duck?(rates)
60
+ return unless rates.cs__is_a?(Contrast::Agent::Protect::Rule::InputClassification::Rates)
61
+
62
+ @tags['event_type'] = name # rubocop:disable Security/Module/Name
63
+ @tags['test_environment'] = ENV['CONTRAST_AGENT_TELEMETRY_TEST'] == '1' ? 'true' : 'false'
64
+ @tags['rule_id'] = rule_id if rule_id
65
+ @tags['input_type'] = rates&.input_type.dup&.to_s || NOT_APPLICABLE
66
+ add_system_tags
67
+ end
68
+
69
+ # Creates the fields for the event.
70
+ # Override if needed.
71
+ #
72
+ # @param rates [Contrast::Agent::Protect::Rule::InputClassification::Rates] base class for all rates.
73
+ def generate_fields rates
74
+ return if Contrast::Utils::DuckUtils.empty_duck?(rates)
75
+ return unless rates.cs__is_a?(Contrast::Agent::Protect::Rule::InputClassification::Rates)
76
+
77
+ rates.to_fields.each { |field, value| @fields[field] = value.dup }
78
+ end
79
+
80
+ # Adds the system tags to the event.
81
+ def add_system_tags
82
+ @tags['agent_version'] = VERSION
83
+ @tags['ruby_version'] = RUBY_VERSION
84
+ @tags['os_type'] = sys_info['os_type'] == 'Darwin' ? 'MacOS' : 'Linux'
85
+ @tags['os_arch'] = sys_info['os_arch']
86
+ @tags['os_version'] = sys_info['os_version']
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end