contrast-agent 4.3.2 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent.rb +5 -1
  3. data/lib/contrast/agent/assess.rb +0 -9
  4. data/lib/contrast/agent/assess/contrast_event.rb +0 -2
  5. data/lib/contrast/agent/assess/contrast_object.rb +5 -2
  6. data/lib/contrast/agent/assess/finalizers/hash.rb +7 -0
  7. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +17 -3
  8. data/lib/contrast/agent/assess/policy/propagation_method.rb +28 -13
  9. data/lib/contrast/agent/assess/policy/propagator/append.rb +28 -13
  10. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +21 -16
  11. data/lib/contrast/agent/assess/policy/propagator/splat.rb +23 -13
  12. data/lib/contrast/agent/assess/policy/propagator/split.rb +14 -7
  13. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +30 -14
  14. data/lib/contrast/agent/assess/policy/trigger_method.rb +13 -8
  15. data/lib/contrast/agent/assess/policy/trigger_node.rb +28 -7
  16. data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +59 -0
  17. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -2
  18. data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +6 -4
  19. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +2 -4
  20. data/lib/contrast/agent/assess/properties.rb +0 -2
  21. data/lib/contrast/agent/assess/property/tagged.rb +37 -19
  22. data/lib/contrast/agent/assess/tracker.rb +1 -1
  23. data/lib/contrast/agent/middleware.rb +85 -55
  24. data/lib/contrast/agent/patching/policy/patch_status.rb +1 -1
  25. data/lib/contrast/agent/patching/policy/patcher.rb +51 -44
  26. data/lib/contrast/agent/patching/policy/trigger_node.rb +5 -2
  27. data/lib/contrast/agent/protect/rule/sqli.rb +17 -11
  28. data/lib/contrast/agent/request_context.rb +12 -0
  29. data/lib/contrast/agent/thread.rb +1 -1
  30. data/lib/contrast/agent/thread_watcher.rb +20 -5
  31. data/lib/contrast/agent/version.rb +1 -1
  32. data/lib/contrast/api/communication/messaging_queue.rb +18 -21
  33. data/lib/contrast/api/communication/response_processor.rb +8 -1
  34. data/lib/contrast/api/communication/socket_client.rb +22 -14
  35. data/lib/contrast/api/decorators.rb +2 -0
  36. data/lib/contrast/api/decorators/agent_startup.rb +58 -0
  37. data/lib/contrast/api/decorators/application_startup.rb +51 -0
  38. data/lib/contrast/api/decorators/route_coverage.rb +15 -5
  39. data/lib/contrast/api/decorators/trace_event.rb +42 -14
  40. data/lib/contrast/components/agent.rb +2 -0
  41. data/lib/contrast/components/app_context.rb +4 -22
  42. data/lib/contrast/components/sampling.rb +48 -6
  43. data/lib/contrast/components/settings.rb +5 -4
  44. data/lib/contrast/framework/manager.rb +13 -12
  45. data/lib/contrast/framework/rails/support.rb +42 -43
  46. data/lib/contrast/framework/sinatra/support.rb +100 -41
  47. data/lib/contrast/logger/log.rb +31 -15
  48. data/lib/contrast/utils/class_util.rb +3 -1
  49. data/lib/contrast/utils/heap_dump_util.rb +103 -87
  50. data/lib/contrast/utils/invalid_configuration_util.rb +21 -12
  51. data/resources/assess/policy.json +3 -9
  52. data/resources/deadzone/policy.json +6 -0
  53. data/ruby-agent.gemspec +54 -16
  54. metadata +105 -136
  55. data/lib/contrast/agent/assess/rule.rb +0 -18
  56. data/lib/contrast/agent/assess/rule/base.rb +0 -52
  57. data/lib/contrast/agent/assess/rule/redos.rb +0 -67
  58. data/lib/contrast/framework/sinatra/patch/base.rb +0 -83
  59. data/lib/contrast/framework/sinatra/patch/support.rb +0 -27
  60. data/lib/contrast/utils/prevent_serialization.rb +0 -52
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09fcc059f5c03c966c230f4576d93399f6a35bc15a42cfd4a5a9541d5e292f95'
4
- data.tar.gz: 96d58e585f42c42599744e9170d1051263e661671ae88ea9ca972a9c9be2f954
3
+ metadata.gz: 0e9acb88aec7b0f51a076b5358738fa6d0c245bb2d91c2dfcf41d2f9fb3ed204
4
+ data.tar.gz: 405ba4ca7da645d6f06767961341c587bc6369279f62742ae7b86d5dc0b6101c
5
5
  SHA512:
6
- metadata.gz: d789f3aa5577188dabc62847c921a2a00f0dec9016dc10d6d8bb8ebe6c74c69c485ee81ae0a95e99ef3d82030e620167f3d433220120de611bf021947fb1ab67
7
- data.tar.gz: 55e07ffd5112cbdde2e97b7eab565c24c811318927c13c55bde4f93a9ac4e78ba00bdb25e18d275700bb0433eeb50cee6d5dc4fa704a8526e71ba2d0ce4a99d4
6
+ metadata.gz: 4bf982f03e6daa5425c13adcb0b2c332745a9dee27733d5f90d77a4e5b4c9983a71b39f7cc0f836d19fc04b9b2a6e4eaf39ef2732be65921fd7c2f125a53b685
7
+ data.tar.gz: c18d27bc472db9037ada00561a81cf0eed181ee717ab7c20f40f39d4675fd1a797b00c0109792afb3902b107ae37b69c3c564399a2d2ca1882dd2ed71f0c93fe
@@ -56,8 +56,12 @@ module Contrast
56
56
  @_framework_manager ||= Contrast::Framework::Manager.new
57
57
  end
58
58
 
59
+ def self.heapdump_util
60
+ thread_watcher.heapdump_util
61
+ end
62
+
59
63
  def self.messaging_queue
60
- @_messaging_queue ||= Contrast::Api::Communication::MessagingQueue.new
64
+ thread_watcher.messaging_queue
61
65
  end
62
66
 
63
67
  def self.thread_watcher
@@ -13,18 +13,9 @@ module Contrast
13
13
  require 'contrast/agent/rewriter'
14
14
  require 'contrast/agent/assess/policy/preshift'
15
15
 
16
- require 'contrast/utils/prevent_serialization'
17
-
18
- # Rules - generic
19
- require 'contrast/agent/assess/rule'
20
- require 'contrast/agent/assess/rule/base'
21
-
22
16
  # Dynamic Sources
23
17
  require 'contrast/agent/assess/policy/dynamic_source_factory'
24
18
 
25
- # Rule: REDOS
26
- require 'contrast/agent/assess/rule/redos'
27
-
28
19
  # reporting / tracking
29
20
  require 'contrast/agent/assess/properties'
30
21
  require 'contrast/agent/assess/tag'
@@ -5,7 +5,6 @@ require 'contrast/utils/assess/tracking_util'
5
5
  require 'contrast/utils/class_util'
6
6
  require 'contrast/utils/duck_utils'
7
7
  require 'contrast/utils/object_share'
8
- require 'contrast/utils/prevent_serialization'
9
8
  require 'contrast/utils/stack_trace_utils'
10
9
  require 'contrast/utils/string_utils'
11
10
  require 'contrast/utils/timer'
@@ -35,7 +34,6 @@ module Contrast
35
34
  # @attr_reader args [Array<Contrast::Agent::Assess::ContrastObject>] the
36
35
  # safe representation of the Arguments with which the method was invoked
37
36
  class ContrastEvent
38
- include Contrast::Utils::PreventSerialization
39
37
  include Contrast::Components::Interface
40
38
  access_component :analysis
41
39
 
@@ -2,6 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/utils/class_util'
5
+ require 'contrast/utils/object_share'
6
+ require 'contrast/agent/assess/tracker'
5
7
 
6
8
  module Contrast
7
9
  module Agent
@@ -28,7 +30,7 @@ module Contrast
28
30
  # Capture the details about the object which we need to render it in
29
31
  # TeamServer.
30
32
  #
31
- # @param object [Object] the thing to keep a Contrast String of
33
+ # @param object [Object, nil] the thing to keep a Contrast String of
32
34
  def initialize object
33
35
  if object
34
36
  @object = Contrast::Utils::ClassUtil.to_contrast_string(object)
@@ -38,7 +40,8 @@ module Contrast
38
40
  # String that was then #reverse!'d, would our tags be wrong?
39
41
  @tags = Contrast::Agent::Assess::Tracker.properties(object)&.tags
40
42
  else
41
- @object_type = Contrast::Utils::ObjectShare::NIL_STRING
43
+ @object = Contrast::Utils::ObjectShare::NIL_STRING
44
+ @object_type = nil.cs__class.name
42
45
  end
43
46
  end
44
47
 
@@ -12,9 +12,14 @@ module Contrast
12
12
  # finalizer on the object to remove its entry from the Hash immediately
13
13
  # after it's GC'd.
14
14
  class Hash < Hash
15
+ include Contrast::Components::Interface
16
+ access_component :agent, :analysis
17
+
15
18
  FROZEN_FINALIZED_IDS = Set.new
16
19
 
17
20
  def []= key, obj
21
+ return unless AGENT.enabled? && ASSESS.enabled?
22
+
18
23
  # We can't finalize frozen things, so only act on those that went
19
24
  # through .pre_freeze
20
25
  if key.cs__frozen?
@@ -35,6 +40,7 @@ module Contrast
35
40
  # @param key [Object] the thing to determine if trackable
36
41
  # @return [Boolean]
37
42
  def trackable? key
43
+ return false unless key
38
44
  # Track things in these, not them themselves.
39
45
  return false if Contrast::Utils::DuckUtils.iterable_hash?(key)
40
46
  return false if Contrast::Utils::DuckUtils.iterable_enumerable?(key)
@@ -81,6 +87,7 @@ module Contrast
81
87
  # @param key [Object] the Object on which we need to pre-define
82
88
  # finalizers
83
89
  def pre_freeze key
90
+ return unless AGENT.enabled? && ASSESS.enabled?
84
91
  return if key.cs__frozen?
85
92
  return if FROZEN_FINALIZED_IDS.include?(key.__id__)
86
93
 
@@ -110,16 +110,30 @@ module Contrast
110
110
  dynamic_source.method_name = Contrast::Utils::StringUtils.force_utf8(field)
111
111
  dynamic_source.instance_method = source_node.instance_method?
112
112
  dynamic_source.target = Contrast::Utils::StringUtils.force_utf8(source_node.target_string)
113
+ append_properties!(dynamic_source, current_context, source_node, field)
114
+ append_events!(dynamic_source, properties.event)
115
+ dynamic_source
116
+ end
117
+
118
+ # Append the properties needed to reconstruct the given DynamicSource in other dataflows and for rendering
119
+ # in TeamServer
120
+ #
121
+ # @param dynamic_source [Contrast::Api::Dtm::DynamicSource] the message to send to the Service to allow it
122
+ # to report the events leading up to the creation of the Dynamic Source
123
+ # @param current_context [Contrast::Agent::RequestContext] the context of the request in which this source
124
+ # is to be created.
125
+ # @param source_node [Contrast::Agent::Assess::Policy::SourceNode] the SourceNode that applies to this
126
+ # method
127
+ # @param field [String] the name of the method to which this source applies
128
+ def append_properties! dynamic_source, current_context, source_node, field
113
129
  dynamic_source.properties[READ_TABLE] = Contrast::Utils::StringUtils.force_utf8(source_node.class_name)
114
130
  dynamic_source.properties[READ_COLUMN] = Contrast::Utils::StringUtils.force_utf8(field)
115
131
  dynamic_source.properties[WRITE_QUERY_TIME] = Contrast::Utils::StringUtils.force_utf8(Contrast::Utils::Timer.now_ms)
116
132
  url = current_context.request.normalized_uri
117
133
  dynamic_source.properties[WRITE_QUERY_URL] = Contrast::Utils::StringUtils.force_utf8(url)
118
- append_events(dynamic_source, properties.event)
119
- dynamic_source
120
134
  end
121
135
 
122
- def append_events dynamic_source, event
136
+ def append_events! dynamic_source, event
123
137
  return unless event
124
138
 
125
139
  event.parent_events&.each do |parent_event|
@@ -258,22 +258,12 @@ module Contrast
258
258
  def handle_cs_properties_propagation propagation_node, preshift, target, object, ret, args, _block
259
259
  return if propagation_node.action == NOOP_ACTION
260
260
  return unless can_propagate?(propagation_node, preshift, target)
261
+ return unless (propagation_class = find_propagation_class(propagation_node))
261
262
 
262
- propagation_class = PROPAGATION_ACTIONS.fetch(propagation_node.action, nil)
263
- unless propagation_class
264
- logger.warn(
265
- 'Unknown propagation action received. Unable to propagate.',
266
- node_id: propagation_node.id,
267
- action: propagation_node.action)
268
- return
269
- end
270
263
  restore_frozen_state = false
271
264
  if target.cs__frozen? && !Contrast::Agent::Assess::Tracker.trackable?(target)
272
- return unless ASSESS.track_frozen_sources?
273
- return unless propagation_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
274
-
275
- dup = safe_dup(ret)
276
- return unless dup
265
+ return unless can_handle_frozen?(propagation_node)
266
+ return unless (dup = safe_dup(ret))
277
267
 
278
268
  restore_frozen_state = true
279
269
  ret = dup
@@ -302,6 +292,31 @@ module Contrast
302
292
  target_id: target.__id__)
303
293
  restore_frozen_state ? ret : nil
304
294
  end
295
+
296
+ # Find the propagation class from the given node, if one exists.
297
+ #
298
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs a
299
+ # propagation event.
300
+ # @return [Contrast::Agent::Assess::Policy::Propagator, nil]
301
+ def find_propagation_class propagation_node
302
+ unless (propagation_class = PROPAGATION_ACTIONS.fetch(propagation_node.action, nil))
303
+ logger.warn(
304
+ 'Unknown propagation action received. Unable to propagate.',
305
+ node_id: propagation_node.id,
306
+ action: propagation_node.action)
307
+ end
308
+ propagation_class
309
+ end
310
+
311
+ # We can handle frozen propagation iff we're allowed to, as determined by configuration, and the target of
312
+ # the propagation is a return, as that's a replaceable value.
313
+ #
314
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs a
315
+ # propagation event.
316
+ # @return [Boolean]
317
+ def can_handle_frozen? propagation_node
318
+ ASSESS.track_frozen_sources? && propagation_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
319
+ end
305
320
  end
306
321
  end
307
322
  end
@@ -29,23 +29,38 @@ module Contrast
29
29
  if source1.length == target.length
30
30
  properties.copy_from(source1, target, 0, propagation_node.untags)
31
31
  else
32
- # find original in the target, copy tags to the new position in
33
- # target
34
- original_start_index = target.index(source1)
35
- properties.copy_from(source1, target, original_start_index, propagation_node.untags)
32
+ handle_append(propagation_node, source1, source2, target, properties)
33
+ end
34
+ properties.cleanup_tags
35
+ end
36
36
 
37
- start = original_start_index + source1.length
38
- while start < target.length
39
- properties.copy_from(source2, target, start, propagation_node.untags)
40
- start += source2.length
41
- next unless start > target.length
37
+ private
42
38
 
43
- properties.tags_at(start - source2.length).each do |tag|
44
- tag.update_end(target.length)
45
- end
39
+ # Given the append operation on source 1 added source 2 to it, changing the target output, modify the
40
+ # tags on the target to account for the change.
41
+ #
42
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
43
+ # propagation action required by this method
44
+ # @param source1 [Object] the thing being appended to
45
+ # @param source2 [Object] the thing being appended
46
+ # @param target [Object] the result of the append operation
47
+ # @param properties [Contrast::Agent::Assess::Properties] the properties of the target
48
+ def handle_append propagation_node, source1, source2, target, properties
49
+ # find original in the target, copy tags to the new position in
50
+ # target
51
+ original_start_index = target.index(source1)
52
+ properties.copy_from(source1, target, original_start_index, propagation_node.untags)
53
+
54
+ start = original_start_index + source1.length
55
+ while start < target.length
56
+ properties.copy_from(source2, target, start, propagation_node.untags)
57
+ start += source2.length
58
+ next unless start > target.length
59
+
60
+ properties.tags_at(start - source2.length).each do |tag|
61
+ tag.update_end(target.length)
46
62
  end
47
63
  end
48
- properties.cleanup_tags
49
64
  end
50
65
  end
51
66
  end
@@ -24,23 +24,8 @@ module Contrast
24
24
 
25
25
  known_tainted = ASSESS.tainted_columns[class_name]
26
26
  propagation_node.sources.each do |source|
27
- arg = preshift.args[source]
28
- next unless arg.cs__respond_to?(:each_pair)
29
-
30
- arg.each_pair do |key, value|
31
- next unless value
32
- next if known_tainted&.include?(key)
33
- next unless (properties = Contrast::Agent::Assess::Tracker.properties!(value))
34
-
35
- # TODO: RUBY-540 handle sanitization, handle nested objects
36
- Contrast::Agent::Assess::Policy::PropagationMethod.apply_tags(propagation_node, value)
37
- properties.build_event(propagation_node, value, preshift.object, target, preshift.args)
38
- next unless tracked_value?(value)
39
-
40
- tainted_columns[key] = properties
41
- end
27
+ handle_write(propagation_node, source, preshift, target, known_tainted, tainted_columns)
42
28
  end
43
-
44
29
  return if tainted_columns.empty?
45
30
 
46
31
  if known_tainted
@@ -51,6 +36,26 @@ module Contrast
51
36
 
52
37
  Contrast::Agent::Assess::Policy::DynamicSourceFactory.create_sources class_type, tainted_columns
53
38
  end
39
+
40
+ private
41
+
42
+ def handle_write propagation_node, source, preshift, target, known_tainted, tainted_columns
43
+ arg = preshift.args[source]
44
+ return unless arg.cs__respond_to?(:each_pair)
45
+
46
+ arg.each_pair do |key, value|
47
+ next unless value
48
+ next if known_tainted&.include?(key)
49
+ next unless (properties = Contrast::Agent::Assess::Tracker.properties!(value))
50
+
51
+ # TODO: RUBY-540 handle sanitization, handle nested objects
52
+ Contrast::Agent::Assess::Policy::PropagationMethod.apply_tags(propagation_node, value)
53
+ properties.build_event(propagation_node, value, preshift.object, target, preshift.args)
54
+ next unless tracked_value?(value)
55
+
56
+ tainted_columns[key] = properties
57
+ end
58
+ end
54
59
  end
55
60
  end
56
61
  end
@@ -19,19 +19,7 @@ module Contrast
19
19
  when Contrast::Utils::ObjectShare::OBJECT_KEY
20
20
  tracked_inputs << preshift.object if Contrast::Agent::Assess::Tracker.tracked?(preshift.object)
21
21
  else
22
- arg = preshift.args[source]
23
- if arg.is_a?(String)
24
- tracked_inputs << arg if Contrast::Agent::Assess::Tracker.tracked?(arg)
25
- elsif Contrast::Utils::DuckUtils.iterable_hash?(arg)
26
- arg.each_pair do |key, value|
27
- tracked_inputs << key if tracked_value?(key)
28
- tracked_inputs << value if tracked_value?(value)
29
- end
30
- elsif Contrast::Utils::DuckUtils.iterable_enumerable?(arg)
31
- arg.each do |value|
32
- tracked_inputs << value if tracked_value?(value)
33
- end
34
- end
22
+ find_argument_inputs(tracked_inputs, preshift.args[source])
35
23
  end
36
24
  end
37
25
 
@@ -50,6 +38,28 @@ module Contrast
50
38
  properties.splat_from(input, target)
51
39
  end
52
40
  end
41
+
42
+ private
43
+
44
+ # The arguments to the splat method are complex and of multiple types. As such, we need to handle
45
+ # Strings and iterables to determine the tracked inputs on which to act.
46
+ #
47
+ # @param tracked_inputs [Array] storage for the inputs to act on later
48
+ # @param arg [Object] an input to the method which act as sources for this propagation.
49
+ def find_argument_inputs tracked_inputs, arg
50
+ if arg.is_a?(String)
51
+ tracked_inputs << arg if Contrast::Agent::Assess::Tracker.tracked?(arg)
52
+ elsif Contrast::Utils::DuckUtils.iterable_hash?(arg)
53
+ arg.each_pair do |key, value|
54
+ tracked_inputs << key if tracked_value?(key)
55
+ tracked_inputs << value if tracked_value?(value)
56
+ end
57
+ elsif Contrast::Utils::DuckUtils.iterable_enumerable?(arg)
58
+ arg.each do |value|
59
+ tracked_inputs << value if tracked_value?(value)
60
+ end
61
+ end
62
+ end
53
63
  end
54
64
  end
55
65
  end
@@ -38,13 +38,7 @@ module Contrast
38
38
  source = find_source(propagation_node.sources[0], preshift)
39
39
  return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
40
40
 
41
- # grapheme_clusters break the string apart based on each "user-perceived" character.
42
- # Otherwise, the default for String#split is to use a single whitespace.
43
- separator_length = if propagation_node.method_name == :grapheme_clusters
44
- 0
45
- else
46
- preshift&.args&.first&.to_s&.length || $FIELD_SEPARATOR&.to_s&.length || 1
47
- end
41
+ separator_length = find_separator_length(propagation_node, preshift)
48
42
 
49
43
  current_index = 0
50
44
  target.each do |target_elem|
@@ -126,6 +120,19 @@ module Contrast
126
120
 
127
121
  private
128
122
 
123
+ # grapheme_clusters break the string apart based on each "user-perceived" character. Otherwise, the
124
+ # default for String#split is to use a single whitespace.
125
+ #
126
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
127
+ # propagation event.
128
+ # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
129
+ # the invocation of the patched method.
130
+ def find_separator_length propagation_node, preshift
131
+ return 0 if propagation_node.method_name == :grapheme_clusters
132
+
133
+ preshift&.args&.first&.to_s&.length || $FIELD_SEPARATOR&.to_s&.length || 1
134
+ end
135
+
129
136
  # Save index of the current split object.
130
137
  # Create index tracking array as needed.
131
138
  def save_split_index!
@@ -95,10 +95,8 @@ module Contrast
95
95
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
96
96
 
97
97
  incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
98
- parent_event = incoming_properties&.event
99
- parent_events << parent_event if parent_event
98
+ parent_events << incoming_properties&.event if incoming_properties&.event
100
99
 
101
- pattern = preshift.args[0]
102
100
  source = preshift.object
103
101
 
104
102
  # We can't efficiently find the places that things were
@@ -111,6 +109,34 @@ module Contrast
111
109
 
112
110
  # if it's just a straight insert, that we can do
113
111
  # Copy the tags from us to the return
112
+ ranges = find_string_sub_insert(properties, preshift, incoming, ret, global)
113
+
114
+ properties.delete_tags_at_ranges(ranges)
115
+ properties.shift_tags(ranges)
116
+ return unless incoming_tracked
117
+ return unless incoming_properties
118
+
119
+ tags = incoming_properties.tag_keys
120
+ ranges.each do |range|
121
+ tags.each do |tag|
122
+ properties.add_tag(tag, range)
123
+ end
124
+ end
125
+ end
126
+
127
+ # Find the points at which the new String was placed into the original
128
+ #
129
+ # @param properties [Contrast::Agent::Assess::Properties] the Properties of the ret
130
+ # @param preshift [Contrast::Agent::Assess::PreShift] the capture of the state of the code just prior to
131
+ # the invocation of the patched method
132
+ # @param incoming [String] the new String going into the substitution
133
+ # @param ret [String] the result of the substitution
134
+ # @param global [Boolean] if this was a global or single substitution
135
+ # @return [Array<Range>] the Ranges where substitution occurred
136
+ def find_string_sub_insert properties, preshift, incoming, ret, global
137
+ pattern = preshift.args[0]
138
+ source = preshift.object
139
+
114
140
  properties.copy_from(source, ret)
115
141
  # Figure out where inserts occurred
116
142
  last_idx = 0
@@ -126,17 +152,7 @@ module Contrast
126
152
  ranges << (start_index...end_index)
127
153
  break unless global
128
154
  end
129
- properties.delete_tags_at_ranges(ranges)
130
- properties.shift_tags(ranges)
131
- return unless incoming_tracked
132
- return unless incoming_properties
133
-
134
- tags = incoming_properties.tag_keys
135
- ranges.each do |range|
136
- tags.each do |tag|
137
- properties.add_tag(tag, range)
138
- end
139
- end
155
+ ranges
140
156
  end
141
157
 
142
158
  def block_sub self_tracked, source, ret