contrast-agent 7.2.0 → 7.3.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +62 -23
  3. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +37 -4
  4. data/lib/contrast/agent/protect/rule/base.rb +5 -1
  5. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +27 -11
  6. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +0 -1
  7. data/lib/contrast/agent/protect/rule/cmdi/cmdi_input_classification.rb +2 -2
  8. data/lib/contrast/agent/protect/rule/input_classification/base.rb +191 -0
  9. data/lib/contrast/agent/protect/rule/input_classification/base64_statistic.rb +71 -0
  10. data/lib/contrast/agent/protect/rule/input_classification/cached_result.rb +37 -0
  11. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +109 -0
  12. data/lib/contrast/agent/protect/rule/input_classification/encoding_rates.rb +47 -0
  13. data/lib/contrast/agent/protect/rule/input_classification/extendable.rb +80 -0
  14. data/lib/contrast/agent/protect/rule/input_classification/lru_cache.rb +198 -0
  15. data/lib/contrast/agent/protect/rule/input_classification/match_rates.rb +66 -0
  16. data/lib/contrast/agent/protect/rule/input_classification/rates.rb +53 -0
  17. data/lib/contrast/agent/protect/rule/input_classification/statistics.rb +115 -0
  18. data/lib/contrast/agent/protect/rule/input_classification/utils.rb +23 -0
  19. data/lib/contrast/agent/protect/rule/no_sqli/no_sqli_input_classification.rb +17 -7
  20. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +18 -15
  21. data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +2 -2
  22. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +18 -15
  23. data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +19 -17
  24. data/lib/contrast/agent/reporting/attack_result/attack_result.rb +6 -0
  25. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +2 -7
  26. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +11 -0
  27. data/lib/contrast/agent/reporting/input_analysis/input_type.rb +33 -1
  28. data/lib/contrast/agent/reporting/masker/masker_utils.rb +1 -1
  29. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +1 -0
  30. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -0
  31. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -1
  32. data/lib/contrast/agent/telemetry/base.rb +28 -2
  33. data/lib/contrast/agent/telemetry/base64_hash.rb +55 -0
  34. data/lib/contrast/agent/telemetry/cache_hash.rb +55 -0
  35. data/lib/contrast/agent/telemetry/client.rb +10 -2
  36. data/lib/contrast/agent/telemetry/{hash.rb → exception_hash.rb} +1 -1
  37. data/lib/contrast/agent/telemetry/input_analysis_cache_event.rb +27 -0
  38. data/lib/contrast/agent/telemetry/input_analysis_encoding_event.rb +26 -0
  39. data/lib/contrast/agent/telemetry/input_analysis_event.rb +91 -0
  40. data/lib/contrast/agent/telemetry/metric_event.rb +12 -0
  41. data/lib/contrast/agent/telemetry/startup_metrics_event.rb +0 -8
  42. data/lib/contrast/agent/version.rb +1 -1
  43. data/lib/contrast/components/config.rb +4 -4
  44. data/lib/contrast/components/protect.rb +11 -1
  45. data/lib/contrast/components/sampling.rb +15 -10
  46. data/lib/contrast/config/diagnostics/environment_variables.rb +3 -1
  47. data/lib/contrast/config/yaml_file.rb +8 -0
  48. data/lib/contrast/framework/rails/support.rb +3 -0
  49. data/lib/contrast/utils/assess/event_limit_utils.rb +13 -13
  50. data/lib/contrast/utils/metrics_hash.rb +1 -1
  51. data/lib/contrast/utils/object_share.rb +2 -1
  52. data/lib/contrast/utils/response_utils.rb +12 -0
  53. data/lib/contrast/utils/timer.rb +2 -0
  54. data/lib/contrast.rb +9 -2
  55. data/ruby-agent.gemspec +1 -1
  56. metadata +21 -6
  57. data/lib/contrast/utils/input_classification_base.rb +0 -169
@@ -0,0 +1,37 @@
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/agent/reporting/input_analysis/input_analysis_result'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Protect
10
+ module InputClassification
11
+ # This Class with store the input classification results for a given user input.
12
+ class CachedResult
13
+ # @return [String]
14
+ attr_reader :result
15
+ # @return [Integer]
16
+ attr_reader :request_id
17
+
18
+ # Initialize Input Classification Cached Result
19
+ #
20
+ # @param result [Contrast::Agent::Reporting::InputAnalysisResult]
21
+ # @param request_id [Integer] the id of current request.
22
+ def initialize result, request_id
23
+ @result = result.dup if result&.cs__is_a?(Contrast::Agent::Reporting::InputAnalysisResult)
24
+ @request_id = request_id
25
+ end
26
+
27
+ # Check if the input classification result is empty.
28
+ #
29
+ # @return [Boolean]
30
+ def empty?
31
+ Contrast::Utils::DuckUtils.empty_duck?(@result) && Contrast::Utils::DuckUtils.empty_duck?(@request_id)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,109 @@
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 'base64'
5
+ require 'cgi'
6
+ require 'uri'
7
+
8
+ module Contrast
9
+ module Agent
10
+ module Protect
11
+ module Rule
12
+ module InputClassification
13
+ # Module to hold different encoding utils.
14
+ module Encoding
15
+ include Contrast::Components::Logger::InstanceMethods
16
+
17
+ # Still a list is needed for this one, as it is not possible to determine if the value is encoded or not.
18
+ # As long as the list is short the method has a good percentage of success.
19
+ KNOWN_DECODING_EXCEPTIONS = %w[cmd].cs__freeze
20
+
21
+ # This methods is not performant, but is more safe for false positive.
22
+ # Base64 check is no trivial task. For example if one passes a value like 'stringdw' it will return true,
23
+ # or value 'pass', but it is indeed not encoded. using regexp like:
24
+ #
25
+ # ^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$
26
+ #
27
+ # This will fail with any inputs from above, and this is because any characters with 4 bytes will be
28
+ # considered as base64 encoded, and without additional context it is impossible to determine if the
29
+ # value is encoded or not. Not to mention the above regexp will not detect empty spaces.
30
+ #
31
+ # Alternative to the above regexp, acting the same way, could be this:
32
+ #
33
+ # Base64.strict_encode64(Base64.decode64(value)) == value
34
+ #
35
+ # Making an exception list is not a good idea, because it will be hard to maintain.
36
+ #
37
+ # The Base64 method will return printable ascii characters, so we can use this to determine if the value is
38
+ # encoded or not.
39
+ #
40
+ # The solution in this case is encodind the value, and then decoding it. If the value is already encoded
41
+ # it will not be eq to the original value. If the value is not encoded, it will be eq to the original value.
42
+ #
43
+ # @param value [String] input to check for encoding status.
44
+ # @param input_type [Symbol] input type.
45
+ # @return [Boolean] true if value is base64 encoded, false otherwise.
46
+ def cs__base64? value, input_type
47
+ return false unless value.is_a?(String)
48
+ return false if Contrast::Utils::DuckUtils.empty_duck?(value)
49
+
50
+ # Encoded string levels of decoding example:
51
+ #
52
+ # Value encoded 'pass' => 'cGFzcw=='
53
+ # decode level 0 => 'pass'
54
+ # decode level 1 => '\xA5\xAB,'
55
+ # decode level 2 => ''
56
+ check_value = value.dup
57
+ return false if KNOWN_DECODING_EXCEPTIONS.include?(check_value)
58
+
59
+ level = 0
60
+ iteration = 0
61
+ until Contrast::Utils::DuckUtils.empty_duck?(Base64.decode64(check_value))
62
+ iteration += 1
63
+ # handle cases like 'command' or 'injection' which will check out as encoded regarding the level of
64
+ # decoding, but will produce ascii escape characters on first iteration, rather than decoded value.
65
+ level += 1 unless iteration == 2 && ::CGI.escape(check_value) != check_value
66
+
67
+ check_value = Base64.decode64(check_value)
68
+ end
69
+
70
+ # if we have more than 2 levels the value is encoded.
71
+ base64 = level > 1
72
+
73
+ # Call base64 statistics:
74
+ if base64
75
+ Contrast::Agent::Protect::InputAnalyzer.base64_statistic.match!(input_type)
76
+ else
77
+ Contrast::Agent::Protect::InputAnalyzer.base64_statistic.mismatch!(input_type)
78
+ end
79
+
80
+ base64
81
+ rescue StandardError => e
82
+ logger.error('Error while checking for base64 encoding',
83
+ error: e,
84
+ message: e.message,
85
+ backtrace: e.backtrace)
86
+ false
87
+ end
88
+
89
+ # This method will decode the value using Base64.decode64, only if value was encoded.
90
+ # If value is not encoded, it will return the original value.
91
+ #
92
+ # @param value [String] input to decode.
93
+ # @param input_type [Symbol] input type.
94
+ # @return [String] decoded or original value.
95
+ # @raise [StandardError]
96
+ def cs__decode64 value, input_type
97
+ return value unless cs__base64?(value, input_type)
98
+
99
+ Base64.decode64(value)
100
+ rescue StandardError => e
101
+ logger.error('Error while decoding base64', error: e, message: e.message, backtrace: e.backtrace)
102
+ value
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,47 @@
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/protect/rule/input_classification/rates'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Protect
9
+ module Rule
10
+ module InputClassification
11
+ # This class will hold match information when input classification is being saved in LRU cache.
12
+ class EncodingRates < Contrast::Agent::Protect::Rule::InputClassification::Rates
13
+ # @return [Integer]
14
+ attr_reader :base64_matches
15
+ # @return [Integer]
16
+ attr_reader :base64_mismatches
17
+
18
+ def initialize input_type
19
+ super(nil, input_type)
20
+ @base64_matches = 0
21
+ @base64_mismatches = 0
22
+ end
23
+
24
+ # Increase the match count for the given rule_id and input.
25
+ def increase_match_base64
26
+ @base64_matches += 1
27
+ end
28
+
29
+ def increase_mismatch_base64
30
+ @base64_mismatches += 1
31
+ end
32
+
33
+ # Returns Agent Telemetry reportable fields.
34
+ #
35
+ # @return [Hash]
36
+ def to_fields
37
+ {
38
+ "#{ to_field_title }.input_base64_matches" => base64_matches,
39
+ "#{ to_field_title }.input_base64_mismatches" => base64_mismatches
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,80 @@
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/reporting/input_analysis/input_type'
5
+ require 'contrast/agent/reporting/input_analysis/score_level'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Protect
10
+ module Rule
11
+ module InputClassification
12
+ # Module holding the overwritable methods for input classification. This is used by the
13
+ # Protect rules to define their own input classification logic. To be Used input_types,
14
+ # score_level, AgentLib, and InputAnalysisResult must be required.
15
+ module Extendable
16
+ THRESHOLD = 90.cs__freeze
17
+ WORTHWATCHING_THRESHOLD = 10.cs__freeze
18
+ include Contrast::Agent::Reporting::InputType
19
+ include Contrast::Agent::Reporting::ScoreLevel
20
+
21
+ ################################################################
22
+ # Methods to be overwritten for each individual Protect rule. #
23
+ ##############################################################
24
+
25
+ # Creates new instance of AgentLib evaluation result with direct call to AgentLib.
26
+ #
27
+ # @param rule_id [String] The name of the Protect Rule.
28
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
29
+ # @param value [String] the value of the input.
30
+ # @return [Contrast::AgentLib::EvalResult, nil] the result of the input evaluation.
31
+ def build_input_eval rule_id, input_type, value
32
+ Contrast::AGENT_LIB.eval_input(value,
33
+ Contrast::Agent::Protect::Rule::InputClassification::Base.
34
+ convert_input_type(input_type),
35
+ Contrast::AGENT_LIB.rule_set[rule_id],
36
+ Contrast::AGENT_LIB.eval_option[:PREFER_WORTH_WATCHING])
37
+ end
38
+
39
+ # Creates specific result from the AgentLib evaluation.
40
+ #
41
+ # @param rule_id [String] The name of the Protect Rule.
42
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
43
+ # @param value [String the value of the input.
44
+ # @param request [Contrast::Agent::Request] the current request context.
45
+ # @param input_eval [Contrast::AgentLib::EvalResult] the result of the input evaluation.
46
+ # @return [Contrast::Agent::Reporting::InputAnalysisResult, nil] the result of the input analysis.
47
+ def build_ia_result rule_id, input_type, value, request, input_eval
48
+ ia_result = new_ia_result(rule_id, input_type, request.path, value)
49
+ score = input_eval&.score || 0
50
+ if score >= WORTHWATCHING_THRESHOLD
51
+ ia_result.score_level = WORTHWATCHING
52
+ ia_result.ids << self::WORTHWATCHING_MATCH
53
+ else
54
+ ia_result.score_level = IGNORE
55
+ end
56
+ ia_result
57
+ end
58
+
59
+ # Creates new isntance of InputAnalysisResult with basic info.
60
+ #
61
+ # @param rule_id [String] The name of the Protect Rule.
62
+ # @param input_type [Contrast::Agent::Reporting::InputType] The type of the user input.
63
+ # @param value [String, Array<String>] the value of the input.
64
+ # @param path [String] the path of the current request context.
65
+ #
66
+ # @return res [Contrast::Agent::Reporting::InputAnalysisResult]
67
+ def new_ia_result rule_id, input_type, path, value = nil
68
+ res = Contrast::Agent::Reporting::InputAnalysisResult.new
69
+ res.rule_id = rule_id
70
+ res.input_type = input_type
71
+ res.path = path
72
+ res.value = value
73
+ res
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,198 @@
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/lru_cache'
5
+ require 'contrast/utils/duck_utils'
6
+ require 'contrast/agent/protect/rule/input_classification/cached_result'
7
+ require 'contrast/agent/protect/rule/input_classification/statistics'
8
+ require 'contrast/agent/protect/rule/input_classification/utils'
9
+
10
+ module Contrast
11
+ module Agent
12
+ module Protect
13
+ module Rule
14
+ module InputClassification
15
+ # This Class with store the input classification results for a given user input.
16
+ # Among the most used inputs are the headers values for session_id and path values.
17
+ class LRUCache < Contrast::Utils::LRUCache
18
+ include Contrast::Components::Logger::InstanceMethods
19
+ include Contrast::Agent::Protect::Rule::InputClassification::Utils
20
+
21
+ # Protect rules will always be fixed number, on other hand the number of inputs will grow,
22
+ # we need to limit the number of inputs to be cached.
23
+ RESULTS_CAPACITY = 20
24
+
25
+ private :[], :[]=
26
+
27
+ # Initialize the LRU Cache.
28
+ #
29
+ # @param capacity [Integer] the maximum number of elements to store in the cache. For this
30
+ # instance it will never reach outside of the number of supported Protect rules.
31
+ def initialize capacity = 10
32
+ super(capacity)
33
+ end
34
+
35
+ def mutex
36
+ @_mutex ||= Mutex.new
37
+ end
38
+
39
+ def with_mutex &block
40
+ return_type = mutex.synchronize(&block)
41
+ ensure
42
+ return_type
43
+ end
44
+
45
+ # Capacity of the statistics will always be the number of rule_id Protect supports.
46
+ #
47
+ # @return [Contrast::Agent::Protect::Rule::InputClassification::Statistics]
48
+ def statistics
49
+ @_statistics ||= Contrast::Agent::Protect::Rule::InputClassification::Statistics.new
50
+ end
51
+
52
+ # Clear the cache and statistics.
53
+ def clear
54
+ with_mutex { @cache.clear }
55
+ clear_statistics
56
+ end
57
+
58
+ # Clear only the statistics.
59
+ def clear_statistics
60
+ with_mutex { statistics.send(:clear) }
61
+ end
62
+
63
+ # Check if the cache is empty.
64
+ #
65
+ # @return [Boolean]
66
+ def empty?
67
+ Contrast::Utils::DuckUtils.empty_duck?(@cache)
68
+ end
69
+
70
+ # Check if the input is cached and returns it if so and record the statistics for the required input.
71
+ #
72
+ # @param rule_id [String] the Protect rule name.
73
+ # @param input [String] the user input.
74
+ # @param input_type [Symbol] Type of the input
75
+ # @param request [Contrast::Agent::Request] the current request.
76
+ # @return [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
77
+ def lookout rule_id, input, input_type, request
78
+ with_mutex { _loockout(rule_id, input, input_type, request) }
79
+ end
80
+
81
+ # Save the input classification result for a given user input.
82
+ #
83
+ # @param rule_id [String] the Protect rule name.
84
+ # @param result [Contrast::Agent::Reporting::InputAnalysisResult]
85
+ # @param request [Contrast::Agent::Request] the current request.
86
+ # @return result [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
87
+ def save rule_id, result, request
88
+ with_mutex { _save(rule_id, result, request) }
89
+ end
90
+
91
+ private
92
+
93
+ # Check if the input is cached and returns it if so and record the statistics for the required input.
94
+ #
95
+ # @param rule_id [String] the Protect rule name.
96
+ # @param input [String] the user input.
97
+ # @param input_type [Symbol] Type of the input
98
+ # @param request [Contrast::Agent::Request] the current request.
99
+ # @return [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
100
+ def _loockout rule_id, input, input_type, request
101
+ cached = retrieve(rule_id, input, input_type)
102
+ if cached.cs__is_a?(Contrast::Agent::Protect::InputClassification::CachedResult)
103
+ # Telemetry event matched.
104
+ statistics.match!(rule_id, cached, request)
105
+ cached
106
+ else
107
+ # Telemetry event mismatched.
108
+ statistics.mismatch!(rule_id, input_type)
109
+ nil
110
+ end
111
+ rescue StandardError => e
112
+ logger.error("[IA_LRU_Cache] Error while looking for #{ input_type } for #{ rule_id }", error: e)
113
+ nil
114
+ end
115
+
116
+ # Save the input classification result for a given user input.
117
+ #
118
+ # @param rule_id [String] the Protect rule name.
119
+ # @param result [Contrast::Agent::Reporting::InputAnalysisResult]
120
+ # @param request [Contrast::Agent::Request] the current request.
121
+ # @return result [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
122
+ def _save rule_id, result, request
123
+ cached_result = Contrast::Agent::Protect::InputClassification::CachedResult.new(result, request.__id__)
124
+ new_entry = safe_extract(push(rule_id, cached_result)) unless cached_result.empty?
125
+ statistics.push(rule_id, cached_result)
126
+
127
+ new_entry
128
+ rescue StandardError => e
129
+ logger.error("[IA_LRU_Cache] Error while saving #{ result } for #{ rule_id }",
130
+ error: e,
131
+ stack_trace: e.backtrace)
132
+ end
133
+
134
+ # @param rule_id [String] the Protect rule name.
135
+ # @param result [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
136
+ # @return [Array<Contrast::Agent::Protect::InputClassification::CachedResult>, nil]
137
+ def push rule_id, result
138
+ return unless result.cs__is_a?(Contrast::Agent::Protect::InputClassification::CachedResult)
139
+ return @cache[rule_id] = [result] unless key?(rule_id)
140
+
141
+ cached_results = @cache[rule_id]
142
+ cached_results.shift if cached_results.length >= RESULTS_CAPACITY
143
+ return @cache[rule_id] << result unless already_saved?(result, rule_id)
144
+
145
+ nil
146
+ end
147
+
148
+ # Returns the cached result for a given user input. If the input is not cached, it will return nil.
149
+ # If the input is cached and key is needed, it will return the cached result and the key and key_type.
150
+ #
151
+ # @param rule_id [String] the Protect rule name.
152
+ # @param input [String] the user input.
153
+ # @param input_type [Symbol] Type of the input
154
+ # @return [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
155
+ def retrieve rule_id, input, input_type
156
+ # Check to see if cache exist if not just return the keys info.
157
+ return unless key?(rule_id)
158
+
159
+ safe_extract(query_fetch(rule_id, input, input_type))
160
+ end
161
+
162
+ # returns true on first input already saved with the same input.
163
+ # We will know if the key is needed from the result itself.
164
+ #
165
+ # @param cached_result [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
166
+ # @param rule_id [String]
167
+ # @return [Boolean]
168
+ def already_saved? cached_result, rule_id
169
+ return false unless cached_result.cs__is_a?(Contrast::Agent::Protect::InputClassification::CachedResult)
170
+
171
+ @cache[rule_id].any? do |entry|
172
+ if entry.result.value == cached_result.result.value &&
173
+ entry.result.input_type == cached_result.result.input_type
174
+
175
+ return true
176
+ end
177
+ end
178
+ false
179
+ end
180
+
181
+ # Returns first match for the required input.
182
+ #
183
+ # @param rule_id [String] the Protect rule name.
184
+ # @param input [String] the user input.
185
+ # @param input_type [Symbol] Type of the input
186
+ # @return result [Contrast::Agent::Protect::InputClassification::CachedResult, nil]
187
+ def query_fetch rule_id, input, input_type
188
+ @cache[rule_id].map do |cached_result|
189
+ return cached_result if cached_result.result.value == input &&
190
+ cached_result.result.input_type == input_type
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,66 @@
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/protect/rule/input_classification/rates'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Protect
9
+ module Rule
10
+ module InputClassification
11
+ # This class will hold match information when input classification is being saved in LRU cache.
12
+ class MatchRates < Rates
13
+ # @return [Integer]
14
+ attr_reader :input_matches
15
+ # @return [Integer]
16
+ attr_reader :input_mismatches
17
+ # @return [Integer]
18
+ attr_reader :request_matches
19
+ # @return [String]
20
+ attr_reader :score_level
21
+
22
+ # @param rule_id [String]
23
+ # @param input_type [Symbol, nil]
24
+ # @param score_level [String]
25
+ def initialize rule_id, input_type, score_level
26
+ super(rule_id, input_type)
27
+ @input_matches = 0
28
+ @request_matches = 0
29
+ @input_mismatches = 0
30
+ @score_level = score_level if Contrast::Agent::Reporting::ScoreLevel.to_a.include?(score_level)
31
+ end
32
+
33
+ # Increase the match count for the given rule_id and input.
34
+ def increase_match_for_input
35
+ @input_matches += 1
36
+ end
37
+
38
+ def increase_mismatch_for_input
39
+ @input_mismatches += 1
40
+ end
41
+
42
+ # increase the missmatch count for the given rule_id and input.
43
+ def increase_match_for_request
44
+ @request_matches += 1
45
+ end
46
+
47
+ def empty?
48
+ super && @input_matches.zero? && @input_mismatches.zero? && @request_matches.zero?
49
+ end
50
+
51
+ # Returns Agent Telemetry reportable fields.
52
+ #
53
+ # @return [Hash]
54
+ def to_fields
55
+ {
56
+ "#{ to_field_title }.input_matches" => input_matches,
57
+ "#{ to_field_title }.input_mismatches" => input_mismatches,
58
+ "#{ to_field_title }.request_matches" => request_matches
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,53 @@
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/reporting/input_analysis/input_type'
5
+ require 'contrast/utils/duck_utils'
6
+
7
+ module Contrast
8
+ module Agent
9
+ module Protect
10
+ module Rule
11
+ module InputClassification
12
+ # This class will hold match information when input classification is being saved in LRU cache.
13
+ class Rates
14
+ # Titles:
15
+ FIELD_NAME = 'ia'
16
+ TEST_NAME = '_t'
17
+
18
+ # @return [String]
19
+ attr_reader :rule_id
20
+ # @return [Symbol, nil]
21
+ attr_reader :input_type
22
+
23
+ def initialize rule_id, input_type
24
+ @rule_id = rule_id if rule_id
25
+ @input_type = input_type if Contrast::Agent::Reporting::InputType.to_a.include?(input_type)
26
+ end
27
+
28
+ def empty?
29
+ Contrast::Utils::DuckUtils.empty_duck?(@input_type)
30
+ end
31
+
32
+ # Override this method to return the required Agent Telemetry fields.
33
+ # @return [Hash]
34
+ def to_fields
35
+ {}
36
+ end
37
+
38
+ private
39
+
40
+ # @return [String]
41
+ def to_field_title
42
+ if (ENV['CONTRAST_AGENT_TELEMETRY_TEST'] = '1')
43
+ TEST_NAME
44
+ else
45
+ FIELD_NAME
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end