contrast-agent 6.4.0 → 6.5.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/cs__contrast_patch/cs__contrast_patch.c +14 -1
  3. data/lib/contrast/agent/assess/finalizers/hash.rb +1 -0
  4. data/lib/contrast/agent/assess/policy/propagation_method.rb +5 -1
  5. data/lib/contrast/agent/assess/policy/propagator/custom.rb +4 -0
  6. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +5 -0
  7. data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -0
  8. data/lib/contrast/agent/assess/policy/source_method.rb +5 -0
  9. data/lib/contrast/agent/assess/policy/trigger_method.rb +8 -2
  10. data/lib/contrast/agent/assess/tracker.rb +12 -0
  11. data/lib/contrast/agent/inventory/dependency_analysis.rb +2 -2
  12. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -1
  13. data/lib/contrast/agent/inventory/policy/datastores.rb +1 -1
  14. data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
  15. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -3
  16. data/lib/contrast/agent/reporting/reporter_heartbeat.rb +1 -3
  17. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +17 -21
  18. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +1 -1
  19. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +26 -3
  20. data/lib/contrast/agent/request_context.rb +8 -0
  21. data/lib/contrast/agent/service_heartbeat.rb +2 -3
  22. data/lib/contrast/agent/static_analysis.rb +1 -1
  23. data/lib/contrast/agent/version.rb +1 -1
  24. data/lib/contrast/agent/worker_thread.rb +10 -0
  25. data/lib/contrast/components/agent.rb +51 -13
  26. data/lib/contrast/components/assess.rb +16 -0
  27. data/lib/contrast/components/contrast_service.rb +1 -1
  28. data/lib/contrast/components/heap_dump.rb +51 -1
  29. data/lib/contrast/components/inventory.rb +19 -13
  30. data/lib/contrast/components/logger.rb +18 -0
  31. data/lib/contrast/config/assess_configuration.rb +28 -0
  32. data/lib/contrast/config/base_configuration.rb +8 -2
  33. data/lib/contrast/config/root_configuration.rb +11 -8
  34. data/lib/contrast/config/service_configuration.rb +4 -4
  35. data/lib/contrast/config.rb +0 -6
  36. data/lib/contrast/extension/object.rb +19 -0
  37. data/lib/contrast/framework/rails/support.rb +4 -1
  38. data/lib/contrast/logger/log.rb +2 -1
  39. data/lib/contrast/utils/assess/event_limit_utils.rb +96 -0
  40. data/lib/contrast/utils/assess/propagation_method_utils.rb +27 -7
  41. data/lib/contrast/utils/log_utils.rb +2 -2
  42. data/lib/contrast/utils/patching/policy/patch_utils.rb +1 -1
  43. data/lib/contrast.rb +4 -19
  44. data/resources/assess/policy.json +4 -12
  45. data/ruby-agent.gemspec +2 -0
  46. metadata +43 -17
  47. data/lib/contrast/config/agent_configuration.rb +0 -63
  48. data/lib/contrast/config/heap_dump_configuration.rb +0 -59
  49. data/lib/contrast/config/inventory_configuration.rb +0 -33
  50. data/lib/contrast/config/logger_configuration.rb +0 -26
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d74206aa94d644cbe7c47523a69efdb9c0f27d59604125287fd98b123f0d20d6
4
- data.tar.gz: d4c28074b8d3f11e968d9c3875bfd16d3de41bdd63452bec470cea65a6c83089
3
+ metadata.gz: bdb8bbd08206dafbb18eee844e1487701c419d27c1df8e4b58c4c226b1995d5c
4
+ data.tar.gz: 57bbfe5e3dca05729f95f2994246e74d59f7eecc2f7fe434e265fa5c60cc3734
5
5
  SHA512:
6
- metadata.gz: 3dd36988b29722b7961e919d2685a8cafbf001917f54f5e43107cf51fddb7c105cfea368e04f819f01dc388dd75f5bf739f9bb0c58301784c90bf48188c43bd8
7
- data.tar.gz: 18a2cbb1e4a4e56d64d3a3821c47ec3aca5709f64673ffb0024b26173de7d1ed4aa8213c851c202c1d30c118d73a4f373b14563d3aa3d9f017ea8caa24ead71d
6
+ metadata.gz: 5f6d80b48529b52719fb7aa4608e0cd42f8ec613e61138d7f7a977652054f9a24f05a78c0d79821749bc4cc1d86b0878009e93f65be47155e47c9e1ec084780d
7
+ data.tar.gz: ad3d855319ebf15f1af37cac9507c2774af76a6151d7e9a4559c9de23891bcc41f0642729087165b19d20ee4891e327895f9e0d9c9c5d4e402e3a4b901b44484
@@ -101,7 +101,18 @@ VALUE rescue_func(VALUE arg1) {
101
101
  return Qnil;
102
102
  }
103
103
 
104
+ /**
105
+ * In the event that the original_method call throws an exception we need to ensure that contrast_post_patch is called
106
+ * to report that error. However, if there is no error we will call post_patch with the original_return instead of
107
+ * Qnil.
108
+ *
109
+ **/
104
110
  VALUE contrast_patch_call_ensure(const VALUE *args) {
111
+ // we do not need to ensure that post patch is called if no error was thrown
112
+ if(!RTEST(rb_errinfo())) {
113
+ return Qnil;
114
+ }
115
+
105
116
  int argc;
106
117
  VALUE object, preshift, method_policy, method;
107
118
  VALUE *argv;
@@ -125,6 +136,7 @@ VALUE ensure_wrapper(const VALUE *args) {
125
136
  original_args = (VALUE)args[1];
126
137
  ensure_args = (VALUE)args[2];
127
138
 
139
+ //this ensure if being treated as a rescue due to issues surrounding Kernel#throw
128
140
  return rb_ensure(original_method, original_args, contrast_patch_call_ensure,
129
141
  (VALUE)ensure_args);
130
142
  }
@@ -220,13 +232,14 @@ VALUE contrast_run_patches(const VALUE *wrapped_args) {
220
232
  * If the original method threw an exception, contrast_patch_call_rescue
221
233
  * re-raises the original exception, which unwinds the stack back to the
222
234
  * call site. This means the rest of this function is not executed.
235
+ * post_patch is called in the ensure_wrapper on exception. rb_rescue
236
+ * raises the exception so the below will not be executed in that event.
223
237
  */
224
238
 
225
239
  /* Invoke Contrast post-call patching. */
226
240
  contrast_call_post_patch(method_policy, preshift, object,
227
241
  original_ret, argc, argv);
228
242
 
229
- /* Special case for tracking frozen sources */
230
243
  return original_ret;
231
244
  }
232
245
 
@@ -14,6 +14,7 @@ module Contrast
14
14
  FROZEN_FINALIZED_IDS = Set.new
15
15
 
16
16
  def []= key, obj
17
+ return unless obj
17
18
  return unless ::Contrast::AGENT.enabled? && ::Contrast::ASSESS.enabled?
18
19
 
19
20
  # We can't finalize frozen things, so only act on those that went through .pre_freeze
@@ -8,6 +8,7 @@ require 'contrast/components/logger'
8
8
  require 'contrast/utils/object_share'
9
9
  require 'contrast/utils/sha256_builder'
10
10
  require 'contrast/utils/assess/propagation_method_utils'
11
+ require 'contrast/utils/assess/event_limit_utils'
11
12
  require 'contrast/agent/assess/events/event_data'
12
13
  require 'contrast/utils/assess/object_store'
13
14
 
@@ -21,6 +22,7 @@ module Contrast
21
22
  module PropagationMethod
22
23
  extend Contrast::Components::Logger::InstanceMethods
23
24
  extend Contrast::Utils::Assess::PropagationMethodUtils
25
+ extend Contrast::Utils::Assess::EventLimitUtils
24
26
 
25
27
  @properties = Contrast::Utils::Assess::ObjectStore.new
26
28
 
@@ -38,6 +40,7 @@ module Contrast
38
40
  def apply_propagation method_policy, preshift, object, ret, args, block
39
41
  return unless (propagation_node = method_policy.propagation_node)
40
42
  return unless propagation_node.use_original_object? || preshift
43
+ return if event_limit?(method_policy)
41
44
 
42
45
  target = determine_target(propagation_node, ret, object, args)
43
46
  propagation_data = Contrast::Agent::Assess::Events::EventData.new(nil, nil, object, ret, args)
@@ -184,7 +187,7 @@ module Contrast
184
187
  # @param _block [Block] the Block passed to the original method
185
188
  def handle_cs_properties_propagation propagation_node, preshift, target, propagation_data, _block
186
189
  return if propagation_node.action == NOOP_ACTION
187
- return unless can_propagate?(propagation_node, preshift, target)
190
+ return unless can_propagate?(propagation_node, preshift, target, propagation_data)
188
191
  return unless (propagation_class = find_propagation_class(propagation_node))
189
192
 
190
193
  # If we are using the original object tracking, the preshift object is not created.
@@ -192,6 +195,7 @@ module Contrast
192
195
  source = propagation_node.use_original_object? ? propagation_data.object : preshift
193
196
  handle_propagation(propagation_class, propagation_node, source, target)
194
197
  update_properties(propagation_node, target, propagation_data)
198
+ increment_event_count(propagation_node)
195
199
  end
196
200
 
197
201
  def handle_propagation propagation_class, propagation_node, source, target
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/extension/module'
5
+ require 'contrast/utils/assess/event_limit_utils'
5
6
 
6
7
  module Contrast
7
8
  module Agent
@@ -13,6 +14,8 @@ module Contrast
13
14
  # action knows the class and method it should call to preform this
14
15
  # action.
15
16
  module Custom
17
+ extend Contrast::Utils::Assess::EventLimitUtils
18
+
16
19
  class << self
17
20
  def propagate propagation_node, preshift, ret, block
18
21
  clazz = propagation_node.patch_class
@@ -26,6 +29,7 @@ module Contrast
26
29
  propagation_node.patch_class = clazz
27
30
  end
28
31
  clazz.send(method, propagation_node, preshift, ret, block)
32
+ increment_event_count(propagation_node)
29
33
  end
30
34
  end
31
35
  end
@@ -1,6 +1,8 @@
1
1
  # Copyright (c) 2022 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/assess/event_limit_utils'
5
+
4
6
  module Contrast
5
7
  module Agent
6
8
  module Assess
@@ -11,6 +13,8 @@ module Contrast
11
13
  # results in new source nodes to track which columns in the database
12
14
  # have been tainted.
13
15
  class DatabaseWrite < Contrast::Agent::Assess::Policy::Propagator::Base
16
+ extend Contrast::Utils::Assess::EventLimitUtils
17
+
14
18
  class << self
15
19
  def propagate propagation_node, preshift, target
16
20
  return unless Contrast::ASSESS.require_dynamic_sources?
@@ -22,6 +26,7 @@ module Contrast
22
26
  known_tainted = ::Contrast::ASSESS.tainted_columns[class_name]
23
27
  propagation_node.sources.each do |source|
24
28
  handle_write(propagation_node, source, preshift, target, known_tainted, tainted_columns)
29
+ increment_event_count(propagation_node)
25
30
  end
26
31
  return if tainted_columns.empty?
27
32
 
@@ -6,6 +6,7 @@ require 'contrast/components/agent'
6
6
  require 'contrast/components/logger'
7
7
  require 'contrast/components/scope'
8
8
  require 'contrast/utils/thread_tracker'
9
+ require 'contrast/utils/assess/event_limit_utils'
9
10
  require 'contrast/utils/assess/split_utils'
10
11
  require 'contrast/agent/assess/events/event_data'
11
12
 
@@ -20,6 +21,7 @@ module Contrast
20
21
  extend Contrast::Components::Scope::InstanceMethods
21
22
  extend Contrast::Components::Logger::InstanceMethods
22
23
  extend Contrast::Utils::Assess::SplitUtils
24
+ extend Contrast::Utils::Assess::EventLimitUtils
23
25
 
24
26
  SPLIT_TRACKER = Contrast::Utils::ThreadTracker.new
25
27
 
@@ -42,6 +44,7 @@ module Contrast
42
44
  return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
43
45
 
44
46
  update_element_properties(propagation_node, target, preshift, source_properties)
47
+ increment_event_count(propagation_node)
45
48
  nil
46
49
  end
47
50
 
@@ -7,6 +7,7 @@ require 'contrast/components/logger'
7
7
  require 'contrast/utils/object_share'
8
8
  require 'contrast/utils/sha256_builder'
9
9
  require 'contrast/utils/assess/source_method_utils'
10
+ require 'contrast/utils/assess/event_limit_utils'
10
11
  require 'contrast/agent/assess/events/event_data'
11
12
 
12
13
  module Contrast
@@ -19,6 +20,7 @@ module Contrast
19
20
  module SourceMethod
20
21
  extend Contrast::Components::Logger::InstanceMethods
21
22
  extend Contrast::Utils::Assess::SourceMethodUtils
23
+ extend Contrast::Utils::Assess::EventLimitUtils
22
24
 
23
25
  PARAMETER_TYPE = 'PARAMETER'
24
26
  PARAMETER_KEY_TYPE = 'PARAMETER_KEY'
@@ -37,6 +39,7 @@ module Contrast
37
39
  # @param args [Array<Object>] the Arguments with which the method was invoked
38
40
  def apply_source method_policy, object, ret, args
39
41
  return unless analyze?(method_policy, object, ret, args)
42
+ return if event_limit?(method_policy)
40
43
  return unless (source_node = method_policy.source_node)
41
44
 
42
45
  # used to hold the object and ret
@@ -66,6 +69,8 @@ module Contrast
66
69
  context = Contrast::Agent::REQUEST_TRACKER.current
67
70
  return unless context && source_node && target
68
71
 
72
+ increment_event_count(source_node)
73
+
69
74
  source_name ||= determine_source_name(source_node, source_data.object, source_data.ret, *args)
70
75
  # We know we only work on certain things.
71
76
  # Skip if this isn't one of them
@@ -25,6 +25,7 @@ module Contrast
25
25
  module TriggerMethod # rubocop:disable Metrics/ModuleLength
26
26
  extend Contrast::Components::Logger::InstanceMethods
27
27
  extend Contrast::Utils::Assess::TriggerMethodUtils
28
+ extend Contrast::Utils::Assess::EventLimitUtils
28
29
 
29
30
  # The level of TeamServer compliance our traces meet when in the abnormal condition of being dataflow rules
30
31
  # without routes.
@@ -48,6 +49,7 @@ module Contrast
48
49
  # @param args [Array<Object>] the Arguments with which the method was invoked
49
50
  def apply_trigger_rule trigger_node, object, ret, args
50
51
  return if trigger_node.nil?
52
+ return if event_limit_for_rule?(trigger_node.rule_id)
51
53
 
52
54
  context = Contrast::Agent::REQUEST_TRACKER.current
53
55
  # return if there is no context and the flag is set to default => false
@@ -102,6 +104,12 @@ module Contrast
102
104
  request = find_request(source)
103
105
  return unless reportable?(request&.env)
104
106
 
107
+ process_reportable_finding(trigger_node, source, object, ret, request, *args)
108
+ rescue StandardError => e
109
+ logger.error('Unable to build a finding', e, rule: trigger_node.rule_id, node_id: trigger_node.id)
110
+ end
111
+
112
+ def process_reportable_finding trigger_node, source, object, ret, request, *args
105
113
  if Contrast::Agent::Reporter.enabled?
106
114
  handle_new_finding(trigger_node, source, object, ret, request, *args)
107
115
  else # TODO: RUBY-1438 -- remove
@@ -112,8 +120,6 @@ module Contrast
112
120
  rule: trigger_node.rule_id)
113
121
  report_finding(finding, request)
114
122
  end
115
- rescue StandardError => e
116
- logger.error('Unable to build a finding', e, rule: trigger_node.rule_id, node_id: trigger_node.id)
117
123
  end
118
124
 
119
125
  # Given a finding, append it to an activity message and send it to the Service for processing. If an
@@ -12,6 +12,7 @@ module Contrast
12
12
  # have tightly coupled dependencies on each other.
13
13
  class Tracker
14
14
  PROPERTIES_HASH = Contrast::Agent::Assess::Finalizers::Hash.new
15
+ KEEP_AGE = 600_000.cs__freeze # 10 minutes
15
16
 
16
17
  class << self
17
18
  # Retrieve the properties of the given Object, iff they exist.
@@ -55,6 +56,17 @@ module Contrast
55
56
  def copy source, target
56
57
  PROPERTIES_HASH[target] ||= properties(source).dup
57
58
  end
59
+
60
+ # Clean PROPERTIES_HASH of any values older than KEEP_AGE ms or
61
+ # have nil properties
62
+ def cleanup!
63
+ PROPERTIES_HASH.delete_if do |_k, properties|
64
+ return true if properties.nil?
65
+ return false unless (event = properties&.event)
66
+
67
+ KEEP_AGE <= (Contrast::Utils::Timer.now_ms - event.time)
68
+ end
69
+ end
58
70
  end
59
71
  end
60
72
  end
@@ -18,8 +18,8 @@ module Contrast
18
18
  # @return [Array<Contrast::Agent::Reporting::LibraryDiscovery>] direct report form of
19
19
  # Gem::Specification that have been loaded for this application.
20
20
  def library_pb_list
21
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless ::Contrast::INVENTORY.enabled?
22
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless ::Contrast::INVENTORY.analyze_libraries?
21
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless ::Contrast::INVENTORY.enable
22
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless ::Contrast::INVENTORY.analyze_libraries
23
23
 
24
24
  loaded_specs.each_with_object([]) do |(_name, spec), reported_lib_list|
25
25
  next unless spec
@@ -111,7 +111,7 @@ module Contrast
111
111
 
112
112
  # We only use this if inventory and library analysis are enabled
113
113
  def enabled?
114
- @_enabled = ::Contrast::INVENTORY.enabled? && ::Contrast::INVENTORY.analyze_libraries? if @_enabled.nil?
114
+ @_enabled = ::Contrast::INVENTORY.enable && ::Contrast::INVENTORY.analyze_libraries if @_enabled.nil?
115
115
  @_enabled
116
116
  end
117
117
  end
@@ -21,7 +21,7 @@ module Contrast
21
21
  DATA_STORE_MARKER = 'data_store'
22
22
 
23
23
  def report_data_store _method, _exception, properties, object, _args
24
- return unless ::Contrast::INVENTORY.enabled?
24
+ return unless ::Contrast::INVENTORY.enabled
25
25
 
26
26
  marker = properties[DATA_STORE_MARKER]
27
27
  return unless marker
@@ -19,7 +19,7 @@ module Contrast
19
19
  end
20
20
 
21
21
  def disabled_globally?
22
- !::Contrast::INVENTORY.enabled?
22
+ !::Contrast::INVENTORY.enable
23
23
  end
24
24
 
25
25
  def node_type
@@ -17,9 +17,9 @@ module Contrast
17
17
  #
18
18
  # @param method_policy [Hash]
19
19
  # {
20
- # source_node [ Contrast::Agent::Assesss::Policy::SourceNode ]
21
- # propagation_node [ Contrast::Agent::Assesss::Policy::PropagationNode ]
22
- # trigger_node [ Contrast::Agent::Assesss::Policy::TriggerNode ]
20
+ # source_node [ Contrast::Agent::Assess::Policy::SourceNode ]
21
+ # propagation_node [ Contrast::Agent::Assess::Policy::PropagationNode ]
22
+ # trigger_node [ Contrast::Agent::Assess::Policy::TriggerNode ]
23
23
  # inventory_node [ Contrast::Agent::Inventory::Policy::TriggerNode ]
24
24
  # protect_node [ Contrast::Agent::Protect::Policy::TriggerNode ]
25
25
  # deadzone_node [ Contrast::Agent::Deadzone::Policy::DeadzoneNode ]
@@ -3,7 +3,6 @@
3
3
 
4
4
  require 'contrast/agent/worker_thread'
5
5
  require 'contrast/agent/reporting/report'
6
- require 'contrast/components/logger'
7
6
  require 'contrast/agent/inventory/dependency_usage_analysis'
8
7
  require 'contrast/agent/reporting/reporting_events/poll'
9
8
  require 'contrast/agent/reporting/reporting_events/server_activity'
@@ -14,8 +13,6 @@ module Contrast
14
13
  # reach out to get the latest settings for this application. It also sends out those messages which do not need to
15
14
  # be associated directly with a request, such as Server Activity and Library Observation.
16
15
  class ReporterHeartbeat < WorkerThread
17
- include Contrast::Components::Logger::InstanceMethods
18
-
19
16
  # TeamServer will mark an application offline after 5 minutes. Sending this every one should be more than enough
20
17
  # to satisfy our goals.
21
18
  REFRESH_INTERVAL_SEC = 60
@@ -29,6 +26,7 @@ module Contrast
29
26
  polling_events.each do |event|
30
27
  Contrast::Agent.reporter&.send_event(event)
31
28
  end
29
+ clean_properties
32
30
  sleep(REFRESH_INTERVAL_SEC)
33
31
  end
34
32
  end
@@ -56,35 +56,31 @@ module Contrast
56
56
  # @param attacker_activity [Contrast::Agent::Reporting::ApplicationDefendAttackerActivity]
57
57
  # @param rule [String]
58
58
  def attach_existing existing_attacker_activity, attacker_activity, rule
59
- # TODO: RUBY-1663 figure out attack time mapping
60
59
  new_violation = attacker_activity.protection_rules[rule]
60
+ sample_activity = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity
61
61
  if (previously_violated = existing_attacker_activity.protection_rules[rule])
62
- if (new_blocked = new_violation.blocked&.samples)
63
- unless previously_violated.blocked
64
- previously_violated.blocked = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity.new
65
- end
66
- previously_violated.blocked.samples.concat(new_blocked)
62
+ if (new_blocked = new_violation.blocked)
63
+ previously_violated.blocked ||= sample_activity.new
64
+ previously_violated.blocked.samples.concat(new_blocked.samples) if new_blocked.samples
65
+ previously_violated.blocked.merge_time_maps(new_blocked.time_map)
67
66
  end
68
67
 
69
- if (new_exploited = new_violation.exploited&.samples)
70
- unless previously_violated.exploited
71
- previously_violated.exploited = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity.new
72
- end
73
- previously_violated.exploited.samples.concat(new_exploited)
68
+ if (new_exploited = new_violation.exploited)
69
+ previously_violated.exploited ||= sample_activity.new
70
+ previously_violated.exploited.samples.concat(new_exploited.samples) if new_exploited.samples
71
+ previously_violated.exploited.merge_time_maps(new_exploited.time_map)
74
72
  end
75
73
 
76
- if (new_ineffective = new_violation.ineffective&.samples)
77
- unless previously_violated.ineffective
78
- previously_violated.ineffective = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity.new
79
- end
80
- previously_violated.ineffective.samples.concat(new_ineffective)
74
+ if (new_ineffective = new_violation.ineffective)
75
+ previously_violated.ineffective ||= sample_activity.new
76
+ previously_violated.ineffective.samples.concat(new_ineffective.samples) if new_ineffective.samples
77
+ previously_violated.ineffective.merge_time_maps(new_ineffective.time_map)
81
78
  end
82
79
 
83
- if (new_suspicious = new_violation.suspicious&.samples)
84
- unless previously_violated.suspicious
85
- previously_violated.suspicious = Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity.new
86
- end
87
- previously_violated.suspicious.samples.concat(new_suspicious)
80
+ if (new_suspicious = new_violation.suspicious)
81
+ previously_violated.suspicious ||= sample_activity.new
82
+ previously_violated.suspicious.samples.concat(new_suspicious.samples) if new_suspicious.samples
83
+ previously_violated.suspicious.merge_time_maps(new_suspicious.time_map)
88
84
  end
89
85
  else
90
86
  existing_attacker_activity.protection_rules[rule] = new_violation
@@ -16,7 +16,7 @@ module Contrast
16
16
  class ApplicationDefendAttackSample
17
17
  include Contrast::Agent::Reporting::InputType
18
18
 
19
- # @return [Hash]
19
+ # @return [Hash] => start: ms, elapsed: ms
20
20
  attr_reader :time_stamp
21
21
 
22
22
  class << self
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/components/logger'
5
+ require 'contrast/utils/timer'
5
6
  require 'contrast/agent/reporting/reporting_events/application_defend_attack_sample'
6
7
 
7
8
  module Contrast
@@ -26,7 +27,7 @@ module Contrast
26
27
 
27
28
  def initialize
28
29
  @samples = []
29
- @start_time = (Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0) / 1000
30
+ @start_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0 # in ms
30
31
  @event_type = :application_defend_attack_sample_activity
31
32
  @time_map = Hash.new { |h, k| h[k] = 0 }
32
33
  super
@@ -36,7 +37,7 @@ module Contrast
36
37
  {
37
38
  attackTimeMap: time_map,
38
39
  samples: samples.map(&:to_controlled_hash),
39
- startTime: @start_time,
40
+ startTime: @start_time, # Start time in ms.
40
41
  total: 1 # there will only ever be 1 attack sample, until batching is done
41
42
  }
42
43
  end
@@ -44,12 +45,34 @@ module Contrast
44
45
  # @param attack_result [Contrast::Api::Dtm::AttackResult]
45
46
  def attach_data attack_result
46
47
  attack_result.samples.each do |attack_sample|
48
+ base_time = Contrast::Agent::REQUEST_TRACKER.current&.timer&.start_ms || 0
49
+ dmt_time = attack_sample.timestamp_ms.to_i
47
50
  converted = Contrast::Agent::Reporting::ApplicationDefendAttackSample.convert(attack_result, attack_sample)
48
51
  samples << converted
49
- attack_second = converted.time_stamp[:elapsed] / 1000
52
+ @start_time = if dmt_time.zero?
53
+ @start_time
54
+ else
55
+ dmt_time
56
+ end
57
+ attack_second = (@start_time - base_time) / 1000 # in seconds
50
58
  time_map[attack_second] += 1
51
59
  end
52
60
  end
61
+
62
+ # This method will merge time_maps of attack samples with same
63
+ # type.
64
+ #
65
+ # @param map [Hash<Integer,Integer>] TimeMap to append to previously_violated rule
66
+ # samples.
67
+ # @return time_map [Hash<Integer,Integer>] merged time map with updated occurrences.
68
+ def merge_time_maps map
69
+ # If the second is the same (key) if we just merge there won't be a new entry,
70
+ # so just increase the attack count.
71
+ map.each_key do |key|
72
+ @time_map[key] = @time_map.fetch(key, 0) + map[key]
73
+ end
74
+ @time_map
75
+ end
53
76
  end
54
77
  end
55
78
  end
@@ -43,6 +43,8 @@ module Contrast
43
43
  # @return [Contrast::Utils::Timer] when the context was created
44
44
  attr_reader :timer
45
45
 
46
+ attr_accessor :propagation_event_count, :source_event_count
47
+
46
48
  def initialize rack_request, app_loaded: true
47
49
  with_contrast_scope do
48
50
  # all requests get a timer and hash
@@ -68,6 +70,12 @@ module Contrast
68
70
  # generic holder for properties that can be set throughout this request
69
71
  @_properties = {}
70
72
 
73
+ # count of propagation events
74
+ @propagation_event_count = 0
75
+
76
+ # count of source events
77
+ @source_event_count = 0
78
+
71
79
  if ::Contrast::ASSESS.enabled?
72
80
  @sample_req, @sample_res = Contrast::Utils::Assess::SamplingUtil.instance.sample?(@request)
73
81
  end
@@ -1,7 +1,6 @@
1
1
  # Copyright (c) 2022 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/components/logger'
5
4
  require 'contrast/agent/worker_thread'
6
5
  require 'contrast/agent/reporting/report'
7
6
 
@@ -10,8 +9,6 @@ module Contrast
10
9
  # The ServiceHeartbeat functions to keep the Contrast Service alive and ensure that it maintains this Agent's
11
10
  # ApplicationContext.
12
11
  class ServiceHeartbeat < WorkerThread
13
- include Contrast::Components::Logger::InstanceMethods
14
-
15
12
  # Spec recommends 30 seconds, we're going with 15.
16
13
  REFRESH_INTERVAL_SEC = 15
17
14
 
@@ -22,7 +19,9 @@ module Contrast
22
19
  @_thread = Contrast::Agent::Thread.new do
23
20
  logger.info('Starting heartbeat thread.')
24
21
  loop do
22
+ logger.info("Queue Size: #{ Contrast::Agent.messaging_queue.queue&.length }")
25
23
  Contrast::Agent.messaging_queue&.send_event_eventually(poll_message)
24
+ clean_properties
26
25
  sleep(REFRESH_INTERVAL_SEC)
27
26
  end
28
27
  end
@@ -24,7 +24,7 @@ module Contrast
24
24
  end
25
25
 
26
26
  def send_inventory_message
27
- return unless ::Contrast::INVENTORY.enabled?
27
+ return unless ::Contrast::INVENTORY.enable
28
28
 
29
29
  report = Contrast::Agent::Reporting::ApplicationUpdate.new
30
30
  # This convert here is left as it'll be easier to be replaced when the Library is being changed
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '6.4.0'
6
+ VERSION = '6.5.0'
7
7
  end
8
8
  end
@@ -1,10 +1,14 @@
1
1
  # Copyright (c) 2022 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/components/logger'
5
+
4
6
  module Contrast
5
7
  module Agent
6
8
  # Base class for threads that do async processing
7
9
  class WorkerThread
10
+ include Contrast::Components::Logger::InstanceMethods
11
+
8
12
  def initialize
9
13
  @_thread = nil
10
14
  end
@@ -27,6 +31,12 @@ module Contrast
27
31
  def attempt_to_start?
28
32
  true
29
33
  end
34
+
35
+ def clean_properties
36
+ logger.debug("Cleaning PROPERTIES_HASH size: #{ Contrast::Agent::Assess::Tracker::PROPERTIES_HASH.size }")
37
+ Contrast::Agent::Assess::Tracker.cleanup!
38
+ logger.debug("Cleaned PROPERTIES_HASH size: #{ Contrast::Agent::Assess::Tracker::PROPERTIES_HASH.size }")
39
+ end
30
40
  end
31
41
  end
32
42
  end