contrast-agent 3.15.0 → 4.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -0
  3. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +22 -10
  4. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +4 -3
  5. data/lib/contrast/agent.rb +4 -12
  6. data/lib/contrast/agent/assess/contrast_event.rb +121 -130
  7. data/lib/contrast/agent/assess/contrast_object.rb +51 -0
  8. data/lib/contrast/agent/assess/events/source_event.rb +5 -10
  9. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +10 -3
  10. data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
  11. data/lib/contrast/agent/assess/policy/policy_node.rb +46 -69
  12. data/lib/contrast/agent/assess/policy/policy_scanner.rb +19 -2
  13. data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
  14. data/lib/contrast/agent/assess/policy/propagation_method.rb +13 -19
  15. data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
  16. data/lib/contrast/agent/assess/policy/propagator/append.rb +1 -2
  17. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
  18. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  19. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -3
  20. data/lib/contrast/agent/assess/policy/propagator/insert.rb +2 -3
  21. data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
  22. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -5
  23. data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
  24. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
  25. data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
  26. data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
  27. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
  28. data/lib/contrast/agent/assess/policy/propagator/select.rb +4 -7
  29. data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -9
  30. data/lib/contrast/agent/assess/policy/propagator/split.rb +77 -122
  31. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +32 -25
  32. data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
  33. data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
  34. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +9 -13
  35. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  36. data/lib/contrast/agent/assess/policy/trigger_method.rb +39 -14
  37. data/lib/contrast/agent/assess/policy/trigger_node.rb +31 -37
  38. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
  39. data/lib/contrast/agent/assess/property/evented.rb +5 -18
  40. data/lib/contrast/agent/assess/property/tagged.rb +28 -16
  41. data/lib/contrast/agent/assess/property/updated.rb +0 -5
  42. data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +58 -5
  43. data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +23 -8
  44. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +83 -14
  45. data/lib/contrast/agent/assess/rule/redos.rb +1 -1
  46. data/lib/contrast/agent/assess/tag.rb +1 -1
  47. data/lib/contrast/agent/assess/tracker.rb +16 -18
  48. data/lib/contrast/agent/at_exit_hook.rb +5 -5
  49. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
  50. data/lib/contrast/agent/inventory.rb +15 -0
  51. data/lib/contrast/agent/inventory/dependencies.rb +50 -0
  52. data/lib/contrast/agent/inventory/dependency_analysis.rb +37 -0
  53. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +104 -0
  54. data/lib/contrast/agent/inventory/gemfile_digest_cache.rb +38 -0
  55. data/lib/contrast/agent/middleware.rb +51 -3
  56. data/lib/contrast/agent/patching/policy/after_load_patch.rb +5 -5
  57. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +20 -20
  58. data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
  59. data/lib/contrast/agent/patching/policy/module_policy.rb +10 -10
  60. data/lib/contrast/agent/patching/policy/patch.rb +6 -0
  61. data/lib/contrast/agent/patching/policy/policy.rb +16 -2
  62. data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +3 -5
  63. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
  64. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +4 -3
  65. data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
  66. data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
  67. data/lib/contrast/agent/protect/rule/base.rb +63 -14
  68. data/lib/contrast/agent/protect/rule/cmd_injection.rb +12 -28
  69. data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
  70. data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
  71. data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
  72. data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +1 -0
  73. data/lib/contrast/agent/protect/rule/sqli.rb +3 -3
  74. data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
  75. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
  76. data/lib/contrast/agent/reaction_processor.rb +1 -1
  77. data/lib/contrast/agent/request.rb +34 -34
  78. data/lib/contrast/agent/request_handler.rb +1 -1
  79. data/lib/contrast/agent/response.rb +5 -5
  80. data/lib/contrast/agent/rewriter.rb +3 -3
  81. data/lib/contrast/agent/scope.rb +81 -55
  82. data/lib/contrast/agent/static_analysis.rb +15 -9
  83. data/lib/contrast/agent/tracepoint_hook.rb +1 -1
  84. data/lib/contrast/agent/version.rb +1 -1
  85. data/lib/contrast/api/communication/socket_client.rb +36 -1
  86. data/lib/contrast/api/decorators.rb +3 -0
  87. data/lib/contrast/api/decorators/address.rb +13 -14
  88. data/lib/contrast/api/decorators/application_update.rb +1 -1
  89. data/lib/contrast/api/decorators/library.rb +54 -0
  90. data/lib/contrast/api/decorators/library_usage_update.rb +31 -0
  91. data/lib/contrast/api/decorators/message.rb +1 -0
  92. data/lib/contrast/api/decorators/trace_event.rb +31 -41
  93. data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
  94. data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
  95. data/lib/contrast/api/decorators/user_input.rb +2 -1
  96. data/lib/contrast/common_agent_configuration.rb +2 -1
  97. data/lib/contrast/components/agent.rb +6 -5
  98. data/lib/contrast/components/app_context.rb +39 -30
  99. data/lib/contrast/components/assess.rb +36 -0
  100. data/lib/contrast/components/config.rb +29 -37
  101. data/lib/contrast/components/contrast_service.rb +9 -9
  102. data/lib/contrast/components/interface.rb +30 -6
  103. data/lib/contrast/components/inventory.rb +6 -1
  104. data/lib/contrast/components/scope.rb +72 -6
  105. data/lib/contrast/components/settings.rb +23 -23
  106. data/lib/contrast/config/assess_configuration.rb +2 -1
  107. data/lib/contrast/config/inventory_configuration.rb +2 -2
  108. data/lib/contrast/config/service_configuration.rb +4 -2
  109. data/lib/contrast/configuration.rb +1 -1
  110. data/lib/contrast/extension/assess/array.rb +9 -6
  111. data/lib/contrast/extension/assess/erb.rb +6 -3
  112. data/lib/contrast/extension/assess/eval_trigger.rb +6 -6
  113. data/lib/contrast/extension/assess/exec_trigger.rb +0 -3
  114. data/lib/contrast/extension/assess/fiber.rb +5 -6
  115. data/lib/contrast/extension/assess/hash.rb +7 -5
  116. data/lib/contrast/extension/assess/kernel.rb +19 -22
  117. data/lib/contrast/extension/assess/marshal.rb +40 -28
  118. data/lib/contrast/extension/assess/regexp.rb +6 -11
  119. data/lib/contrast/extension/assess/string.rb +14 -13
  120. data/lib/contrast/extension/protect/kernel.rb +3 -3
  121. data/lib/contrast/framework/base_support.rb +51 -53
  122. data/lib/contrast/framework/manager.rb +6 -5
  123. data/lib/contrast/framework/rack/patch/session_cookie.rb +10 -10
  124. data/lib/contrast/framework/rack/support.rb +2 -1
  125. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +14 -14
  126. data/lib/contrast/framework/rails/patch/assess_configuration.rb +1 -1
  127. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +11 -11
  128. data/lib/contrast/framework/rails/patch/support.rb +1 -1
  129. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +12 -12
  130. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +13 -13
  131. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +3 -3
  132. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +13 -13
  133. data/lib/contrast/framework/rails/support.rb +5 -1
  134. data/lib/contrast/framework/sinatra/patch/base.rb +11 -11
  135. data/lib/contrast/framework/sinatra/support.rb +7 -6
  136. data/lib/contrast/logger/application.rb +1 -4
  137. data/lib/contrast/logger/log.rb +7 -2
  138. data/lib/contrast/utils/duck_utils.rb +1 -1
  139. data/lib/contrast/utils/heap_dump_util.rb +1 -1
  140. data/lib/contrast/utils/invalid_configuration_util.rb +2 -5
  141. data/lib/contrast/utils/inventory_util.rb +0 -7
  142. data/lib/contrast/utils/object_share.rb +3 -3
  143. data/lib/contrast/utils/preflight_util.rb +1 -1
  144. data/lib/contrast/utils/prevent_serialization.rb +1 -1
  145. data/lib/contrast/utils/resource_loader.rb +1 -1
  146. data/lib/contrast/utils/sha256_builder.rb +2 -14
  147. data/lib/contrast/utils/string_utils.rb +1 -1
  148. data/lib/contrast/utils/tag_util.rb +9 -13
  149. data/resources/assess/policy.json +31 -12
  150. data/resources/deadzone/policy.json +156 -0
  151. data/resources/protect/policy.json +12 -0
  152. data/ruby-agent.gemspec +11 -6
  153. data/service_executables/VERSION +1 -1
  154. data/service_executables/linux/contrast-service +0 -0
  155. data/service_executables/mac/contrast-service +0 -0
  156. metadata +91 -28
  157. data/lib/contrast/utils/boolean_util.rb +0 -30
  158. data/lib/contrast/utils/gemfile_reader.rb +0 -193
@@ -16,10 +16,9 @@ module Contrast
16
16
  # Unlike additive propagation, this currently only supports one source
17
17
  # We assume that insert changes the preshift target
18
18
  def propagate propagation_node, preshift, target
19
- properties = Contrast::Agent::Assess::Tracker.properties(target)
20
- return unless properties
19
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
21
20
 
22
- source = find_source(propagation_node.sources[0], preshift)
21
+ source = find_source(propagation_node.sources[1], preshift)
23
22
 
24
23
  patcher_target = propagation_node.targets[0]
25
24
  preshift_target = case patcher_target
@@ -14,8 +14,7 @@ module Contrast
14
14
  # Keep means the tags just pass from the source to the target
15
15
  # as is.
16
16
  def propagate propagation_node, preshift, target
17
- properties = Contrast::Agent::Assess::Tracker.properties(target)
18
- return unless properties
17
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
19
18
 
20
19
  source = find_source(propagation_node.sources[0], preshift)
21
20
  properties.copy_from(source, target, 0, propagation_node.untags)
@@ -67,17 +67,15 @@ module Contrast
67
67
  def square_bracket_single argument_index, preshift, return_value, propagation_node
68
68
  original_start_index = preshift.object.begin(argument_index)
69
69
  original_end_index = preshift.object.end(argument_index)
70
- original_properties = Contrast::Agent::Assess::Tracker.properties(preshift.object)
70
+ return unless (original_properties = Contrast::Agent::Assess::Tracker.properties(preshift.object))
71
+
71
72
  applicable_tags = original_properties.tags_at_range(original_start_index...original_end_index)
72
73
  return if applicable_tags.empty?
74
+ return unless (return_properties = Contrast::Agent::Assess::Tracker.properties!(return_value))
73
75
 
74
- return_properties = Contrast::Agent::Assess::Tracker.properties(return_value)
75
76
  applicable_tags.each do |tag_name, tag_ranges|
76
77
  return_properties.set_tags(tag_name, tag_ranges)
77
78
  end
78
- original_properties.events.each do |event|
79
- return_properties.add_event(event)
80
- end
81
79
  return_properties.build_event(propagation_node, return_value, preshift.object, return_value, preshift.args)
82
80
  end
83
81
  end
@@ -15,8 +15,7 @@ module Contrast
15
15
  # String has some silly methods like next. Basically, this flips a
16
16
  # character in a predictable manner
17
17
  def propagate propagation_node, preshift, target
18
- properties = Contrast::Agent::Assess::Tracker.properties(target)
19
- return unless properties
18
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
20
19
 
21
20
  source = find_source(propagation_node.sources[0], preshift)
22
21
  properties.copy_from(source, target, 0, propagation_node.untags)
@@ -14,8 +14,7 @@ module Contrast
14
14
  # For the source, prepend its tags to the target. It's basically the
15
15
  # opposite of append. :-P
16
16
  def propagate propagation_node, preshift, target
17
- properties = Contrast::Agent::Assess::Tracker.properties(target)
18
- return unless properties
17
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
19
18
 
20
19
  sources = propagation_node.sources
21
20
  # source1 is the copy of the thing being prepended to
@@ -17,8 +17,7 @@ module Contrast
17
17
  # Once the tag is applied, remove the section that was removed by the delete.
18
18
  # Unlike additive propagation, this currently only supports one source
19
19
  def propagate propagation_node, preshift, target
20
- properties = Contrast::Agent::Assess::Tracker.properties(target)
21
- return unless properties
20
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
22
21
 
23
22
  source = find_source(propagation_node.sources[0], preshift)
24
23
  properties.copy_from(source, target, 0, propagation_node.untags)
@@ -27,8 +26,7 @@ module Contrast
27
26
  end
28
27
 
29
28
  def handle_removal source_chars, target
30
- properties = Contrast::Agent::Assess::Tracker.properties(target)
31
- return unless properties
29
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
32
30
 
33
31
  source_idx = 0
34
32
 
@@ -14,8 +14,7 @@ module Contrast
14
14
  # Replace means we're replacing the target w/ the source. Anything
15
15
  # on the source should be passed to the target.
16
16
  def propagate propagation_node, preshift, target
17
- properties = Contrast::Agent::Assess::Tracker.properties(target)
18
- return unless properties
17
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
19
18
 
20
19
  source = find_source(propagation_node.sources[0], preshift)
21
20
  properties.clear_tags
@@ -13,8 +13,7 @@ module Contrast
13
13
  class Reverse < Contrast::Agent::Assess::Policy::Propagator::Base
14
14
  class << self
15
15
  def propagate propagation_node, preshift, target
16
- properties = Contrast::Agent::Assess::Tracker.properties(target)
17
- return unless properties
16
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
18
17
 
19
18
  source = find_source(propagation_node.sources[0], preshift)
20
19
  properties.copy_from(source, target, 0, propagation_node.untags)
@@ -14,9 +14,6 @@ module Contrast
14
14
  class Select
15
15
  class << self
16
16
  def select_tagger patcher, preshift, ret, _block
17
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
18
- return unless properties
19
-
20
17
  source = preshift.object
21
18
  args = preshift.args
22
19
 
@@ -27,13 +24,13 @@ module Contrast
27
24
  unless ret &&
28
25
  !ret.empty? &&
29
26
  Contrast::Agent::Assess::Tracker.tracked?(source)
27
+
30
28
  return
31
29
  end
32
30
 
33
- source_properties = Contrast::Agent::Assess::Tracker.properties(source)
34
- source_properties.events.each do |event|
35
- properties.events << event
36
- end
31
+ return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
32
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
33
+
37
34
  properties.build_event(
38
35
  patcher,
39
36
  ret,
@@ -36,25 +36,18 @@ module Contrast
36
36
  end
37
37
 
38
38
  splat_tags(tracked_inputs, target)
39
- Contrast::Agent::Assess::Tracker.properties(target).cleanup_tags
39
+ Contrast::Agent::Assess::Tracker.properties(target)&.cleanup_tags
40
40
  end
41
41
 
42
42
  def splat_tags tracked_inputs, target
43
43
  return if tracked_inputs.empty?
44
-
45
- properties = Contrast::Agent::Assess::Tracker.properties(target)
46
- return unless properties
44
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
47
45
 
48
46
  tracked_inputs.each do |input|
49
47
  input_properties = Contrast::Agent::Assess::Tracker.properties(input)
50
48
  next unless input_properties
51
49
 
52
50
  properties.splat_from(input, target)
53
- next if input == target
54
-
55
- input_properties.events.each do |event|
56
- properties.events << event
57
- end
58
51
  end
59
52
  end
60
53
  end
@@ -15,13 +15,12 @@ module Contrast
15
15
  class Split < Contrast::Agent::Assess::Policy::Propagator::Base
16
16
  include Contrast::Components::Interface
17
17
 
18
- access_component :agent, :logging
18
+ access_component :agent, :logging, :scope
19
19
 
20
20
  SPLIT_TRACKER = Contrast::Utils::ThreadTracker.new
21
+
21
22
  class << self
22
- # Propagate taint from a source as it is split into composite
23
- # sections. This method MUST return nil, otherwise it risks
24
- # changing the result of of the propagation.
23
+ # Propagate taint from a source as it is split into composite sections.
25
24
  #
26
25
  # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode]
27
26
  # the node that governs this propagation event.
@@ -29,178 +28,138 @@ module Contrast
29
28
  # of the state of the code just prior to the invocation of the
30
29
  # patched method.
31
30
  # @param target [Array, String] the target to which to propagate.
32
- # @return [nil]
31
+ # @return [nil] so as not to risk changing the result of the propagation.
32
+
33
33
  def propagate propagation_node, preshift, target
34
34
  logger.trace('Propagation detected',
35
35
  node_id: propagation_node.id,
36
36
  target_id: target.__id__)
37
- unless target.is_a?(Array)
38
- Contrast::Agent::Assess::Policy::Propagator::Keep.propagate(propagation_node, preshift, target)
39
- return
40
- end
41
37
 
42
38
  source = find_source(propagation_node.sources[0], preshift)
39
+ return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
43
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.
44
43
  separator_length = if propagation_node.method_name == :grapheme_clusters
45
- # grapheme_clusters break the string apart based on each "user-perceived" character
46
44
  0
47
45
  else
48
- # The default for String#split is to use a single whitespace
49
46
  preshift&.args&.first&.to_s&.length || $FIELD_SEPARATOR&.to_s&.length || 1
50
47
  end
51
48
 
52
49
  current_index = 0
53
- target.each do |elem|
54
- elem_length = elem.length
55
- range = current_index...(current_index + elem_length)
56
- elem_properties = Contrast::Agent::Assess::Tracker.properties(elem)
57
- next unless elem_properties
50
+ target.each do |target_elem|
51
+ next unless (elem_properties = Contrast::Agent::Assess::Tracker.properties!(target_elem))
58
52
 
59
- source_properties = Contrast::Agent::Assess::Tracker.properties(source)
53
+ # Get tags for element from source by element range.
54
+ range = current_index...(current_index + target_elem.length)
60
55
  tags = source_properties.tags_at_range(range)
56
+
57
+ # Set element properties accordingly.
61
58
  elem_properties.clear_tags
62
- tags.each_pair do |key, value|
63
- elem_properties.set_tags(key, value)
64
- end
65
- source_properties.events.each do |event|
66
- elem_properties.add_event(event)
67
- end
68
- elem_properties.build_event(propagation_node, elem, preshift.object, target, preshift.args, 0)
59
+ tags.each_pair { |key, value| elem_properties.set_tags(key, value) }
60
+ elem_properties.build_event(propagation_node, target_elem, preshift.object, target, preshift.args, 0)
69
61
  elem_properties.add_properties(propagation_node.properties)
70
- current_index = current_index + elem_length + separator_length
62
+
63
+ current_index = range.end + separator_length
71
64
  end
72
65
  nil
73
66
  end
74
67
 
75
- # Marks the point in which the String#split method is called.
76
- # Responsible for building the context required to propagate when
77
- # the results of #split are yielded directly to a block
68
+ # Context for block split execution.
78
69
  #
79
- # @param string [String] the String on which split is invoked
80
- # @param args [Array<Object>] the arguments passed to the
81
- # original split call
82
- def begin_split string, args
83
- save_split_depth!
84
- depth = SPLIT_TRACKER.get(:split_depth)
85
- save_split_index!(depth)
86
- save_split_value!(depth, string, args)
87
- rescue Exception => e # rubocop:disable Lint/RescueException
88
- # don't let our errors propagate and disable String#split for
89
- # this since we're in an error state
90
- logger.warn('Unable to record split context', e)
91
- end_split
92
- end
70
+ # @param string [String] the String on which split is invoked.
71
+ # @param args [Array<Object>] the arguments passed to the original split call.
72
+ def wrap_split string, args
73
+ # String#split start. Build context and yield.
74
+ begin
75
+ enter_split_scope!
76
+ save_split_index!
77
+ save_split_value!(string, args)
78
+ rescue Exception => e # rubocop:disable Lint/RescueException
79
+ logger.warn('Unable to record split context', e)
80
+ end
93
81
 
94
- # Marks the point in which the String#split method is exited.
95
- # Responsible for removing the context required to propagate when
96
- # the results of #split are yielded directly to a block
97
- def end_split
98
- depth = SPLIT_TRACKER.get(:split_depth)
99
- return unless depth
100
-
101
- depth -= 1
102
- if depth.negative?
103
- SPLIT_TRACKER.delete(:split_depth)
104
- SPLIT_TRACKER.delete(:split_index)
105
- SPLIT_TRACKER.delete(:split_value)
106
- else
107
- SPLIT_TRACKER.set(:split_depth, depth)
82
+ yield
83
+ ensure
84
+ # String#split exit. Remove propagation context.
85
+ begin
86
+ exit_split_scope!
87
+ unless in_split_scope?
88
+ SPLIT_TRACKER.delete(:split_index)
89
+ SPLIT_TRACKER.delete(:split_value)
90
+ end
91
+ rescue StandardError => e
92
+ logger.warn('Unable to remove split context', e)
108
93
  end
109
- rescue StandardError => e
110
- logger.warn('Unable to remove split context', e)
111
94
  end
112
95
 
113
- # This method is called whenever an rb_yield is called. We need
114
- # to leave it as soon as possible with as little work as
115
- # possible.
96
+ # This method is called whenever an rb_yield is called.
97
+ # We need to leave it as soon as possible with as little work as possible.
116
98
  #
117
- # @param target [String] the entity being passed to the yield
118
- # block
99
+ # @param target [String] the entity being passed to the yield block
119
100
  def propagate_yield target
120
- depth, index = nil
121
-
122
- depth = SPLIT_TRACKER.get(:split_depth)
123
- return unless depth
124
-
125
- source = SPLIT_TRACKER.get(:split_value)&.fetch(depth)
126
- return unless source
127
-
128
- index = SPLIT_TRACKER.get(:split_index)&.fetch(depth)
129
- return unless index
130
-
131
- properties = Contrast::Agent::Assess::Tracker.properties(target)
132
- return unless properties
101
+ return unless (source = SPLIT_TRACKER.get(:split_value)&.fetch(split_scope_depth))
102
+ return unless (index = SPLIT_TRACKER.get(:split_index)&.fetch(split_scope_depth))
103
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
133
104
 
134
105
  true_source = source[index]
135
106
  properties.copy_from(true_source, target)
136
107
  rescue StandardError => e
137
108
  logger.warn('Unable to track within split context', e)
138
109
  ensure
139
- if depth && index
110
+ if in_split_scope? && index
140
111
  idx = SPLIT_TRACKER.get(:split_index)
141
- idx[depth] = index + 1 if defined?(idx) && idx.is_a?(Array)
112
+ idx[split_scope_depth] = index + 1 if defined?(idx) && idx.is_a?(Array)
142
113
  end
143
114
  end
144
115
 
116
+ # Load patch.
145
117
  def instrument_string_split
146
- if @_instrument_string_split.nil?
147
- @_instrument_string_split = begin
148
- require 'cs__assess_yield_track/cs__assess_yield_track' if AGENT.patch_yield? && Funchook.available?
149
- true
150
- rescue StandardError => e
151
- logger.error('Error loading split rb_yield patch', e)
152
- false
153
- end
118
+ @_instrument_string_split ||= begin
119
+ require 'cs__assess_yield_track/cs__assess_yield_track' if AGENT.patch_yield? && Funchook.available?
120
+ true
121
+ rescue StandardError => e
122
+ logger.error('Error loading split rb_yield patch', e)
123
+ false
154
124
  end
155
- @_instrument_string_split
156
125
  end
157
126
 
158
127
  private
159
128
 
160
- def save_split_depth!
161
- depth = SPLIT_TRACKER.get(:split_depth)
162
- if depth
163
- depth += 1
164
- SPLIT_TRACKER.set(:split_depth, depth)
165
- else
166
- SPLIT_TRACKER.set(:split_depth, 0)
167
- end
168
- end
169
-
170
- def save_split_index! depth
171
- split_index = SPLIT_TRACKER.get(:split_index)
172
- unless split_index
129
+ # Save index of the current split object.
130
+ # Create index tracking array as needed.
131
+ def save_split_index!
132
+ unless (split_index = SPLIT_TRACKER.get(:split_index))
173
133
  split_index = []
174
134
  SPLIT_TRACKER.set(:split_index, split_index)
175
135
  end
176
- # save the index to the ThreadLocal; not useless
177
- split_index[depth] = 0 # rubocop:disable Lint/UselessSetterCall
136
+ # save the index to the ThreadLocal; not useless.
137
+ split_index[split_scope_depth] = 0 # rubocop:disable Lint/UselessSetterCall
178
138
  end
179
139
 
180
- def save_split_value! depth, string, args
140
+ # Save value of the current split object.
141
+ # Create value tracking array as needed.
142
+ def save_split_value! string, args
181
143
  preshift = Contrast::Agent::Assess::PreShift.build_preshift(split_node, string, args)
182
144
  target = string.split
183
145
  propagate(split_node, preshift, target)
184
- split_value = SPLIT_TRACKER.get(:split_value)
185
- unless split_value
146
+ unless (split_value = SPLIT_TRACKER.get(:split_value))
186
147
  split_value = []
187
148
  SPLIT_TRACKER.set(:split_value, split_value)
188
149
  end
189
- # save the target to the ThreadLocal; not useless
190
- split_value[depth] = target # rubocop:disable Lint/UselessSetterCall
150
+ # Save the target to the ThreadLocal; not useless.
151
+ split_value[split_scope_depth] = target # rubocop:disable Lint/UselessSetterCall
191
152
  end
192
153
 
193
- # Quick hook to the String#split propagation node in our Assess
194
- # policy
154
+ # Quick hook to the String#split propagation node in our Assess policy
195
155
  #
196
- # @return [Contrast::Agent::Assess::Policy::PropagationNode]
197
- # String#split node
156
+ # @return [Contrast::Agent::Assess::Policy::PropagationNode] String#split node
198
157
  def split_node
199
158
  @_split_node ||= begin
200
- Contrast::Agent::Assess::Policy::Policy.instance.propagators.find do |node|
201
- node.class_name == 'String' && node.method_name == :split && node.instance_method?
202
- end
203
- end
159
+ Contrast::Agent::Assess::Policy::Policy.instance.propagators.find do |node|
160
+ node.class_name == 'String' && node.method_name == :split && node.instance_method?
161
+ end
162
+ end
204
163
  end
205
164
  end
206
165
  end
@@ -216,19 +175,15 @@ if RUBY_VERSION >= '2.6.0'
216
175
  class String
217
176
  alias_method :cs__patched_string_split_special, :split
218
177
 
219
- # override of the the standard split method to handle the 2.6 direct
220
- # yield case.
178
+ # Override of the the standard split method to handle the 2.6 direct yield case.
221
179
  #
222
180
  # Note: because this patch is applied before our standard propagation, this
223
- # call wrapped in it. As such, any call here happens in scope, so there is
181
+ # call is wrapped in it. As such, any call here happens in scope, so there is
224
182
  # no need to manage it on our own.
225
183
  def split *args, &block
226
184
  if block
227
- Contrast::Agent::Assess::Policy::Propagator::Split.begin_split(self, args)
228
- begin
185
+ Contrast::Agent::Assess::Policy::Propagator::Split.wrap_split(self, args) do
229
186
  cs__patched_string_split_special(*args, &block)
230
- ensure
231
- Contrast::Agent::Assess::Policy::Propagator::Split.end_split
232
187
  end
233
188
  else
234
189
  cs__patched_string_split_special(*args, &block)