contrast-agent 3.16.0 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) 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 +2 -3
  6. data/lib/contrast/agent/assess/contrast_event.rb +49 -130
  7. data/lib/contrast/agent/assess/contrast_object.rb +51 -0
  8. data/lib/contrast/agent/assess/events/source_event.rb +4 -9
  9. data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
  10. data/lib/contrast/agent/assess/policy/policy_node.rb +31 -59
  11. data/lib/contrast/agent/assess/policy/policy_scanner.rb +17 -6
  12. data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
  13. data/lib/contrast/agent/assess/policy/propagation_method.rb +13 -19
  14. data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
  15. data/lib/contrast/agent/assess/policy/propagator/append.rb +1 -2
  16. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
  17. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  18. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -3
  19. data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -2
  20. data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
  21. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -2
  22. data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
  23. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
  24. data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
  25. data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
  26. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
  27. data/lib/contrast/agent/assess/policy/propagator/select.rb +3 -4
  28. data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -4
  29. data/lib/contrast/agent/assess/policy/propagator/split.rb +73 -117
  30. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +11 -11
  31. data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
  32. data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
  33. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +5 -8
  34. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  35. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
  36. data/lib/contrast/agent/assess/property/tagged.rb +21 -15
  37. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +3 -2
  38. data/lib/contrast/agent/assess/rule/redos.rb +1 -1
  39. data/lib/contrast/agent/assess/tracker.rb +16 -18
  40. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
  41. data/lib/contrast/agent/inventory.rb +15 -0
  42. data/lib/contrast/agent/inventory/dependencies.rb +50 -0
  43. data/lib/contrast/agent/inventory/dependency_analysis.rb +37 -0
  44. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +104 -0
  45. data/lib/contrast/agent/inventory/gemfile_digest_cache.rb +38 -0
  46. data/lib/contrast/agent/middleware.rb +51 -3
  47. data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
  48. data/lib/contrast/agent/patching/policy/patch.rb +6 -0
  49. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
  50. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +4 -3
  51. data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
  52. data/lib/contrast/agent/protect/rule/base.rb +63 -14
  53. data/lib/contrast/agent/protect/rule/cmd_injection.rb +12 -28
  54. data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
  55. data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
  56. data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
  57. data/lib/contrast/agent/protect/rule/sqli.rb +3 -3
  58. data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
  59. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
  60. data/lib/contrast/agent/reaction_processor.rb +1 -1
  61. data/lib/contrast/agent/request_handler.rb +1 -1
  62. data/lib/contrast/agent/response.rb +5 -5
  63. data/lib/contrast/agent/rewriter.rb +3 -3
  64. data/lib/contrast/agent/scope.rb +81 -55
  65. data/lib/contrast/agent/static_analysis.rb +14 -8
  66. data/lib/contrast/agent/tracepoint_hook.rb +1 -1
  67. data/lib/contrast/agent/version.rb +1 -1
  68. data/lib/contrast/api/decorators.rb +3 -0
  69. data/lib/contrast/api/decorators/address.rb +0 -1
  70. data/lib/contrast/api/decorators/application_update.rb +1 -1
  71. data/lib/contrast/api/decorators/library.rb +54 -0
  72. data/lib/contrast/api/decorators/library_usage_update.rb +31 -0
  73. data/lib/contrast/api/decorators/trace_event.rb +19 -31
  74. data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
  75. data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
  76. data/lib/contrast/api/decorators/user_input.rb +2 -1
  77. data/lib/contrast/common_agent_configuration.rb +2 -1
  78. data/lib/contrast/components/agent.rb +6 -5
  79. data/lib/contrast/components/assess.rb +36 -0
  80. data/lib/contrast/components/config.rb +29 -37
  81. data/lib/contrast/components/interface.rb +30 -6
  82. data/lib/contrast/components/inventory.rb +6 -1
  83. data/lib/contrast/components/scope.rb +72 -6
  84. data/lib/contrast/components/settings.rb +6 -3
  85. data/lib/contrast/config/assess_configuration.rb +2 -1
  86. data/lib/contrast/config/inventory_configuration.rb +2 -2
  87. data/lib/contrast/extension/assess/array.rb +2 -3
  88. data/lib/contrast/extension/assess/erb.rb +1 -3
  89. data/lib/contrast/extension/assess/exec_trigger.rb +1 -4
  90. data/lib/contrast/extension/assess/fiber.rb +2 -3
  91. data/lib/contrast/extension/assess/hash.rb +4 -2
  92. data/lib/contrast/extension/assess/kernel.rb +1 -2
  93. data/lib/contrast/extension/assess/marshal.rb +34 -26
  94. data/lib/contrast/extension/assess/regexp.rb +3 -8
  95. data/lib/contrast/extension/assess/string.rb +1 -2
  96. data/lib/contrast/framework/base_support.rb +51 -53
  97. data/lib/contrast/framework/manager.rb +3 -2
  98. data/lib/contrast/framework/rack/patch/session_cookie.rb +2 -2
  99. data/lib/contrast/framework/rack/support.rb +2 -1
  100. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +1 -1
  101. data/lib/contrast/framework/rails/patch/assess_configuration.rb +1 -1
  102. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +1 -1
  103. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +1 -1
  104. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +1 -1
  105. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +1 -1
  106. data/lib/contrast/framework/rails/support.rb +5 -1
  107. data/lib/contrast/framework/sinatra/support.rb +3 -2
  108. data/lib/contrast/logger/application.rb +1 -4
  109. data/lib/contrast/utils/duck_utils.rb +1 -1
  110. data/lib/contrast/utils/heap_dump_util.rb +1 -1
  111. data/lib/contrast/utils/inventory_util.rb +0 -7
  112. data/lib/contrast/utils/object_share.rb +3 -3
  113. data/lib/contrast/utils/preflight_util.rb +1 -1
  114. data/lib/contrast/utils/prevent_serialization.rb +1 -1
  115. data/lib/contrast/utils/resource_loader.rb +1 -1
  116. data/lib/contrast/utils/sha256_builder.rb +2 -14
  117. data/lib/contrast/utils/string_utils.rb +1 -1
  118. data/lib/contrast/utils/tag_util.rb +9 -13
  119. data/resources/assess/policy.json +9 -9
  120. data/resources/deadzone/policy.json +150 -0
  121. data/resources/protect/policy.json +12 -0
  122. data/ruby-agent.gemspec +10 -6
  123. data/service_executables/VERSION +1 -1
  124. data/service_executables/linux/contrast-service +0 -0
  125. data/service_executables/mac/contrast-service +0 -0
  126. metadata +76 -27
  127. data/lib/contrast/utils/boolean_util.rb +0 -30
  128. data/lib/contrast/utils/gemfile_reader.rb +0 -193
@@ -19,8 +19,8 @@ module Contrast
19
19
  @source_string = policy_hash[JSON_SOURCE]
20
20
  @target_string = policy_hash[JSON_TARGET]
21
21
  @tags = Set.new(policy_hash[JSON_TAGS])
22
- generate_sources
23
- generate_targets
22
+ @sources = convert_policy_markers(source_string)
23
+ @targets = convert_policy_markers(target_string)
24
24
  end
25
25
 
26
26
  def feature
@@ -47,7 +47,7 @@ module Contrast
47
47
 
48
48
  def target_string= value
49
49
  @target_string = value
50
- generate_targets
50
+ @targets = convert_policy_markers(value)
51
51
  end
52
52
 
53
53
  # Sometimes we need to tie information to an event. We'll add a
@@ -66,62 +66,6 @@ module Contrast
66
66
  @properties[name]
67
67
  end
68
68
 
69
- # Given a source in the format A,B,C, populate the sources of this node
70
- # 1) Split on ','
71
- # 2) If 'O', add the source, else it's P (we don't have R sources) and
72
- # needs to be converted. P type will either be P:name or P# where #
73
- # is the index of the parameter. Drop the P and store the int as int
74
- # or name as symbol
75
- def generate_sources
76
- if source_string
77
- @sources = []
78
- source_string.split(Contrast::Utils::ObjectShare::COMMA).each do |s|
79
- is_object = (s == Contrast::Utils::ObjectShare::OBJECT_KEY)
80
- if is_object
81
- @sources << s
82
- else
83
- parameter_source = s[1..-1]
84
- @sources << if parameter_source.start_with?(Contrast::Utils::ObjectShare::COLON)
85
- parameter_source[1..-1].to_sym
86
- else
87
- parameter_source.to_i
88
- end
89
- end
90
- end
91
- else
92
- @sources = Contrast::Utils::ObjectShare::EMPTY_ARRAY
93
- end
94
- end
95
-
96
- # Given a target in the format A,B,C, populate the targets of this node
97
- # 1) Split on ','
98
- # 2) If 'O' or 'R', add the target, else it's P and needs to be
99
- # converted. P type will either be P:name or P# where # is the index
100
- # of the paramter. Drop the P and store the int as int or name as
101
- # symbol
102
- def generate_targets
103
- if target_string
104
- @targets = []
105
- target_string.split(Contrast::Utils::ObjectShare::COMMA).each do |t|
106
- case t
107
- when Contrast::Utils::ObjectShare::OBJECT_KEY
108
- @targets << t
109
- when Contrast::Utils::ObjectShare::RETURN_KEY
110
- @targets << t
111
- else
112
- parameter_target = t[1..-1]
113
- @targets << if parameter_target.start_with?(Contrast::Utils::ObjectShare::COLON)
114
- parameter_target[1..-1].to_sym
115
- else
116
- parameter_target.to_i
117
- end
118
- end
119
- end
120
- else
121
- @targets = Contrast::Utils::ObjectShare::EMPTY_ARRAY
122
- end
123
- end
124
-
125
69
  # Don't let nodes be created that will be missing things we need
126
70
  # later on. Really, if they don't have these things, they couldn't have
127
71
  # done their jobs anyway.
@@ -186,6 +130,34 @@ module Contrast
186
130
  JSON_TARGET = 'target'
187
131
  JSON_TAGS = 'tags'
188
132
  JSON_DATAFLOW = 'dataflow'
133
+
134
+ private
135
+
136
+ # Given a policy string in the format A,B,C, populate the given array
137
+ # 1) Split on ','
138
+ # 2) If 'O' or 'R', add the array, else it's P and needs to be
139
+ # converted. P type will either be P# where # is the index
140
+ # of the parameter. Drop the P and store the # as an int.
141
+ #
142
+ # @param markers [String] the String from the policy to parse
143
+ # @return [Array] the array generated by converting the marker string
144
+ def convert_policy_markers markers
145
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless markers
146
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY if markers.empty?
147
+
148
+ converted = []
149
+ markers.split(Contrast::Utils::ObjectShare::COMMA).each do |t|
150
+ case t
151
+ when Contrast::Utils::ObjectShare::OBJECT_KEY,
152
+ Contrast::Utils::ObjectShare::RETURN_KEY
153
+
154
+ converted << t
155
+ else
156
+ converted << Integer(t[1..-1])
157
+ end
158
+ end
159
+ converted
160
+ end
189
161
  end
190
162
  end
191
163
  end
@@ -17,22 +17,33 @@ module Contrast
17
17
  access_component :analysis
18
18
 
19
19
  class << self
20
+ # Use the given trace_point, built from an :end event, to determine
21
+ # where the loaded code lives and scan that code for policy
22
+ # violations.
23
+ #
24
+ # @param trace_point [TracePoint] the TracePoint generated by an
25
+ # :end event at the end of a Module definition.
20
26
  def scan trace_point
21
27
  return unless ASSESS.enabled?
22
28
  return unless ASSESS.require_scan?
23
29
 
30
+ provider_values = policy.providers.values
31
+ return if provider_values.all?(&:disabled?)
32
+
24
33
  return unless trace_point.path
25
34
  return if trace_point.path.start_with?(Gem.dir)
26
35
 
27
36
  mod = trace_point.self
28
37
  return if mod.cs__frozen? || mod.singleton_class?
29
38
 
30
- # TODO: RUBY-1013 - get AST here instead of TP, so we only need
31
- # to make one per provider, instead of one per rule
32
- policy.providers.each_value do |provider|
33
- if RUBY_VERSION >= '2.6.0'
34
- provider.parse(trace_point)
35
- else # TODO: RUBY-1014 - remove alternative
39
+ # TODO: RUBY-1014 - remove non-AST approach
40
+ if RUBY_VERSION >= '2.6.0'
41
+ ast = RubyVM::AbstractSyntaxTree.parse_file(trace_point.path)
42
+ provider_values.each do |provider|
43
+ provider.parse(trace_point, ast)
44
+ end
45
+ else
46
+ provider_values.each do |provider|
36
47
  provider.analyze(mod)
37
48
  end
38
49
  end
@@ -36,11 +36,11 @@ module Contrast
36
36
  # the state of the object and arguments just prior to the method
37
37
  # being called or nil if one is not required.
38
38
  def build_preshift propagation_node, object, args
39
- return nil unless propagation_node
40
- return nil unless ASSESS.enabled?
39
+ return unless propagation_node
40
+ return unless ASSESS.enabled?
41
41
 
42
42
  initializing = propagation_node.method_name == :initialize
43
- return nil if unsafe_io_object?(object, initializing)
43
+ return if unsafe_io_object?(object, initializing)
44
44
 
45
45
  needs_object = propagation_node.needs_object?
46
46
  needs_args = propagation_node.needs_args?
@@ -37,20 +37,15 @@ module Contrast
37
37
 
38
38
  class << self
39
39
  def determine_target propagation_node, ret, object, args
40
- target_key = propagation_node.targets[0]
41
- return ret if target_key == Contrast::Utils::ObjectShare::RETURN_KEY
42
- return object if target_key == Contrast::Utils::ObjectShare::OBJECT_KEY
43
-
44
- return args[target_key] if target_key.is_a?(Integer)
45
-
46
- arg = nil
47
- args.each do |search|
48
- next unless search.is_a?(Hash)
49
-
50
- arg = search[target_key]
51
- break if arg
40
+ target = propagation_node.targets[0]
41
+ case target
42
+ when Contrast::Utils::ObjectShare::OBJECT_KEY
43
+ object
44
+ when Contrast::Utils::ObjectShare::RETURN_KEY
45
+ ret
46
+ else
47
+ args[target]
52
48
  end
53
- arg
54
49
  end
55
50
 
56
51
  # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy]
@@ -208,8 +203,8 @@ module Contrast
208
203
  # If this patcher has tags, apply them to the entire target
209
204
  def apply_tags propagation_node, target
210
205
  return unless propagation_node.tags
206
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties(target))
211
207
 
212
- properties = Contrast::Agent::Assess::Tracker.properties(target)
213
208
  length = Contrast::Utils::StringUtils.ret_length(target)
214
209
  propagation_node.tags.each do |tag|
215
210
  properties.add_tag(tag, 0...length)
@@ -219,9 +214,7 @@ module Contrast
219
214
  # If this patcher has tags, remove them from the entire target
220
215
  def apply_untags propagation_node, target
221
216
  return unless propagation_node.untags
222
-
223
- properties = Contrast::Agent::Assess::Tracker.properties(target)
224
- return unless properties
217
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties(target))
225
218
 
226
219
  propagation_node.untags.each do |tag|
227
220
  properties.delete_tags(tag)
@@ -256,7 +249,7 @@ module Contrast
256
249
 
257
250
  def handle_enumerable_propagation propagation_node, preshift, target, object, ret, args, block
258
251
  target.each do |value|
259
- next if target == value # Some Enumerable#each are overriden to return self the first time which leads to infinite propagation
252
+ next if target == value # Some Enumerable#each are overridden to return self the first time which leads to infinite propagation
260
253
 
261
254
  apply_propagator(propagation_node, preshift, value, object, ret, args, block)
262
255
  end
@@ -300,7 +293,8 @@ module Contrast
300
293
  # both and there should never be a propagator that has a tag in
301
294
  # its untag.
302
295
  apply_untags(propagation_node, target)
303
- properties = Contrast::Agent::Assess::Tracker.properties(target)
296
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
297
+
304
298
  properties.add_properties(propagation_node.properties)
305
299
  properties.build_event(propagation_node, target, object, ret, args)
306
300
  logger.trace('Propagation detected',
@@ -83,35 +83,23 @@ module Contrast
83
83
  end
84
84
 
85
85
  def needs_object?
86
- @_needs_object ||= begin
87
- if action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION
88
- true
89
- elsif action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION
90
- true
91
- elsif sources.any? { |source| source == Contrast::Utils::ObjectShare::OBJECT_KEY }
92
- true
93
- elsif targets.any? { |target| target == Contrast::Utils::ObjectShare::OBJECT_KEY }
94
- true
95
- else
96
- false
97
- end
86
+ if @_needs_object.nil?
87
+ @_needs_object = action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION ||
88
+ action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION ||
89
+ sources.any? { |source| source == Contrast::Utils::ObjectShare::OBJECT_KEY } ||
90
+ targets.any? { |target| target == Contrast::Utils::ObjectShare::OBJECT_KEY }
98
91
  end
92
+ @_needs_object
99
93
  end
100
94
 
101
95
  def needs_args?
102
- @_needs_args ||= begin
103
- if action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION
104
- true
105
- elsif action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION
106
- true
107
- elsif sources.any? { |source| source.is_a?(Integer) || source.is_a?(Symbol) }
108
- true
109
- elsif targets.any? { |target| target.is_a?(Integer) || target.is_a?(Symbol) }
110
- true
111
- else
112
- false
113
- end
96
+ if @_needs_args.nil?
97
+ @_needs_args = action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION ||
98
+ action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION ||
99
+ sources.any? { |source| source.is_a?(Integer) || source.is_a?(Symbol) } ||
100
+ targets.any? { |target| target.is_a?(Integer) || target.is_a?(Symbol) }
114
101
  end
102
+ @_needs_args
115
103
  end
116
104
 
117
105
  # This is a tagger if it has a tag or an untag.
@@ -16,8 +16,7 @@ module Contrast
16
16
  # copy tags from the param to the target in chunks of param size or less
17
17
  # if param is appended in space less than param length
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
21
  sources = propagation_node.sources
23
22
  source1 = find_source(sources[0], preshift)
@@ -12,8 +12,7 @@ module Contrast
12
12
  class Center < Contrast::Agent::Assess::Policy::Propagator::Base
13
13
  class << self
14
14
  def propagate propagation_node, preshift, target
15
- properties = Contrast::Agent::Assess::Tracker.properties(target)
16
- return unless properties
15
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
17
16
 
18
17
  sources = propagation_node.sources
19
18
  source1 = find_source(sources[0], preshift)
@@ -12,7 +12,7 @@ module Contrast
12
12
  # of tags from the source to the target. Each node with the CUSTOM
13
13
  # action knows the class and method it should call to preform this
14
14
  # action.
15
- class Custom
15
+ module Custom
16
16
  class << self
17
17
  def propagate propagation_node, preshift, ret, block
18
18
  clazz = propagation_node.patch_class
@@ -30,9 +30,7 @@ module Contrast
30
30
  arg.each_pair do |key, value|
31
31
  next unless value
32
32
  next if known_tainted&.include?(key)
33
-
34
- properties = Contrast::Agent::Assess::Tracker.properties(value)
35
- next unless properties
33
+ next unless (properties = Contrast::Agent::Assess::Tracker.properties!(value))
36
34
 
37
35
  # TODO: RUBY-540 handle sanitization, handle nested objects
38
36
  Contrast::Agent::Assess::Policy::PropagationMethod.apply_tags(propagation_node, value)
@@ -16,8 +16,7 @@ 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
21
  source = find_source(propagation_node.sources[1], preshift)
23
22
 
@@ -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,11 +67,12 @@ 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
@@ -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
 
@@ -31,7 +28,9 @@ module Contrast
31
28
  return
32
29
  end
33
30
 
34
- source_properties = Contrast::Agent::Assess::Tracker.properties(source)
31
+ return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
32
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
33
+
35
34
  properties.build_event(
36
35
  patcher,
37
36
  ret,