contrast-agent 4.1.0 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) 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 +5 -1
  6. data/lib/contrast/agent/assess.rb +0 -9
  7. data/lib/contrast/agent/assess/contrast_event.rb +49 -132
  8. data/lib/contrast/agent/assess/contrast_object.rb +54 -0
  9. data/lib/contrast/agent/assess/events/source_event.rb +4 -9
  10. data/lib/contrast/agent/assess/finalizers/hash.rb +7 -0
  11. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +17 -3
  12. data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
  13. data/lib/contrast/agent/assess/policy/policy_node.rb +31 -59
  14. data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
  15. data/lib/contrast/agent/assess/policy/propagation_method.rb +41 -32
  16. data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
  17. data/lib/contrast/agent/assess/policy/propagator/append.rb +29 -15
  18. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
  19. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  20. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +21 -18
  21. data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -2
  22. data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
  23. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -2
  24. data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
  25. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
  26. data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
  27. data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
  28. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
  29. data/lib/contrast/agent/assess/policy/propagator/select.rb +3 -4
  30. data/lib/contrast/agent/assess/policy/propagator/splat.rb +25 -17
  31. data/lib/contrast/agent/assess/policy/propagator/split.rb +83 -120
  32. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +41 -25
  33. data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
  34. data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
  35. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +5 -8
  36. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  37. data/lib/contrast/agent/assess/policy/trigger_method.rb +13 -8
  38. data/lib/contrast/agent/assess/policy/trigger_node.rb +28 -7
  39. data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +59 -0
  40. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +2 -3
  41. data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +6 -4
  42. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +2 -4
  43. data/lib/contrast/agent/assess/properties.rb +0 -2
  44. data/lib/contrast/agent/assess/property/tagged.rb +56 -32
  45. data/lib/contrast/agent/assess/tracker.rb +16 -18
  46. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
  47. data/lib/contrast/agent/middleware.rb +134 -55
  48. data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
  49. data/lib/contrast/agent/patching/policy/patch.rb +6 -0
  50. data/lib/contrast/agent/patching/policy/patch_status.rb +1 -1
  51. data/lib/contrast/agent/patching/policy/patcher.rb +51 -44
  52. data/lib/contrast/agent/patching/policy/trigger_node.rb +5 -2
  53. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
  54. data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
  55. data/lib/contrast/agent/protect/rule/base.rb +63 -14
  56. data/lib/contrast/agent/protect/rule/cmd_injection.rb +12 -28
  57. data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
  58. data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
  59. data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
  60. data/lib/contrast/agent/protect/rule/sqli.rb +20 -14
  61. data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
  62. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
  63. data/lib/contrast/agent/reaction_processor.rb +1 -1
  64. data/lib/contrast/agent/request_context.rb +12 -0
  65. data/lib/contrast/agent/response.rb +5 -5
  66. data/lib/contrast/agent/rewriter.rb +3 -3
  67. data/lib/contrast/agent/scope.rb +81 -55
  68. data/lib/contrast/agent/static_analysis.rb +13 -7
  69. data/lib/contrast/agent/thread.rb +1 -1
  70. data/lib/contrast/agent/thread_watcher.rb +20 -5
  71. data/lib/contrast/agent/version.rb +1 -1
  72. data/lib/contrast/api/communication/messaging_queue.rb +18 -21
  73. data/lib/contrast/api/communication/response_processor.rb +8 -1
  74. data/lib/contrast/api/communication/socket_client.rb +22 -14
  75. data/lib/contrast/api/decorators.rb +2 -0
  76. data/lib/contrast/api/decorators/agent_startup.rb +58 -0
  77. data/lib/contrast/api/decorators/application_startup.rb +51 -0
  78. data/lib/contrast/api/decorators/library.rb +1 -0
  79. data/lib/contrast/api/decorators/library_usage_update.rb +1 -0
  80. data/lib/contrast/api/decorators/route_coverage.rb +15 -5
  81. data/lib/contrast/api/decorators/trace_event.rb +58 -42
  82. data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
  83. data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
  84. data/lib/contrast/api/decorators/user_input.rb +2 -1
  85. data/lib/contrast/common_agent_configuration.rb +2 -1
  86. data/lib/contrast/components/agent.rb +2 -0
  87. data/lib/contrast/components/app_context.rb +4 -22
  88. data/lib/contrast/components/assess.rb +36 -0
  89. data/lib/contrast/components/interface.rb +5 -3
  90. data/lib/contrast/components/sampling.rb +48 -6
  91. data/lib/contrast/components/scope.rb +72 -6
  92. data/lib/contrast/components/settings.rb +11 -7
  93. data/lib/contrast/config/assess_configuration.rb +2 -1
  94. data/lib/contrast/extension/assess/array.rb +2 -3
  95. data/lib/contrast/extension/assess/erb.rb +1 -3
  96. data/lib/contrast/extension/assess/exec_trigger.rb +1 -4
  97. data/lib/contrast/extension/assess/fiber.rb +2 -3
  98. data/lib/contrast/extension/assess/hash.rb +4 -2
  99. data/lib/contrast/extension/assess/kernel.rb +1 -2
  100. data/lib/contrast/extension/assess/marshal.rb +34 -26
  101. data/lib/contrast/extension/assess/regexp.rb +3 -8
  102. data/lib/contrast/extension/assess/string.rb +1 -2
  103. data/lib/contrast/framework/base_support.rb +51 -53
  104. data/lib/contrast/framework/manager.rb +16 -14
  105. data/lib/contrast/framework/rack/patch/session_cookie.rb +1 -1
  106. data/lib/contrast/framework/rack/support.rb +2 -1
  107. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +1 -1
  108. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +1 -1
  109. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +1 -1
  110. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +1 -1
  111. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +1 -1
  112. data/lib/contrast/framework/rails/support.rb +44 -44
  113. data/lib/contrast/framework/sinatra/support.rb +102 -42
  114. data/lib/contrast/logger/application.rb +0 -3
  115. data/lib/contrast/logger/log.rb +31 -15
  116. data/lib/contrast/utils/class_util.rb +3 -1
  117. data/lib/contrast/utils/duck_utils.rb +1 -1
  118. data/lib/contrast/utils/heap_dump_util.rb +103 -87
  119. data/lib/contrast/utils/invalid_configuration_util.rb +21 -12
  120. data/lib/contrast/utils/object_share.rb +3 -3
  121. data/lib/contrast/utils/preflight_util.rb +1 -1
  122. data/lib/contrast/utils/resource_loader.rb +1 -1
  123. data/lib/contrast/utils/sha256_builder.rb +2 -2
  124. data/lib/contrast/utils/string_utils.rb +1 -1
  125. data/lib/contrast/utils/tag_util.rb +9 -13
  126. data/resources/assess/policy.json +12 -18
  127. data/resources/deadzone/policy.json +156 -0
  128. data/resources/protect/policy.json +12 -0
  129. data/ruby-agent.gemspec +61 -19
  130. data/service_executables/VERSION +1 -1
  131. data/service_executables/linux/contrast-service +0 -0
  132. data/service_executables/mac/contrast-service +0 -0
  133. metadata +126 -113
  134. data/lib/contrast/agent/assess/rule.rb +0 -18
  135. data/lib/contrast/agent/assess/rule/base.rb +0 -52
  136. data/lib/contrast/agent/assess/rule/redos.rb +0 -67
  137. data/lib/contrast/framework/sinatra/patch/base.rb +0 -83
  138. data/lib/contrast/framework/sinatra/patch/support.rb +0 -27
  139. data/lib/contrast/utils/prevent_serialization.rb +0 -52
@@ -79,6 +79,10 @@ module Contrast
79
79
  # range : 5-10
80
80
  # result : 0-05
81
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
+ #
82
86
  # @param range [Range] the span to check, inclusive to exclusive
83
87
  # @return [Hash{String => Contrast::Agent::Assess::Tag}] the hash of
84
88
  # key to tags
@@ -87,28 +91,15 @@ module Contrast
87
91
 
88
92
  at = Hash.new { |h, k| h[k] = [] }
89
93
  tags.each_pair do |key, value|
90
- add = []
94
+ add = nil
91
95
  value.each do |tag|
92
- comparison = tag.compare_range(range.begin, range.end)
93
- # BELOW and ABOVE are applicable to this check and are removed.
94
- case comparison
95
- # part of the tag is being selected
96
- when Contrast::Agent::Assess::Tag::LOW_SPAN
97
- add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
98
- # the tag exists in the requested range, figure out the boundaries
99
- when Contrast::Agent::Assess::Tag::WITHIN
100
- start = tag.start_idx - range.begin
101
- finish = range.size - start
102
- add << Contrast::Agent::Assess::Tag.new(tag.label, finish, start)
103
- # the tag spans the requested range.
104
- when Contrast::Agent::Assess::Tag::WITHOUT
105
- add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
106
- # part of the tag is being selected
107
- when Contrast::Agent::Assess::Tag::HIGH_SPAN
108
- add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
96
+ within_range = resize_to_range(tag, range)
97
+ if within_range
98
+ add ||= []
99
+ add << within_range
109
100
  end
110
101
  end
111
- next if add.empty?
102
+ next unless add&.any?
112
103
 
113
104
  at[key] = add
114
105
  end
@@ -173,14 +164,6 @@ module Contrast
173
164
  tags.fetch(label, nil) if tracked?
174
165
  end
175
166
 
176
- # Convert the tags of this object into the TraceTaintRange required
177
- # to be sent to the service
178
- #
179
- # @return [Array<Contrast::Api::Dtm::TraceTaintRange>]
180
- def tags_to_dtm
181
- Contrast::Api::Dtm::TraceTaintRange.build_for_event(tags)
182
- end
183
-
184
167
  # Remove all tags within the given ranges.
185
168
  # This does not delete an entire tag if part of that tag is
186
169
  # outside this range, meaning we may reduce sizes of tags
@@ -301,9 +284,19 @@ module Contrast
301
284
  end
302
285
  end
303
286
 
304
- # Shift the tag ranges covering the given range
305
- # We assume this is for a insertion, meaning we
306
- # have to move tags to the right
287
+ # Shift the tag ranges covering the given range to account for new
288
+ # data being added. We assume this is for a insertion, meaning we
289
+ # have to move tags out of the range and to the right. For example,
290
+ # given current tags: 0-15
291
+ # when we insert a range: 5-10
292
+ # then the result is: 0-5, 10-20
293
+ #
294
+ # Note that we disable Lint/DuplicateBranch in this branch in order
295
+ # list out all tag range cases in the proper order to make this
296
+ # easier to understand
297
+ #
298
+ # @param range [Range] the range of new information that's been
299
+ # inserted
307
300
  def shift_tags_for_insertion range
308
301
  return unless tracked?
309
302
 
@@ -325,19 +318,50 @@ module Contrast
325
318
  when Contrast::Agent::Assess::Tag::WITHIN
326
319
  tag.shift(length)
327
320
  # the tag spans the range. leave the part below alone
328
- when Contrast::Agent::Assess::Tag::WITHOUT
321
+ when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
329
322
  new_tag = tag.clone
330
323
  new_tag.update_start(range.begin)
331
324
  new_tag.shift(length)
332
325
  add << new_tag
333
326
  tag.update_end(range.begin)
334
- when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE
327
+ when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE # rubocop:disable Lint/DuplicateBranch
335
328
  tag.shift(length)
336
329
  end
337
330
  end
338
331
  Contrast::Utils::TagUtil.ordered_merge(value, add)
339
332
  end
340
333
  end
334
+
335
+ private
336
+
337
+ # 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
+ # covering the union of the original tag and the range. This new tag will start at the
339
+ # max(tag.start, range.start) and end at min(tag.end, range.end)
340
+ #
341
+ # @param tag [Contrast::Agent::Assess::Tag] the Tag that may be in this range
342
+ # @param range [Range] the span to check, inclusive to exclusive
343
+ # @return [Contrast::Agent::Assess::Tag, nil] a new tag, truncated to only span within the given range or nil
344
+ # if no overlap exists
345
+ def resize_to_range tag, range
346
+ comparison = tag.compare_range(range.begin, range.end)
347
+ # BELOW and ABOVE are not applicable to this check and result in nil
348
+ case comparison
349
+ # part of the tag is being selected
350
+ when Contrast::Agent::Assess::Tag::LOW_SPAN
351
+ Contrast::Agent::Assess::Tag.new(tag.label, range.size)
352
+ # the tag exists in the requested range, figure out the boundaries
353
+ when Contrast::Agent::Assess::Tag::WITHIN
354
+ start = tag.start_idx - range.begin
355
+ finish = range.size - start
356
+ Contrast::Agent::Assess::Tag.new(tag.label, finish, start)
357
+ # the tag spans the requested range.
358
+ when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
359
+ Contrast::Agent::Assess::Tag.new(tag.label, range.size)
360
+ # part of the tag is being selected
361
+ when Contrast::Agent::Assess::Tag::HIGH_SPAN # rubocop:disable Lint/DuplicateBranch
362
+ Contrast::Agent::Assess::Tag.new(tag.label, range.size)
363
+ end
364
+ end
341
365
  end
342
366
  end
343
367
  end
@@ -14,7 +14,20 @@ module Contrast
14
14
  PROPERTIES_HASH = Contrast::Agent::Assess::Finalizers::Hash.new
15
15
 
16
16
  class << self
17
+ # Retrieve the properties of the given Object, iff they exist.
18
+ #
19
+ # @param source [Object, nil] the thing for which to look up properties.
20
+ # @return [Contrast::Agent::Assess::Properties, nil]
17
21
  def properties source
22
+ PROPERTIES_HASH[source]
23
+ end
24
+
25
+ # Retrieve the properties of the given Object, returning a new
26
+ # instance if one does not already exist and the source is trackable.
27
+ #
28
+ # @param source [Object] the thing for which to look up properties.
29
+ # @return [Contrast::Agent::Assess::Properties, nil]
30
+ def properties! source
18
31
  return unless trackable?(source)
19
32
 
20
33
  PROPERTIES_HASH[source] ||= Contrast::Agent::Assess::Properties.new
@@ -33,30 +46,15 @@ module Contrast
33
46
  end
34
47
 
35
48
  # Copy the properties from one object to the next, assuming the
36
- # target does not already have its own properties.
49
+ # target does not already have its own properties. This should only
50
+ # ever be called when building PreShift for those objects sources
51
+ # which are already tracked.
37
52
  #
38
53
  # @param source [Object] the instance from which to copy properties
39
54
  # @param target [Object] the instance to which to copy properties
40
55
  def copy source, target
41
56
  PROPERTIES_HASH[target] ||= properties(source).dup
42
57
  end
43
-
44
- # Duplicate the given object, returning the duplicate after copying
45
- # the properties of the original and storing them as the properties
46
- # of the duplicate.
47
- #
48
- # @param source [Object] the thing to duplicate
49
- # @return [Object] the duplicate of the original, or the original if
50
- # it does not respond to duplication
51
- def duplicate source
52
- return source unless source
53
-
54
- duplicate = source.dup
55
- PROPERTIES_HASH[duplicate] ||= PROPERTIES_HASH[source].dup
56
- duplicate
57
- rescue StandardError
58
- source
59
- end
60
58
  end
61
59
  end
62
60
  end
@@ -19,6 +19,13 @@ module Contrast
19
19
  def feature
20
20
  'Deadzone'
21
21
  end
22
+
23
+ # Deadzone means place the code inside of the contrast scope so that
24
+ # none of our code executes. As such, all DeadZone nodes have that as
25
+ # their method scope
26
+ def method_scope
27
+ :contrast
28
+ end
22
29
  end
23
30
  end
24
31
  end
@@ -46,27 +46,6 @@ module Contrast
46
46
  agent_startup_routine
47
47
  end
48
48
 
49
- # Startup the Agent as part of the initialization process:
50
- # - start the service sending thread, responsible for sending and
51
- # processing messages
52
- # - start the heartbeat thread, which triggers service startup
53
- # - start instrumenting libraries and do a 'catchup' patch for everything
54
- # we didn't see get loaded
55
- # - enable TracePoint, which handles all class loads and required
56
- # instrumentation going forward
57
- def agent_startup_routine
58
- logger.debug_with_time('middleware: starting service') do
59
- Contrast::Agent.thread_watcher.ensure_running?
60
- end
61
-
62
- logger.debug_with_time('middleware: instrument shared libraries and patch') do
63
- Contrast::Agent::Patching::Policy::Patcher.patch
64
- end
65
-
66
- AGENT.enable_tracepoint
67
- Contrast::Agent::AtExitHook.exit_hook
68
- end
69
-
70
49
  # This is where we're hooked into the middleware stack. If the agent is enabled, we're ready
71
50
  # to do some processing on a per request basis. If not, we just pass the request along to the
72
51
  # next middleware in the stack.
@@ -76,14 +55,11 @@ module Contrast
76
55
  # @return [Array,Rack::Response] the Response of this and subsequent Middlewares to be passed back
77
56
  # to the user up the Rack framework.
78
57
  def call env
79
- Contrast::Utils::HeapDumpUtil.run
58
+ return app.call(env) unless AGENT.enabled?
80
59
 
81
- if AGENT.enabled?
82
- Contrast::Agent::StaticAnalysis.catchup
83
- call_with_agent(env)
84
- else
85
- app.call(env)
86
- end
60
+ Contrast::Agent.heapdump_util.start_thread!
61
+ handle_first_request
62
+ call_with_agent(env)
87
63
  end
88
64
 
89
65
  private
@@ -91,6 +67,8 @@ module Contrast
91
67
  def setup_agent
92
68
  SETTINGS.reset_state
93
69
 
70
+ inform_deprecations
71
+
94
72
  if CONFIG.invalid?
95
73
  AGENT.disable!
96
74
  logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
@@ -101,13 +79,67 @@ module Contrast
101
79
  end
102
80
  end
103
81
 
82
+ # Startup the Agent as part of the initialization process:
83
+ # - start the service sending thread, responsible for sending and
84
+ # processing messages
85
+ # - start the heartbeat thread, which triggers service startup
86
+ # - start instrumenting libraries and do a 'catchup' patch for everything
87
+ # we didn't see get loaded
88
+ # - enable TracePoint, which handles all class loads and required
89
+ # instrumentation going forward
90
+ def agent_startup_routine
91
+ logger.debug_with_time('middleware: starting service') do
92
+ Contrast::Agent.thread_watcher.ensure_running?
93
+ end
94
+
95
+ logger.debug_with_time('middleware: instrument shared libraries and patch') do
96
+ Contrast::Agent::Patching::Policy::Patcher.patch
97
+ end
98
+
99
+ logger.debug_with_time('middleware: enabling tracepoint') do
100
+ AGENT.enable_tracepoint
101
+ end
102
+ Contrast::Agent::AtExitHook.exit_hook
103
+ end
104
+
105
+ # Some things have to wait until first request to happen, either because
106
+ # resolution is not complete or because the framework will preload
107
+ # classes, which confuses some of our instrumentation.
108
+ def handle_first_request
109
+ @_handle_first_request ||= begin
110
+ Contrast::Agent::StaticAnalysis.catchup
111
+ force_patching
112
+ true
113
+ end
114
+ end
115
+
116
+ # TODO: RUBY-1090 remove this method and those it calls.
117
+ #
118
+ # These modules are auto-loaded by Rails, meaning they are defined at
119
+ # startup, but that they don't actually exist. We account for this in
120
+ # most cases by using the ClassUtil.truly_defined? method, but it appears
121
+ # to fail for these Modules. In the short term, we can forcing a re-patch
122
+ # so that their dead zones apply.
123
+ def force_patching
124
+ force_patch(ActionDispatch::FileHandler) if defined?(ActionDispatch::FileHandler)
125
+ force_patch(ActionDispatch::Http::MimeNegotiation) if defined?(ActionDispatch::Http::MimeNegotiation)
126
+ force_patch(ActionView::Template) if defined?(ActionView::Template)
127
+ end
128
+
129
+ def force_patch mod
130
+ data = Contrast::Agent::ModuleData.new(mod)
131
+ Contrast::Agent::Patching::Policy::Patcher.send(:patch_into_module, data, true)
132
+ end
133
+
104
134
  # This is where we process each request we intercept as a middleware. We make the request context
105
135
  # available globally so that it can be accessed from anywhere. A RequestHandler object is made
106
136
  # for each request, which handles prefilter and postfilter operations.
137
+ # @param env [Hash] the various variables stored by this and other Middlewares to know the state
138
+ # and values of this Request
139
+ # @return [Array,Rack::Response] the Response of this and subsequent Middlewares to be passed back
140
+ # to the user up the Rack framework.
107
141
  def call_with_agent env
108
142
  Contrast::Agent.thread_watcher.ensure_running?
109
- return unless AGENT.enabled?
110
-
111
143
  framework_request = Contrast::Agent.framework_manager.retrieve_request(env)
112
144
  context = Contrast::Agent::RequestContext.new(framework_request)
113
145
  response = nil
@@ -117,28 +149,9 @@ module Contrast
117
149
  logger.request_start
118
150
  request_handler = Contrast::Agent::RequestHandler.new(context)
119
151
 
120
- # prefilter sequence
121
- with_contrast_scope do
122
- context.service_extract_request
123
- request_handler.ruleset.prefilter
124
- end
125
-
126
- response = application_code(env) # pass request down the Rack chain with original env
127
-
128
- # postfilter sequence
129
- with_contrast_scope do
130
- context.extract_after(response) # update context with final response information
131
-
132
- if Contrast::Agent.framework_manager.streaming?(env)
133
- context.reset_activity
134
- request_handler.stream_safe_postfilter
135
- else
136
- request_handler.ruleset.postfilter
137
- # return response stored in the context in case any postfilter rules updated the response data
138
- response = context.response&.rack_response || response
139
- request_handler.send_activity_messages
140
- end
141
- end
152
+ pre_call_with_agent(context, request_handler)
153
+ response = application_code(env)
154
+ post_call_with_agent(context, env, request_handler, response)
142
155
  ensure
143
156
  logger.request_end
144
157
  end
@@ -148,6 +161,47 @@ module Contrast
148
161
  handle_exception(e)
149
162
  end
150
163
 
164
+ # Handle the operations the Agent needs to accomplish prior to the Application code executing during this
165
+ # request.
166
+ #
167
+ # @param context [Contrast::Agent::RequestContext]
168
+ # @param request_handler [Contrast::Agent::RequestHandler]
169
+ def pre_call_with_agent context, request_handler
170
+ with_contrast_scope do
171
+ context.service_extract_request
172
+ request_handler.ruleset.prefilter
173
+ end
174
+ rescue StandardError => e
175
+ raise e if security_exception?(e)
176
+
177
+ logger.error('Unable to execute agent pre_call', e)
178
+ end
179
+
180
+ # Handle the operations the Agent needs to accomplish after the Application code executes during this request.
181
+ #
182
+ # @param context [Contrast::Agent::RequestContext]
183
+ # @param env [Hash] the various variables stored by this and other Middlewares to know the state and values of
184
+ # this Request
185
+ # @param request_handler [Contrast::Agent::RequestHandler]
186
+ # @param response [Array,Rack::Response]
187
+ def post_call_with_agent context, env, request_handler, response
188
+ with_contrast_scope do
189
+ context.extract_after(response) # update context with final response information
190
+
191
+ if Contrast::Agent.framework_manager.streaming?(env)
192
+ context.reset_activity
193
+ request_handler.stream_safe_postfilter
194
+ else
195
+ request_handler.ruleset.postfilter
196
+ request_handler.send_activity_messages
197
+ end
198
+ end
199
+ rescue StandardError => e
200
+ raise e if security_exception?(e)
201
+
202
+ logger.error('Unable to execute agent post_call', e)
203
+ end
204
+
151
205
  def application_code env
152
206
  logger.trace_with_time('application') do
153
207
  app.call(env)
@@ -161,9 +215,7 @@ module Contrast
161
215
  # We're only going to suppress SecurityExceptions indicating a blocked attack.
162
216
  # And, only if the config.agent.ruby.exceptions.capture? is set
163
217
  def handle_exception exception
164
- if exception.is_a?(Contrast::SecurityException) ||
165
- exception.message&.include?(SECURITY_EXCEPTION_MARKER)
166
-
218
+ if security_exception?(exception)
167
219
  exception_control = AGENT.exception_control
168
220
  raise exception unless exception_control[:enable]
169
221
 
@@ -173,6 +225,33 @@ module Contrast
173
225
  raise exception
174
226
  end
175
227
  end
228
+
229
+ # Is the given exception one raised by our Protect code?
230
+ #
231
+ # @param exception [Exception]
232
+ # @return [Boolean]
233
+ def security_exception? exception
234
+ exception.is_a?(Contrast::SecurityException) ||
235
+ exception.message&.include?(SECURITY_EXCEPTION_MARKER)
236
+ end
237
+
238
+ # As we deprecate support to prepare to remove dead code, we need to
239
+ # inform our users still relying on the now deprecated and soon to be
240
+ # removed functionality. This method handles doing that by leveraging the
241
+ # standard Kernel#warn approach
242
+ def inform_deprecations
243
+ # Ruby 2.5 is currently in security maintenance, meaning int is only
244
+ # receiving updates for security issues. It will move to eol on 31
245
+ # March 2021. As such, we can remove support for it in Q2. We'll begin
246
+ # the deprecation warnings now so that customers have time to reach out
247
+ # if they'll be impacted.
248
+ # TODO: RUBY-715 remove this part of the method, leaving it empty if
249
+ # there are no other deprecations, when we drop 2.5 support.
250
+ return unless RUBY_VERSION < '2.6.0'
251
+
252
+ Kernel.warn('[Contrast Security] [DEPRECATION] Support for Ruby 2.5 will be removed in April 2021. '\
253
+ 'Please contact Customer Support prior if you require continued support.')
254
+ end
176
255
  end
177
256
  end
178
257
  end