contrast-agent 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) 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/assess/contrast_event.rb +49 -130
  6. data/lib/contrast/agent/assess/contrast_object.rb +51 -0
  7. data/lib/contrast/agent/assess/events/source_event.rb +4 -9
  8. data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
  9. data/lib/contrast/agent/assess/policy/policy_node.rb +31 -59
  10. data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
  11. data/lib/contrast/agent/assess/policy/propagation_method.rb +13 -19
  12. data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
  13. data/lib/contrast/agent/assess/policy/propagator/append.rb +1 -2
  14. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
  15. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  16. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -3
  17. data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -2
  18. data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
  19. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -2
  20. data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
  21. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
  22. data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
  23. data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
  24. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
  25. data/lib/contrast/agent/assess/policy/propagator/select.rb +3 -4
  26. data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -4
  27. data/lib/contrast/agent/assess/policy/propagator/split.rb +73 -117
  28. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +11 -11
  29. data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
  30. data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
  31. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +5 -8
  32. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  33. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
  34. data/lib/contrast/agent/assess/property/tagged.rb +21 -15
  35. data/lib/contrast/agent/assess/rule/redos.rb +1 -1
  36. data/lib/contrast/agent/assess/tracker.rb +16 -18
  37. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
  38. data/lib/contrast/agent/middleware.rb +50 -1
  39. data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
  40. data/lib/contrast/agent/patching/policy/patch.rb +4 -4
  41. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
  42. data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
  43. data/lib/contrast/agent/protect/rule/base.rb +63 -14
  44. data/lib/contrast/agent/protect/rule/cmd_injection.rb +3 -3
  45. data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
  46. data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
  47. data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
  48. data/lib/contrast/agent/protect/rule/sqli.rb +3 -3
  49. data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
  50. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
  51. data/lib/contrast/agent/reaction_processor.rb +1 -1
  52. data/lib/contrast/agent/response.rb +5 -5
  53. data/lib/contrast/agent/rewriter.rb +3 -3
  54. data/lib/contrast/agent/scope.rb +33 -13
  55. data/lib/contrast/agent/static_analysis.rb +13 -7
  56. data/lib/contrast/agent/version.rb +1 -1
  57. data/lib/contrast/api/decorators/library.rb +1 -0
  58. data/lib/contrast/api/decorators/library_usage_update.rb +1 -0
  59. data/lib/contrast/api/decorators/trace_event.rb +19 -31
  60. data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
  61. data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
  62. data/lib/contrast/api/decorators/user_input.rb +2 -1
  63. data/lib/contrast/common_agent_configuration.rb +1 -1
  64. data/lib/contrast/components/assess.rb +36 -0
  65. data/lib/contrast/components/interface.rb +5 -3
  66. data/lib/contrast/components/scope.rb +23 -0
  67. data/lib/contrast/components/settings.rb +3 -3
  68. data/lib/contrast/config/assess_configuration.rb +2 -1
  69. data/lib/contrast/extension/assess/array.rb +1 -2
  70. data/lib/contrast/extension/assess/erb.rb +1 -3
  71. data/lib/contrast/extension/assess/exec_trigger.rb +1 -1
  72. data/lib/contrast/extension/assess/fiber.rb +2 -3
  73. data/lib/contrast/extension/assess/hash.rb +4 -2
  74. data/lib/contrast/extension/assess/kernel.rb +1 -2
  75. data/lib/contrast/extension/assess/marshal.rb +34 -26
  76. data/lib/contrast/extension/assess/regexp.rb +3 -8
  77. data/lib/contrast/extension/assess/string.rb +1 -2
  78. data/lib/contrast/framework/base_support.rb +51 -53
  79. data/lib/contrast/framework/manager.rb +3 -2
  80. data/lib/contrast/framework/rack/patch/session_cookie.rb +1 -1
  81. data/lib/contrast/framework/rack/support.rb +2 -1
  82. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +1 -1
  83. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +1 -1
  84. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +1 -1
  85. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +1 -1
  86. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +1 -1
  87. data/lib/contrast/framework/rails/support.rb +2 -1
  88. data/lib/contrast/framework/sinatra/support.rb +3 -2
  89. data/lib/contrast/logger/application.rb +0 -3
  90. data/lib/contrast/utils/duck_utils.rb +1 -1
  91. data/lib/contrast/utils/heap_dump_util.rb +1 -1
  92. data/lib/contrast/utils/object_share.rb +3 -3
  93. data/lib/contrast/utils/preflight_util.rb +1 -1
  94. data/lib/contrast/utils/prevent_serialization.rb +1 -1
  95. data/lib/contrast/utils/resource_loader.rb +1 -1
  96. data/lib/contrast/utils/sha256_builder.rb +2 -2
  97. data/lib/contrast/utils/string_utils.rb +1 -1
  98. data/lib/contrast/utils/tag_util.rb +9 -13
  99. data/resources/assess/policy.json +9 -9
  100. data/resources/deadzone/policy.json +156 -0
  101. data/resources/protect/policy.json +12 -0
  102. data/ruby-agent.gemspec +9 -6
  103. data/service_executables/VERSION +1 -1
  104. data/service_executables/linux/contrast-service +0 -0
  105. data/service_executables/mac/contrast-service +0 -0
  106. metadata +68 -25
@@ -58,21 +58,21 @@ module Contrast
58
58
 
59
59
  PROTECT_STATE_ATTRS.each do |attr|
60
60
  # TODO: RUBY-1052
61
- define_method(attr) do # rubocop:disable Kernel/DefineMethod
61
+ define_method(attr) do # rubocop:disable Performance/Kernel/DefineMethod
62
62
  protect_state[attr]
63
63
  end
64
64
  end
65
65
 
66
66
  ASSESS_STATE_ATTRS.each do |attr|
67
67
  # TODO: RUBY-1052
68
- define_method(attr) do # rubocop:disable Kernel/DefineMethod
68
+ define_method(attr) do # rubocop:disable Performance/Kernel/DefineMethod
69
69
  assess_state[attr]
70
70
  end
71
71
  end
72
72
 
73
73
  APPLICATION_STATE_ATTRS.each do |attr|
74
74
  # TODO: RUBY-1052
75
- define_method(attr) do # rubocop:disable Kernel/DefineMethod
75
+ define_method(attr) do # rubocop:disable Performance/Kernel/DefineMethod
76
76
  application_state[attr]
77
77
  end
78
78
  end
@@ -11,7 +11,8 @@ module Contrast
11
11
  enable: EMPTY_VALUE,
12
12
  enable_scan_response: Contrast::Config::DefaultValue.new('true'),
13
13
  sampling: Contrast::Config::SamplingConfiguration,
14
- rules: Contrast::Config::AssessRulesConfiguration
14
+ rules: Contrast::Config::AssessRulesConfiguration,
15
+ stacktraces: Contrast::Config::DefaultValue.new('ALL')
15
16
  }.cs__freeze
16
17
 
17
18
  def initialize hsh
@@ -39,8 +39,7 @@ module Contrast
39
39
  return ret if Contrast::Agent::Patching::Policy::Patch.skip_assess_analysis?
40
40
 
41
41
  with_contrast_scope do
42
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
43
- return ret unless properties
42
+ return ret unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
44
43
 
45
44
  shift = 0
46
45
  separator_length = separator.nil? ? 0 : separator.to_s.length
@@ -6,9 +6,7 @@ module ERBPropagator
6
6
  class << self
7
7
  def result_tagger patcher, preshift, ret, _block
8
8
  return unless preshift.args.length >= 1
9
-
10
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
11
- return unless properties
9
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
12
10
 
13
11
  used_binding = preshift.args[0]
14
12
  binding_variable_set = used_binding.local_variables
@@ -27,7 +27,7 @@ module Contrast
27
27
  source,
28
28
  Kernel,
29
29
  nil,
30
- [source])
30
+ source)
31
31
  end
32
32
 
33
33
  private
@@ -61,8 +61,7 @@ module Contrast
61
61
 
62
62
  with_contrast_scope do
63
63
  results.each do |result|
64
- result_properties = Contrast::Agent::Assess::Tracker.properties(result)
65
- next unless result_properties
64
+ next unless (result_properties = Contrast::Agent::Assess::Tracker.properties!(result))
66
65
 
67
66
  result_properties.splat_from(fiber, result)
68
67
  result_properties.build_event(
@@ -82,7 +81,7 @@ module Contrast
82
81
  return unless underlying.is_a?(String) && !underlying.empty?
83
82
 
84
83
  with_contrast_scope do
85
- properties = Contrast::Agent::Assess::Tracker.properties(fiber)
84
+ properties = Contrast::Agent::Assess::Tracker.properties!(fiber)
86
85
  return unless properties
87
86
 
88
87
  properties.splat_from(underlying, fiber)
@@ -17,11 +17,13 @@ module Contrast
17
17
  return object unless object.is_a?(String) && !object.cs__frozen?
18
18
  return object unless Contrast::Agent::Assess::Tracker.tracked?(object)
19
19
 
20
- ret = Contrast::Agent::Assess::Tracker.duplicate(object)
21
- ret.cs__freeze
20
+ # Copy the object, then freeze it, so that it looks the same
21
+ # externally, but will have our finalizer on it.
22
+ object.dup&.cs__freeze
22
23
  rescue StandardError
23
24
  # we'll rescue this error, but we can't log it here as that will
24
25
  # result in a seg fault
26
+ object
25
27
  end
26
28
 
27
29
  def instrument_hash_track
@@ -39,8 +39,7 @@ module Contrast
39
39
  # oh, and there's also %<name>type and %{name}... b/c of course there is
40
40
  # -HM
41
41
  def sprintf_tagger patcher, preshift, ret, _block
42
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
43
- return unless properties
42
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
44
43
 
45
44
  format_string = preshift.args[0]
46
45
  args = preshift.args[1]
@@ -6,42 +6,50 @@ require 'contrast/components/interface'
6
6
  module Contrast
7
7
  module Extension
8
8
  module Assess
9
- # This is our patch of the Array class required to handle propagation
9
+ # This is our patch of the Marshal class
10
10
  # Disclaimer: there may be a better way, but we're in a 'get it work' state.
11
11
  # Hopefully, we'll be in a 'get it right' state soon.
12
- # This module is used for our Marshal#load patches
12
+ # This module is used for our Marshal.load patches
13
13
  class MarshalPropagator
14
14
  include Contrast::Components::Interface
15
15
 
16
- access_component :logging
16
+ access_component :logging, :scope
17
17
 
18
18
  class << self
19
- def cs__load_trigger_check source, ret
20
- current_context = Contrast::Agent::REQUEST_TRACKER.current
21
- return unless current_context
19
+ def cs__load_protect arg
20
+ return if in_contrast_scope?
22
21
 
23
- # Since we know this is the source of the trigger, we can do some
24
- # optimization here and return when it is not tracked
25
- return unless Contrast::Agent::Assess::Tracker.tracked?(source)
22
+ with_contrast_scope do
23
+ Contrast::Agent::Protect::Policy::AppliesDeserializationRule.prepended_invoke(arg)
24
+ end
25
+ nil
26
+ end
27
+
28
+ def cs__load_assess source, ret
29
+ with_contrast_scope do
30
+ current_context = Contrast::Agent::REQUEST_TRACKER.current
31
+ return unless current_context
26
32
 
27
- args = [source]
28
- # source might not be all the args passed in, but it is the one we care
29
- # about. we could pass in all the args in the last param here if it
30
- # becomes an issue in rendering on TS
31
- Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(
32
- current_context,
33
- trigger_node('Marshal', :load),
34
- source,
35
- self,
36
- ret,
37
- args)
38
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
39
- properties.copy_from(source, ret)
33
+ args = [source]
34
+ # source might not be all the args passed in, but it is the one we care
35
+ # about. we could pass in all the args in the last param here if it
36
+ # becomes an issue in rendering on TS
37
+ Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(
38
+ current_context,
39
+ trigger_node('Marshal', :load),
40
+ source,
41
+ self,
42
+ ret,
43
+ *args)
44
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
40
45
 
41
- node = Contrast::Agent::Assess::Policy::Policy.instance.find_propagator_node('Marshal', :load, false)
42
- properties.build_event(node, ret, self, ret, args)
43
- rescue StandardError => e
44
- logger.error('Unable to determine if a trigger occurred in Marshal.load', e)
46
+ properties.copy_from(source, ret)
47
+
48
+ node = Contrast::Agent::Assess::Policy::Policy.instance.find_propagator_node('Marshal', :load, false)
49
+ properties.build_event(node, ret, self, ret, args)
50
+ rescue StandardError => e
51
+ logger.error('Unable to run Assess for Marshal.load', e)
52
+ end
45
53
  end
46
54
 
47
55
  def instrument_marshal_load
@@ -48,14 +48,9 @@ module Contrast
48
48
 
49
49
  target = info_hash[:back_ref]
50
50
  with_contrast_scope do
51
- result = info_hash[:result]
52
- return unless result
53
-
54
- string = info_hash[:string]
55
- return unless string
56
-
57
- properties = Contrast::Agent::Assess::Tracker.properties(target)
58
- return unless properties
51
+ return unless (result = info_hash[:result])
52
+ return unless (string = info_hash[:string])
53
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
59
54
 
60
55
  properties.splat_from(string, target)
61
56
  properties.build_event(
@@ -36,8 +36,7 @@ module Contrast
36
36
  return unless inputs.any? { |input| Contrast::Agent::Assess::Tracker.tracked?(input) }
37
37
 
38
38
  with_contrast_scope do
39
- properties = Contrast::Agent::Assess::Tracker.properties(result)
40
- return unless properties
39
+ return unless (properties = Contrast::Agent::Assess::Tracker.properties!(result))
41
40
 
42
41
  parent_events = []
43
42
  offset = 0
@@ -4,68 +4,66 @@
4
4
  module Contrast
5
5
  module Framework
6
6
  # The API for all subclasses to implement to correctly support a given framework
7
- class BaseSupport
8
- class << self
9
- # The top level module name used by the framework
10
- def detection_class
11
- raise NoMethodError('Subclasses of BaseSupport should implement this method')
12
- end
7
+ module BaseSupport
8
+ # The top level module name used by the framework
9
+ def detection_class
10
+ raise NoMethodError('Subclasses of BaseSupport should implement this method')
11
+ end
13
12
 
14
- def version
15
- raise NoMethodError('Subclasses of BaseSupport should implement this method')
16
- end
13
+ def version
14
+ raise NoMethodError('Subclasses of BaseSupport should implement this method')
15
+ end
17
16
 
18
- def application_name
19
- raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
20
- end
17
+ def application_name
18
+ raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
19
+ end
21
20
 
22
- def server_type
23
- raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
24
- end
21
+ def server_type
22
+ raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
23
+ end
25
24
 
26
- # Find all the predefined routes for this application and append them to the
27
- # provided inventory message
28
- # msg should be a Contrast::Api::Dtm::ApplicationUpdate or some other msg
29
- # that has a routes array consisting of Contrast::Api::Dtm::RouteCoverage
30
- def collect_routes
31
- raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
32
- end
25
+ # Find all the predefined routes for this application and append them to the
26
+ # provided inventory message
27
+ # msg should be a Contrast::Api::Dtm::ApplicationUpdate or some other msg
28
+ # that has a routes array consisting of Contrast::Api::Dtm::RouteCoverage
29
+ def collect_routes
30
+ raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
31
+ end
33
32
 
34
- def current_route
35
- raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
36
- end
33
+ def current_route
34
+ raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
35
+ end
37
36
 
38
- def retrieve_request _env
39
- raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
40
- end
37
+ def retrieve_request _env
38
+ raise NoMethodError, 'Subclasses of BaseSupport should implement this method'
39
+ end
41
40
 
42
- # Some Frameworks require specific patching for their classes to handle
43
- # functionality like configuration scanning. To accommodate this, this
44
- # method provides a place to register those patches for invocation on
45
- # Agent load.
46
- #
47
- # By default, and hopefully in all cases, we won't need these patches,
48
- # so we're allowing nil here rather than raising an exception.
49
- def before_load_patches!; end
41
+ # Some Frameworks require specific patching for their classes to handle
42
+ # functionality like configuration scanning. To accommodate this, this
43
+ # method provides a place to register those patches for invocation on
44
+ # Agent load.
45
+ #
46
+ # By default, and hopefully in all cases, we won't need these patches,
47
+ # so we're allowing nil here rather than raising an exception.
48
+ def before_load_patches!; end
50
49
 
51
- # Some Frameworks require specific patching for their classes to handle
52
- # functionality like routing. To accommodate this, this method provides
53
- # a place to register those patches for invocation in our
54
- # AfterLoadPatcher flow.
55
- #
56
- # By default, and hopefully in all cases, we won't need these patches,
57
- # so we're allowing nil here rather than raising an exception.
58
- #
59
- # @return [Set<Contrast::Agent::Patching::Policy::AfterLoadPatch>,nil]
60
- # those patches required for a Framework which can only be installed
61
- # once a specific module has been loaded.
62
- def after_load_patches; end
50
+ # Some Frameworks require specific patching for their classes to handle
51
+ # functionality like routing. To accommodate this, this method provides
52
+ # a place to register those patches for invocation in our
53
+ # AfterLoadPatcher flow.
54
+ #
55
+ # By default, and hopefully in all cases, we won't need these patches,
56
+ # so we're allowing nil here rather than raising an exception.
57
+ #
58
+ # @return [Set<Contrast::Agent::Patching::Policy::AfterLoadPatch>,nil]
59
+ # those patches required for a Framework which can only be installed
60
+ # once a specific module has been loaded.
61
+ def after_load_patches; end
63
62
 
64
- # We only support websockets in rails right now, so we won't detect streaming in
65
- # any other framework
66
- def streaming? _env
67
- false
68
- end
63
+ # We only support websockets in rails right now, so we won't detect streaming in
64
+ # any other framework
65
+ def streaming? _env
66
+ false
69
67
  end
70
68
  end
71
69
  end
@@ -128,9 +128,10 @@ module Contrast
128
128
  # @param method_name [Symbol] the method to call on each FrameworkSupport class
129
129
  # @return [Array]
130
130
  def data_for_all_frameworks method_name
131
- @_frameworks.flat_map do |framework|
131
+ data = @_frameworks.flat_map do |framework|
132
132
  framework.send(method_name)
133
- end.compact
133
+ end
134
+ data.compact
134
135
  end
135
136
 
136
137
  # This returns a single object from the first framework to successfully respond
@@ -26,7 +26,7 @@ module Contrast
26
26
  @_instrument ||= begin
27
27
  ::Rack::Session::Cookie.class_eval do
28
28
  alias_method :cs__patched_initialize, :initialize
29
- def initialize app, options = {}
29
+ def initialize app, options = {} # rubocop:disable Style/OptionHash
30
30
  Contrast::Framework::Rack::Patch::SessionCookie.analyze(options)
31
31
  cs__patched_initialize(app, options)
32
32
  end
@@ -9,7 +9,8 @@ module Contrast
9
9
  module Rack
10
10
  # Used when Rack is present to define framework specific behavior. For
11
11
  # now, the only part of this implemented is the Patch Support.
12
- class Support < BaseSupport
12
+ module Support
13
+ extend Contrast::Framework::BaseSupport
13
14
  extend Contrast::Framework::Rack::Patch::Support
14
15
  class << self
15
16
  def detection_class
@@ -7,7 +7,7 @@ module Contrast
7
7
  module Patch
8
8
  # This class acts as our patch into the ActionController::Live::Buffer
9
9
  # class, allowing us to track the close event on streamed responses.
10
- class ActionControllerLiveBuffer
10
+ module ActionControllerLiveBuffer
11
11
  class << self
12
12
  def send_messages
13
13
  return unless (context = Contrast::Agent::REQUEST_TRACKER.current)
@@ -10,7 +10,7 @@ module Contrast
10
10
  # for the runtime detection of insecure configurations on individual
11
11
  # ActionDispatch::Session::AbstractStore instances within the
12
12
  # application.
13
- class RailsApplicationConfiguration
13
+ module RailsApplicationConfiguration
14
14
  def self.instrument
15
15
  @_instrument ||= begin
16
16
  ::Rails::Application::Configuration.class_eval do
@@ -12,7 +12,7 @@ module Contrast
12
12
  # TODO: RUBY-714 remove w/ EOL of 2.5
13
13
  # @deprecated Changes to this class are discouraged as this approach is
14
14
  # being phased out with support for those language versions.
15
- class ActionControllerRailtiesHelperInherited
15
+ module ActionControllerRailtiesHelperInherited
16
16
  def self.instrument
17
17
  @_instrument ||= begin
18
18
  ::ActionController::Railties::Helpers.class_eval do
@@ -14,7 +14,7 @@ module Contrast
14
14
  # TODO: RUBY-714 remove w/ EOL of 2.5
15
15
  # @deprecated Changes to this class are discouraged as this approach is
16
16
  # being phased out with support for those language versions.
17
- class ActiveRecordAttributeMethodsRead
17
+ module ActiveRecordAttributeMethodsRead
18
18
  def self.instrument
19
19
  @_instrument ||= begin
20
20
  ::ActiveRecord::AttributeMethods::Read::ClassMethods.class_eval do
@@ -9,7 +9,7 @@ module Contrast
9
9
  # TODO: RUBY-714 remove w/ EOL of 2.5
10
10
  # @deprecated Changes to this class are discouraged as this approach is
11
11
  # being phased out with support for those language versions.
12
- class ActiveRecordTimeZoneInherited
12
+ module ActiveRecordTimeZoneInherited
13
13
  def self.instrument
14
14
  @_instrument ||= begin
15
15
  ::ActiveRecord::AttributeMethods::TimeZoneConversion::ClassMethods.class_eval do
@@ -10,7 +10,8 @@ module Contrast
10
10
  module Framework
11
11
  module Rails
12
12
  # Used when Rails is present to define framework specific behavior
13
- class Support < BaseSupport
13
+ class Support
14
+ extend Contrast::Framework::BaseSupport
14
15
  extend Contrast::Framework::Rails::Patch::Support
15
16
 
16
17
  class << self
@@ -8,7 +8,8 @@ module Contrast
8
8
  module Framework
9
9
  module Sinatra
10
10
  # Used when Sinatra is present to define framework specific behavior
11
- class Support < BaseSupport
11
+ class Support
12
+ extend Contrast::Framework::BaseSupport
12
13
  extend Contrast::Framework::Sinatra::Patch::Support
13
14
  class << self
14
15
  def detection_class
@@ -67,7 +68,7 @@ module Contrast
67
68
  private
68
69
 
69
70
  def app_class
70
- return nil unless defined?(::Sinatra) && defined?(::Sinatra::Base)
71
+ return unless defined?(::Sinatra) && defined?(::Sinatra::Base)
71
72
 
72
73
  @_app_class ||= begin
73
74
  sinatra_layers = ObjectSpace.each_object(::Sinatra::Base).to_a