contrast-agent 4.9.1 → 4.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rspec_parallel +6 -0
  4. data/ext/cs__assess_module/cs__assess_module.c +48 -0
  5. data/ext/cs__assess_module/cs__assess_module.h +7 -0
  6. data/ext/cs__common/cs__common.c +24 -7
  7. data/ext/cs__common/cs__common.h +12 -2
  8. data/ext/cs__contrast_patch/cs__contrast_patch.c +48 -12
  9. data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -4
  10. data/ext/cs__os_information/cs__os_information.c +31 -0
  11. data/ext/cs__os_information/cs__os_information.h +7 -0
  12. data/ext/{cs__protect_kernel → cs__os_information}/extconf.rb +0 -0
  13. data/lib/contrast/agent/assess/contrast_event.rb +1 -2
  14. data/lib/contrast/agent/assess/contrast_object.rb +1 -4
  15. data/lib/contrast/agent/assess/finalizers/hash.rb +0 -1
  16. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
  17. data/lib/contrast/agent/assess/policy/patcher.rb +0 -1
  18. data/lib/contrast/agent/assess/policy/policy_scanner.rb +0 -2
  19. data/lib/contrast/agent/assess/policy/preshift.rb +29 -12
  20. data/lib/contrast/agent/assess/policy/propagation_method.rb +71 -142
  21. data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
  22. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -2
  23. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +31 -11
  24. data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -9
  25. data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -2
  26. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +1 -0
  27. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +0 -1
  28. data/lib/contrast/agent/assess/policy/source_method.rb +15 -88
  29. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -1
  30. data/lib/contrast/agent/assess/policy/trigger_method.rb +45 -172
  31. data/lib/contrast/agent/assess/policy/trigger_node.rb +52 -19
  32. data/lib/contrast/agent/assess/property/evented.rb +2 -1
  33. data/lib/contrast/agent/assess/property/tagged.rb +15 -132
  34. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +0 -1
  35. data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
  36. data/lib/contrast/agent/disable_reaction.rb +1 -1
  37. data/lib/contrast/agent/exclusion_matcher.rb +0 -4
  38. data/lib/contrast/agent/inventory/database_config.rb +117 -0
  39. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +7 -5
  40. data/lib/contrast/agent/inventory/policy/datastores.rb +2 -2
  41. data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
  42. data/lib/contrast/agent/middleware.rb +23 -0
  43. data/lib/contrast/agent/patching/policy/after_load_patch.rb +3 -0
  44. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +17 -12
  45. data/lib/contrast/agent/patching/policy/method_policy.rb +54 -9
  46. data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
  47. data/lib/contrast/agent/patching/policy/patch.rb +42 -238
  48. data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
  49. data/lib/contrast/agent/patching/policy/patcher.rb +10 -49
  50. data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
  51. data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
  52. data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
  53. data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
  54. data/lib/contrast/agent/reaction_processor.rb +1 -1
  55. data/lib/contrast/agent/request.rb +9 -4
  56. data/lib/contrast/agent/request_context.rb +51 -33
  57. data/lib/contrast/agent/request_handler.rb +7 -3
  58. data/lib/contrast/agent/rule_set.rb +2 -4
  59. data/lib/contrast/agent/scope.rb +32 -20
  60. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +71 -0
  61. data/lib/contrast/agent/static_analysis.rb +5 -3
  62. data/lib/contrast/agent/telemetry.rb +129 -0
  63. data/lib/contrast/agent/telemetry_event.rb +34 -0
  64. data/lib/contrast/agent/thread_watcher.rb +43 -14
  65. data/lib/contrast/agent/tracepoint_hook.rb +16 -3
  66. data/lib/contrast/agent/version.rb +1 -1
  67. data/lib/contrast/agent.rb +6 -1
  68. data/lib/contrast/api/communication/messaging_queue.rb +12 -6
  69. data/lib/contrast/api/communication/service_lifecycle.rb +4 -1
  70. data/lib/contrast/api/communication/socket_client.rb +4 -4
  71. data/lib/contrast/api/decorators/agent_startup.rb +4 -4
  72. data/lib/contrast/api/decorators/application_startup.rb +6 -5
  73. data/lib/contrast/api/decorators/route_coverage.rb +24 -1
  74. data/lib/contrast/components/agent.rb +5 -2
  75. data/lib/contrast/components/api.rb +34 -0
  76. data/lib/contrast/components/app_context.rb +24 -0
  77. data/lib/contrast/components/assess.rb +13 -3
  78. data/lib/contrast/components/base.rb +2 -2
  79. data/lib/contrast/components/config.rb +91 -11
  80. data/lib/contrast/components/contrast_service.rb +10 -2
  81. data/lib/contrast/components/logger.rb +13 -8
  82. data/lib/contrast/components/scope.rb +9 -28
  83. data/lib/contrast/config/api_configuration.rb +22 -0
  84. data/lib/contrast/config/assess_configuration.rb +1 -0
  85. data/lib/contrast/config/base_configuration.rb +14 -6
  86. data/lib/contrast/config/env_variables.rb +25 -0
  87. data/lib/contrast/config/root_configuration.rb +1 -0
  88. data/lib/contrast/config/service_configuration.rb +2 -1
  89. data/lib/contrast/config.rb +1 -0
  90. data/lib/contrast/configuration.rb +22 -15
  91. data/lib/contrast/extension/assess/array.rb +1 -11
  92. data/lib/contrast/extension/assess/eval_trigger.rb +0 -20
  93. data/lib/contrast/extension/assess/fiber.rb +0 -11
  94. data/lib/contrast/extension/assess/hash.rb +0 -10
  95. data/lib/contrast/extension/assess/kernel.rb +1 -10
  96. data/lib/contrast/extension/assess/marshal.rb +3 -11
  97. data/lib/contrast/extension/assess/regexp.rb +0 -11
  98. data/lib/contrast/extension/assess/string.rb +1 -26
  99. data/lib/contrast/extension/extension.rb +61 -0
  100. data/lib/contrast/framework/grape/support.rb +174 -0
  101. data/lib/contrast/framework/manager.rb +56 -18
  102. data/lib/contrast/framework/rack/support.rb +1 -1
  103. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
  104. data/lib/contrast/framework/rails/patch/assess_configuration.rb +0 -1
  105. data/lib/contrast/framework/rails/patch/support.rb +35 -30
  106. data/lib/contrast/framework/rails/railtie.rb +1 -1
  107. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +1 -0
  108. data/lib/contrast/framework/rails/support.rb +60 -13
  109. data/lib/contrast/framework/sinatra/support.rb +1 -1
  110. data/lib/contrast/logger/application.rb +4 -0
  111. data/lib/contrast/logger/log.rb +89 -15
  112. data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
  113. data/lib/contrast/utils/assess/property/tagged_utils.rb +142 -0
  114. data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
  115. data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
  116. data/lib/contrast/utils/class_util.rb +58 -44
  117. data/lib/contrast/utils/exclude_key.rb +20 -0
  118. data/lib/contrast/utils/io_util.rb +43 -35
  119. data/lib/contrast/utils/lru_cache.rb +45 -0
  120. data/lib/contrast/utils/metrics_hash.rb +59 -0
  121. data/lib/contrast/utils/os.rb +23 -0
  122. data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
  123. data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
  124. data/lib/contrast/utils/requests_client.rb +150 -0
  125. data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
  126. data/lib/contrast/utils/tag_util.rb +2 -1
  127. data/lib/contrast/utils/telemetry.rb +78 -0
  128. data/lib/contrast/utils/telemetry_identifier.rb +137 -0
  129. data/lib/contrast.rb +19 -1
  130. data/resources/assess/policy.json +208 -7
  131. data/resources/deadzone/policy.json +91 -0
  132. data/ruby-agent.gemspec +12 -2
  133. data/service_executables/VERSION +1 -1
  134. data/service_executables/linux/contrast-service +0 -0
  135. data/service_executables/mac/contrast-service +0 -0
  136. metadata +102 -18
  137. data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
  138. data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
  139. data/lib/contrast/extension/protect/kernel.rb +0 -39
  140. data/lib/contrast/utils/inventory_util.rb +0 -113
@@ -6,35 +6,35 @@ require 'contrast/agent/assess/policy/trigger_validation/trigger_validation'
6
6
  require 'contrast/components/logger'
7
7
  require 'contrast/utils/object_share'
8
8
  require 'contrast/utils/sha256_builder'
9
+ require 'contrast/utils/assess/trigger_method_utils'
9
10
 
10
11
  module Contrast
11
12
  module Agent
12
13
  module Assess
13
14
  module Policy
14
- # A trigger method is one which can perform a dangerous action, as
15
- # described by the Contrast::Agent::Assess::Policy::TriggerNode class.
16
- # Each such method will call to this module just after invocation in
17
- # order to determine if the call was done safely. In those cases where
18
- # it was not, a Finding report is issued to the Service
15
+ # A trigger method is one which can perform a dangerous action, as described by the
16
+ # Contrast::Agent::Assess::Policy::TriggerNode class. Each such method will call to this module just after
17
+ # invocation in order to determine if the call was done safely. In those cases where it was not, a Finding
18
+ # report is issued to the Service.
19
19
  module TriggerMethod
20
20
  extend Contrast::Components::Logger::InstanceMethods
21
+ extend Contrast::Utils::Assess::TriggerMethodUtils
21
22
 
22
- # The level of TeamServer compliance our traces meet when in the
23
- # abnormal condition of being dataflow rules without routes
23
+ # The level of TeamServer compliance our traces meet when in the abnormal condition of being dataflow rules
24
+ # without routes.
24
25
  MINIMUM_FINDING_VERSION = 3
25
- # The level of TeamServer compliance our traces meet
26
+ # The level of TeamServer compliance our traces meet.
26
27
  CURRENT_FINDING_VERSION = 4
27
28
 
28
29
  class << self
29
- # This is called from within our woven proc. It will be called as if it
30
- # were inline in the Rack application.
30
+ # This is called from within our woven proc. It will be called as if it were inline in the Rack
31
+ # application.
31
32
  #
32
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
33
- # the node that applies to the method being called
33
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node that applies to the method
34
+ # being called
34
35
  # @param object [Object] the Object on which the method was invoked
35
36
  # @param ret [Object] the Return of the invoked method
36
- # @param args [Array<Object>] the Arguments with which the method
37
- # was invoked
37
+ # @param args [Array<Object>] the Arguments with which the method was invoked
38
38
  def apply_trigger_rule trigger_node, object, ret, args
39
39
  return if trigger_node.nil?
40
40
 
@@ -52,19 +52,16 @@ module Contrast
52
52
  apply_trigger(trigger_node, source, object, ret, *args)
53
53
  end
54
54
 
55
- # This converts the source of the finding, and the events leading
56
- # up to it into a Finding
55
+ # This converts the source of the finding, and the events leading up to it into a Finding
57
56
  #
58
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
59
- # the node to direct applying this trigger event
57
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
58
+ # trigger event
60
59
  # @param source [Object] the source of the Trigger Event
61
60
  # @param object [Object] the Object on which the method was invoked
62
61
  # @param ret [Object] the Return of the invoked method
63
- # @param args [Array<Object>] the Arguments with which the method
64
- # was invoked
65
- # @return [Contrast::Api::Dtm::Finding, nil] the
66
- # Contrast::Api::Dtm::Finding to send to TeamServer or nil if
67
- # conditions were not met
62
+ # @param args [Array<Object>] the Arguments with which the method was invoked
63
+ # @return [Contrast::Api::Dtm::Finding, nil] the Contrast::Api::Dtm::Finding to send to TeamServer or
64
+ # nil if conditions were not met
68
65
  def build_finding trigger_node, source, object, ret, *args
69
66
  return unless Contrast::Agent::Assess::Policy::TriggerValidation.valid?(trigger_node, object, ret, args)
70
67
 
@@ -85,13 +82,14 @@ module Contrast
85
82
  logger.error('Unable to build a finding', e, rule: trigger_node.rule_id, node_id: trigger_node.id)
86
83
  end
87
84
 
88
- # Given a finding, append it to an activity message and send it to
89
- # the Service for processing.
85
+ # Given a finding, append it to an activity message and send it to the Service for processing. If an
86
+ # activity message does not exist, b/c we're invoked outside of a request context, build an activity and
87
+ # immediately report it with the finding.
88
+ #
89
+ # TODO: RUBY-1351
90
90
  #
91
- # @param finding [Contrast::Api::Dtm::Finding] the Finding to
92
- # report.
93
- # @param request [Contrast::Agent::Request] our wrapper around the
94
- # Rack::Request.
91
+ # @param finding [Contrast::Api::Dtm::Finding] the Finding to report.
92
+ # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
95
93
  def report_finding finding, request = nil
96
94
  context = Contrast::Agent::REQUEST_TRACKER.current
97
95
  if context
@@ -107,94 +105,28 @@ module Contrast
107
105
  logger.debug('Attempted to report finding without request', finding: finding)
108
106
  end
109
107
 
110
- # If we're out of request context, then we need to report this
111
- # finding ourselves, so we'll send it in the one-off activity we
112
- # created.
108
+ # If we're out of request context, then we need to report this finding ourselves, so we'll send it in the
109
+ # one-off activity we created.
113
110
  Contrast::Agent.messaging_queue.send_event_eventually(activity)
114
111
  end
115
112
 
116
113
  private
117
114
 
118
- # A request is reportable if it is not from ActionController::Live
119
- #
120
- # @param env [Hash] the env of the Request
121
- # @return [Boolean]
122
- def reportable? env
123
- !(defined?(ActionController::Live) &&
124
- env &&
125
- env['action_controller.instance'].cs__class.included_modules.include?(ActionController::Live))
126
- end
127
-
128
- # Find the request for this finding. This assumes, for now, that if
129
- # there is an active request, then that is the request to report.
130
- # Otherwise, we'll use the first request found in the events of the
131
- # source_object.
132
- #
133
- # @param source [Object,nil] some Object used as the source of a
134
- # trigger event
135
- # @return [Contrast::Agent::Request,nil] the request from which the
136
- # dataflow on the request originated.
137
- def find_request source
138
- return Contrast::Agent::REQUEST_TRACKER.current.request if Contrast::Agent::REQUEST_TRACKER.current
139
- return unless (properties = Contrast::Agent::Assess::Tracker.properties(source))
140
-
141
- properties.events.each do |event|
142
- next unless event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
143
-
144
- return event.request if event.request
145
- end
146
- nil
147
- end
148
-
149
115
  def settings
150
116
  Contrast::Agent::FeatureState.instance
151
117
  end
152
118
 
153
- # This is our method that actually checks the taint on the object
154
- # our trigger_node targets.
155
- #
156
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
157
- # the node to direct applying this trigger event
158
- # @param source [Object] the source of the Trigger Event
159
- # @param object [Object] the Object on which the method was invoked
160
- # @param ret [Object] the Return of the invoked method
161
- # @param args [Array<Object>] the Arguments with which the method
162
- # was invoked
163
- def apply_trigger trigger_node, source, object, ret, *args
164
- return unless trigger_node
165
- return if trigger_node.rule_disabled?
166
- return if trigger_node.dataflow? && source.nil?
167
-
168
- if trigger_node.regexp_rule?
169
- apply_regexp_rule(trigger_node, source, object, ret, *args)
170
- elsif trigger_node.custom_trigger?
171
- trigger_node.apply_custom_trigger(trigger_node, source, object, ret, *args)
172
- elsif trigger_node.dataflow?
173
- apply_dataflow_rule(trigger_node, source, object, ret, *args)
174
- else # trigger rule - just calling the method is dangerous
175
- build_finding(trigger_node, source, object, ret, *args)
176
- end
177
- rescue StandardError => e
178
- logger.warn('Unable to apply trigger', e, node_id: trigger_node.id)
179
- end
180
-
181
- # Given the marker from the trigger_node (the pointer indicating
182
- # the entity from which the taint originated), return the entity on
183
- # which this trigger needs to operate.
119
+ # Given the marker from the trigger_node (the pointer indicating the entity from which the taint
120
+ # originated), return the entity on which this trigger needs to operate.
184
121
  #
185
- # In an effort to speed up this lookup, we've changed the marker
186
- # for parameters to be implicit - if it is not a return or an
187
- # object, it must be a parameter, which we can reference either by
188
- # index or by name.
122
+ # In an effort to speed up this lookup, we've changed the marker for parameters to be implicit - if it is
123
+ # not a return or an object, it must be a parameter, which we can reference by index.
189
124
  #
190
- # @param marker [String] the source marker that indicates which
191
- # Object
125
+ # @param marker [String] the source marker that indicates which Object
192
126
  # @param object [Object] the Object on which the method was invoked
193
127
  # @param ret [Object] the Return of the invoked method
194
- # @param args [Array<Object>] the Arguments with which the method
195
- # was invoked
196
- # @return [Object] the literal object that this Trigger Event
197
- # verifies
128
+ # @param args [Array<Object>] the Arguments with which the method was invoked
129
+ # @return [Object] the literal object that this Trigger Event verifies
198
130
  def determine_source marker, object, ret, args
199
131
  case marker
200
132
  when Contrast::Utils::ObjectShare::RETURN_KEY
@@ -217,61 +149,6 @@ module Contrast
217
149
  end
218
150
  end
219
151
 
220
- # This is our method that actually checks the taint on the object
221
- # our trigger_node targets for our Regexp based rules.
222
- #
223
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
224
- # the node to direct applying this trigger event
225
- # @param source [Object] the source of the Trigger Event
226
- # @param object [Object] the Object on which the method was invoked
227
- # @param ret [Object] the Return of the invoked method
228
- # @param args [Array<Object>] the Arguments with which the method
229
- # was invoked
230
- def apply_regexp_rule trigger_node, source, object, ret, *args
231
- return unless source.is_a?(String)
232
- return if trigger_node.good_value && source.match?(trigger_node.good_value)
233
- return if trigger_node.bad_value && source !~ trigger_node.bad_value
234
-
235
- build_finding(trigger_node, source, object, ret, *args)
236
- end
237
-
238
- # This is our method that actually checks the taint on the object
239
- # our trigger_node targets for our Dataflow based rules.
240
- #
241
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode]
242
- # the node to direct applying this trigger event
243
- # @param source [Object] the source of the Trigger Event
244
- # @param object [Object] the Object on which the method was invoked
245
- # @param ret [Object] the Return of the invoked method
246
- # @param args [Array<Object>] the Arguments with which the method
247
- # was invoked
248
- def apply_dataflow_rule trigger_node, source, object, ret, *args
249
- return unless source
250
-
251
- if Contrast::Agent::Assess::Tracker.trackable?(source)
252
- return unless Contrast::Agent::Assess::Tracker.tracked?(source)
253
- return unless trigger_node.violated?(source)
254
-
255
- build_finding(trigger_node, source, object, ret, *args)
256
- elsif Contrast::Utils::DuckUtils.iterable_hash?(source)
257
- source.each_pair do |key, value|
258
- apply_dataflow_rule(trigger_node, key, object, ret, *args)
259
- apply_dataflow_rule(trigger_node, value, object, ret, *args)
260
- end
261
- elsif Contrast::Utils::DuckUtils.iterable_enumerable?(source)
262
- source.each do |value|
263
- apply_dataflow_rule(trigger_node, value, object, ret, *args)
264
- end
265
- else
266
- logger.debug('Trigger source is untrackable. Unable to inspect.',
267
- node_id: trigger_node.id,
268
- source_id: source.__id__,
269
- source_type: source.cs__class.to_s,
270
- frozen: source.cs__frozen?)
271
- logger.trace(source.to_s[0..99])
272
- end
273
- end
274
-
275
152
  def append_events finding, trigger_node, source, object, ret, args
276
153
  append_from_source(finding, source)
277
154
  finding.events << Contrast::Agent::Assess::Events::EventFactory.build(trigger_node, source, object, ret,
@@ -287,8 +164,7 @@ module Contrast
287
164
 
288
165
  build_events finding, properties.event if properties.event
289
166
 
290
- # Google::Protobuf::Map doesn't support merge!, so we have to do this
291
- # long form
167
+ # Google::Protobuf::Map doesn't support merge!, so we have to do this long form
292
168
  source_props = properties.properties
293
169
  return unless source_props
294
170
 
@@ -304,8 +180,8 @@ module Contrast
304
180
  event.parent_events&.each do |parent_event|
305
181
  build_events(finding, parent_event)
306
182
  end
307
- # events could technically be nil, but we would have failed
308
- # the rule check before getting here. not worth the nil check
183
+ # events could technically be nil, but we would have failed the rule check before getting here. not
184
+ # worth the nil check
309
185
  finding.events << event.to_dtm_event
310
186
  end
311
187
 
@@ -324,21 +200,18 @@ module Contrast
324
200
  finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
325
201
  end
326
202
 
327
- # Because our APIs are not versioned, TeamServer relies on a field,
328
- # version, to tell it what, if any, validation it can preform on
329
- # the findings we send it. Examine the finding and determine the
330
- # level to which it conforms.
203
+ # Because our APIs are not versioned, TeamServer relies on a field, version, to tell it what, if any,
204
+ # validation it can preform on the findings we send it. Examine the finding and determine the level to
205
+ # which it conforms.
331
206
  #
332
207
  # @param finding [Contrast::Api::Dtm::Finding]
333
208
  # @return [int] the version of compliance
334
209
  def determine_compliance_version finding
335
210
  return MINIMUM_FINDING_VERSION unless finding
336
- # as routes are the only variable between findings, in the case
337
- # where we couldn't determine one, any finding with a route is at
338
- # maximum compliance
211
+ # as routes are the only variable between findings, in the case where we couldn't determine one, any
212
+ # finding with a route is at maximum compliance
339
213
  return CURRENT_FINDING_VERSION if finding.routes.any?
340
- # any finding without events is not of a dataflow type and
341
- # therefore at maximum compliance
214
+ # any finding without events is not of a dataflow type and therefore at maximum compliance
342
215
  return CURRENT_FINDING_VERSION unless finding.events.any?
343
216
 
344
217
  MINIMUM_FINDING_VERSION
@@ -14,7 +14,7 @@ module Contrast
14
14
  # specifically for those methods which result in the trigger of a
15
15
  # vulnerability (indicate points in the application where uncontrolled
16
16
  # user input can do damage).
17
- class TriggerNode < PolicyNode
17
+ class TriggerNode < PolicyNode # rubocop:disable Metrics/ClassLength
18
18
  JSON_BAD_VALUE = 'bad_value'
19
19
  JSON_GOOD_VALUE = 'good_value'
20
20
  JSON_DISALLOWED_TAGS = 'disallowed_tags'
@@ -104,8 +104,7 @@ module Contrast
104
104
 
105
105
  properties = Contrast::Agent::Assess::Tracker.properties(source)
106
106
  # find the ranges that violate the rule (untrusted, etc)
107
- vulnerable_ranges = ranges_with_all_tags(Contrast::Utils::StringUtils.ret_length(source), properties,
108
- required_tags)
107
+ vulnerable_ranges = ranges_with_all_tags(properties, required_tags)
109
108
  # if there aren't any vulnerable ranges, nope out
110
109
  return false if vulnerable_ranges.empty?
111
110
 
@@ -170,49 +169,56 @@ module Contrast
170
169
 
171
170
  tags.each do |tag|
172
171
  raise(ArgumentError, "Rule #{ rule_id } had an invalid tag. #{ tag } is not a known value.") unless
173
- Contrast::Api::Decorators::TraceTaintRangeTags::VALID_TAGS.include?(tag) ||
174
- Contrast::Api::Decorators::TraceTaintRangeTags::VALID_SOURCE_TAGS.include?(tag)
172
+ Contrast::Api::Decorators::TraceTaintRangeTags::VALID_TAGS.include?(tag) ||
173
+ Contrast::Api::Decorators::TraceTaintRangeTags::VALID_SOURCE_TAGS.include?(tag)
175
174
  end
176
175
  end
177
176
 
178
177
  # Find the ranges that satisfy all of the given tags.
179
178
  #
180
- # @param length [Integer] the length of the object which may have the
181
- # given tags -- used as the maximum index to search for all of the
182
- # tags.
183
179
  # @param properties [Contrast::Agent::Assess::Properties] the
184
180
  # properties to check for the tags
185
181
  # @param required_tags [Set<String>] the list of tags on which to match
186
182
  # @return [Array<Contrast::Agent::Assess::Tag>] the ranges satisfied
187
183
  # by the given conditions
188
- def ranges_with_all_tags length, properties, required_tags
184
+ def ranges_with_all_tags properties, required_tags
189
185
  return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless matches_tags?(properties, required_tags)
190
186
 
191
- ranges = []
192
187
  chunking = false
188
+ ranges = []
189
+ # find the start and end range of required tags:
190
+ search_ranges = find_required_ranges properties, required_tags
191
+ start_range = search_ranges.first
192
+ end_range = search_ranges.last + 1
193
+
193
194
  # find all the indicies on the source that have all the given tags
194
- (0..length).each do |idx|
195
- tags_at = properties.tags_at(idx).to_a
195
+ while start_range < end_range
196
+
197
+ tags_at = properties.tags_at(start_range).to_a
196
198
  # only those that have all the required tags in the tags_at
197
199
  # satisfy the requirement
198
200
  satisfied = tags_at.any? && required_tags.all? { |tag| tags_at.any? { |found| found.label == tag } }
199
201
  # if this range matches all the required tags and we're already
200
202
  # chunking, meaning the previous range matched, do nothing
201
- next if satisfied && chunking
203
+ if satisfied && chunking
204
+ start_range += 1
205
+ next
206
+ end
202
207
 
203
208
  # if we are satisfied and we were not chunking, this represents
204
209
  # the start of the next range, so create a new entry.
205
210
  if satisfied
206
- ranges << Contrast::Agent::Assess::Tag.new('required', 0, idx)
211
+ ranges << Contrast::Agent::Assess::Tag.new('required', 0, start_range)
207
212
  chunking = true
208
- # if we are chunking and not satisfied, this represents the end
209
- # of the range, meaning the last index is what satisfied the
210
- # range. Because the range is exclusive end, we can just use this
211
- # index.
213
+ # if we are chunking and not satisfied, this represents the end
214
+ # of the range, meaning the last index is what satisfied the
215
+ # range. Because the range is exclusive end, we can just use this
216
+ # index.
212
217
  elsif chunking
213
- ranges[-1]&.update_end(idx)
218
+ ranges[-1]&.update_end(start_range)
214
219
  chunking = false
215
220
  end
221
+ start_range += 1
216
222
  end
217
223
  ranges
218
224
  end
@@ -265,6 +271,33 @@ module Contrast
265
271
 
266
272
  true
267
273
  end
274
+
275
+ # Range finder helper for #ranges_with_all_tags
276
+ #
277
+ # @param properties [Contrast::Agent::Assess::Properties] the properties to check for the tags
278
+ # @param required_tags [Set<String>] the list of tags on which to match
279
+ # @return [Array] of required tags ranges to search
280
+ def find_required_ranges properties, required_tags
281
+ start_range = 0
282
+ end_range = 0
283
+ required_tags_arr = required_tags.to_a
284
+ idx = 0
285
+
286
+ while idx < required_tags_arr.length
287
+ # find the start and end range of required tags:
288
+ start_temp = properties.fetch_tag(required_tags_arr[idx])[0].start_idx
289
+ end_temp = properties.fetch_tag(required_tags_arr[idx])[0].end_idx
290
+ # first iteration only
291
+ start_range = start_temp if idx.zero?
292
+ end_range = end_temp if idx.zero?
293
+
294
+ # find the tag with smallest ranges
295
+ start_range = start_temp if start_range < start_temp
296
+ end_range = end_temp if end_range > end_temp
297
+ idx += 1
298
+ end
299
+ [start_range, end_range]
300
+ end
268
301
  end
269
302
  end
270
303
  end
@@ -50,7 +50,8 @@ module Contrast
50
50
  return unless (current_request = Contrast::Agent::REQUEST_TRACKER.current)
51
51
 
52
52
  if current_request.observed_route.sources.any? do |source|
53
- source.type == event.forced_source_type && source.name == event.forced_source_name
53
+ source.type == event.forced_source_type &&
54
+ source.name == event.forced_source_name # rubocop:disable Security/Module/Name
54
55
  end
55
56
 
56
57
  return
@@ -5,6 +5,7 @@ require 'contrast/agent/assess/tag'
5
5
  require 'contrast/utils/object_share'
6
6
  require 'contrast/utils/string_utils'
7
7
  require 'contrast/utils/tag_util'
8
+ require 'contrast/utils/assess/property/tagged_utils'
8
9
 
9
10
  module Contrast
10
11
  module Agent
@@ -13,6 +14,7 @@ module Contrast
13
14
  # This module serves to hold the functionality required for the
14
15
  # management of our dataflow tags.
15
16
  module Tagged
17
+ include Contrast::Utils::Assess::TaggedUtils
16
18
  # Is any tag present?
17
19
  # Creating Tags is expensive and we check for Tags all the time on
18
20
  # untracked things. ALWAYS!!! call this method before checking if an
@@ -45,125 +47,6 @@ module Contrast
45
47
  false
46
48
  end
47
49
 
48
- # Find all of the ranges that span a given index. This is used
49
- # in propagation when we need to shift tags about. For instance, in
50
- # the append method when we need to see if any tag at the end needs
51
- # to be expanded out to the size of the new String.
52
- #
53
- # Note: Tags do not know their key, so this is only the range covered
54
- #
55
- # @param idx [Integer] the index to check for tags
56
- # @return [Array<Contrast::Agent::Assess::Tag>] a set of all the Tags
57
- # covering the given index. This is only the ranges, not the names.
58
- def tags_at idx
59
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
60
-
61
- at = []
62
- tags.each_value do |tag_array|
63
- tag_array.each do |tag|
64
- if tag.covers?(idx)
65
- at << tag
66
- elsif tag.above?(idx)
67
- break
68
- end
69
- end
70
- end
71
- at
72
- end
73
-
74
- # given a range, select all tags in that range the selected tags are
75
- # shifted such that the start index of the new tag (0) aligns with
76
- # the given start index in the range
77
- #
78
- # current tags: 5-15
79
- # range : 5-10
80
- # result : 0-05
81
- #
82
- # Note that we disable Lint/DuplicateBranch in this branch in order
83
- # list out all tag range cases in the proper order to make this
84
- # easier to understand
85
- #
86
- # @param range [Range] the span to check, inclusive to exclusive
87
- # @return [Hash{String => Contrast::Agent::Assess::Tag}] the hash of
88
- # key to tags
89
- def tags_at_range range
90
- return Contrast::Utils::ObjectShare::EMPTY_HASH unless tracked?
91
-
92
- at = Hash.new { |h, k| h[k] = [] }
93
- tags.each_pair do |key, value|
94
- add = nil
95
- value.each do |tag|
96
- within_range = resize_to_range(tag, range)
97
- if within_range
98
- add ||= []
99
- add << within_range
100
- end
101
- end
102
- next unless add&.any?
103
-
104
- at[key] = add
105
- end
106
- at
107
- end
108
-
109
- # Given a tag name and range object, add a new tag to this
110
- # collection. If the given range touches an existing tag,
111
- # we'll combine the two, adjusting the existing one and
112
- # dropping this new one.
113
- #
114
- # @param label [String] the name of the tag
115
- # @param range [Range] the Range that the tag covers, inclusive to
116
- # exclusive
117
- def add_tag label, range
118
- length = range.end - range.begin
119
- tag = Contrast::Agent::Assess::Tag.new(label, length, range.begin)
120
- existing = fetch_tag(label)
121
- tags[label] = Contrast::Utils::TagUtil.ordered_merge(existing, tag)
122
- end
123
-
124
- def set_tags label, tag_ranges
125
- tags[label] = tag_ranges
126
- end
127
-
128
- # Remove all tags with a given label
129
- def delete_tags label
130
- tags.delete(label) if tracked?
131
- end
132
-
133
- # Reset the tag hash
134
- def clear_tags
135
- tags.clear if tracked?
136
- end
137
-
138
- # Returns a list of all current tag labels, most likely to be used for
139
- # a splat operation
140
- def tag_keys
141
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
142
-
143
- tags.keys
144
- end
145
-
146
- # Calls merge to combine touching or overlapping tags
147
- # Deletes empty tags
148
- def cleanup_tags
149
- return unless tracked?
150
-
151
- Contrast::Utils::TagUtil.merge_tags(tags)
152
- tags.delete_if { |_, value| value.empty? }
153
- end
154
-
155
- # We'll use this as a helper method to retrieve tags from the hash.
156
- # Because the hash auto-populates an empty array when we try to
157
- # access a tag in it, we cannot use the [] method without side
158
- # effect. To get around this, we'll use a fetch work around.
159
- #
160
- # @param label [Symbol] the label to look up
161
- # @return [Array<Contrast::Agent::Assess::Tag>] all the tags with
162
- # that label
163
- def fetch_tag label
164
- tags.fetch(label, nil) if tracked?
165
- end
166
-
167
50
  # Remove all tags within the given ranges.
168
51
  # This does not delete an entire tag if part of that tag is
169
52
  # outside this range, meaning we may reduce sizes of tags
@@ -218,19 +101,6 @@ module Contrast
218
101
  end
219
102
  end
220
103
 
221
- # Because of the auto-fill thing, we should not allow direct access to
222
- # the tags hash. Instead, the methods above should be used to do
223
- # operations like add, delete, and fetch.
224
- #
225
- # CONTRAST-22914
226
- # please do NOT expose this w/ an attr_reader / accessor. there are
227
- # helper methods in this class that safely access the hash. the tags
228
- # method is private to avoid the side effect of a direct lookup with
229
- # `[]` adding an empty array to the hash.
230
- def tags
231
- @_tags ||= Hash.new { |h, k| h[k] = [] }
232
- end
233
-
234
104
  # Remove the tag ranges covering the given range
235
105
  def remove_tags range
236
106
  return unless tracked?
@@ -334,6 +204,19 @@ module Contrast
334
204
 
335
205
  private
336
206
 
207
+ # Because of the auto-fill thing, we should not allow direct access to
208
+ # the tags hash. Instead, the methods above should be used to do
209
+ # operations like add, delete, and fetch.
210
+ #
211
+ # CONTRAST-22914
212
+ # please do NOT expose this w/ an attr_reader / accessor. there are
213
+ # helper methods in this class that safely access the hash. the tags
214
+ # method is private to avoid the side effect of a direct lookup with
215
+ # `[]` adding an empty array to the hash.
216
+ def tags
217
+ @_tags ||= Hash.new { |h, k| h[k] = [] }
218
+ end
219
+
337
220
  # Given a tag, compare it to a given range and, if any part of that tag is within the range, return a new tag
338
221
  # covering the union of the original tag and the range. This new tag will start at the
339
222
  # max(tag.start, range.start) and end at min(tag.end, range.end)
@@ -20,7 +20,6 @@ module Contrast
20
20
  module HardcodedValueRule
21
21
  include Contrast::Components::Logger::InstanceMethods
22
22
 
23
-
24
23
  def disabled?
25
24
  !::Contrast::ASSESS.enabled? || ::Contrast::ASSESS.rule_disabled?(rule_id)
26
25
  end
@@ -35,6 +35,12 @@ module Contrast
35
35
  end
36
36
  end
37
37
 
38
+ def validate
39
+ return if class_name
40
+
41
+ raise(ArgumentError, "#{ node_class } #{ id } did not have a proper class name. Unable to create.")
42
+ end
43
+
38
44
  def module_names
39
45
  @_module_names ||= Set.new(deadzones.map(&:class_name))
40
46
  end
@@ -8,7 +8,7 @@ module Contrast
8
8
  # A Reaction from TeamServer which indicates the Agent should be disabled,
9
9
  # typically because some configuration setting did not satisfy requirements
10
10
  # set by the Organization's Administrator
11
- class DisableReaction
11
+ module DisableReaction
12
12
  extend Contrast::Components::Logger::InstanceMethods
13
13
 
14
14
  def self.run _reaction, level