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
@@ -12,7 +12,7 @@ module Contrast
12
12
  # methods which are too complex to fit into one of the standard
13
13
  # Contrast::Agent::Assess::Policy::Propagator molds without cluttering up the
14
14
  # String Class or exposing our methods there.
15
- class StringPropagator
15
+ class StringPropagator # rubocop:disable Style/StaticClass
16
16
  extend Contrast::Components::Logger::InstanceMethods
17
17
  extend Contrast::Components::Scope::InstanceMethods
18
18
 
@@ -52,31 +52,6 @@ module Contrast
52
52
  rescue StandardError => e
53
53
  logger.error('Unable to track interpolation', e)
54
54
  end
55
-
56
- def instrument_string
57
- @_instrument_string ||= begin
58
- require 'cs__assess_string/cs__assess_string'
59
- true
60
- end
61
- rescue StandardError, LoadError => e
62
- logger.error('Error loading hash track patch', e)
63
- false
64
- end
65
-
66
- def instrument_string_interpolation
67
- if @_instrument_string_interpolation.nil?
68
- @_instrument_string_interpolation = begin
69
- if ::Contrast::AGENT.patch_interpolation? && Funchook.available?
70
- require 'cs__assess_string_interpolation26/cs__assess_string_interpolation26'
71
- end
72
- true
73
- rescue StandardError, LoadError => e
74
- logger.error('Error loading interpolation patch', e)
75
- false
76
- end
77
- end
78
- @_instrument_string_interpolation
79
- end
80
55
  end
81
56
  end
82
57
  end
@@ -0,0 +1,61 @@
1
+ # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/components/logger'
5
+
6
+ module Contrast
7
+ module Extension
8
+ # Our top level Assess namespace in the Core Extension section of our
9
+ # code. These patches are those that are invoked directly from a patched
10
+ # Class.
11
+ #
12
+ module Assess
13
+ # This is the main instrument helper giving the method of requiring C patches
14
+ #
15
+ module InstrumentHelper
16
+ class << self
17
+ include Contrast::Components::Logger::InstanceMethods
18
+
19
+ # Unites the different require methods into one, using only
20
+ # the provided path for the C patches
21
+ # parameters
22
+ # @param path[String] Path to the required patch
23
+ #
24
+ def instrument path
25
+ var_name, extracted_name = gen_name(path)
26
+ return if instance_variable_get(var_name) == true
27
+
28
+ instance_variable_set(var_name, assign_value(path))
29
+ rescue StandardError, LoadError => e
30
+ logger.error("Error loading #{ extracted_name&.nil? ? '' : extracted_name } patch", e)
31
+ false
32
+ end
33
+
34
+ # Some of the requires have some extra conditions for them to require
35
+ # the C patches, so this method is helping us move the logic by making some
36
+ # conditions
37
+ def assign_value path
38
+ case path
39
+ when /fiber/
40
+ require path if Funchook.available?
41
+ when /interpolation26/
42
+ require path if ::Contrast::AGENT.patch_interpolation? && Funchook.available?
43
+ else
44
+ require path
45
+ end
46
+ true
47
+ end
48
+
49
+ # Generate the needed instance variable name and return the extracted name
50
+ def gen_name path
51
+ extracted_name = path.split(%r{[\s_/]})&.uniq&.delete_if do |s|
52
+ s.empty? || s == 'cs' || s == 'assess' || s == 'track'
53
+ end
54
+ extracted_name = (extracted_name&.length || 0) > 1 ? extracted_name&.join('_') : extracted_name&.pop
55
+ ["@_instrument_#{ extracted_name }_track", extracted_name]
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,174 @@
1
+ # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/framework/base_support'
5
+ require 'contrast/components/logger'
6
+
7
+ module Contrast
8
+ module Framework
9
+ module Grape
10
+ # Used when Grape is present to define framework specific behaviour
11
+ class Support
12
+ extend Contrast::Framework::BaseSupport
13
+ class << self
14
+ include Contrast::Components::Logger::InstanceMethods
15
+ def detection_class
16
+ 'Grape::API'
17
+ end
18
+
19
+ def version
20
+ ::Grape::VERSION
21
+ end
22
+
23
+ def application_name
24
+ app_class&.cs__name
25
+ end
26
+
27
+ def application_root
28
+ app_instance&.root
29
+ end
30
+
31
+ def server_type
32
+ 'grape'
33
+ end
34
+
35
+ # Given an object, determine if it is a Grape controller.
36
+ # Which could include cases of ::Grape::API subclass or actual class
37
+ #
38
+ # @param app [Object] suspected Grape app.
39
+ # @return [Boolean]
40
+ def grape_controller? app
41
+ # Grape is loaded?
42
+ return false unless grape_defined?
43
+
44
+ # App is a subclass of or actually is ::Grape::API.
45
+ return false unless app.cs__respond_to?(:<=) && app <= ::Grape::API
46
+
47
+ true
48
+ end
49
+
50
+ # Find all classes that subclass ::Grape::API, Gather their routes
51
+ #
52
+ # @return [Array<Contrast::Api::Dtm::RouteCoverage>, Array]- founded routes as Dtms
53
+ def collect_routes
54
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless grape_defined?
55
+
56
+ # Each Grape controller has endpoints and each endpoints has routes
57
+ # and that's why we need to go through each one and create separate RouteCoverage object
58
+ routes = []
59
+ grape_controllers.each do |c|
60
+ c&.endpoints&.each do |endpoint|
61
+ endpoint&.routes&.map do |r|
62
+ pattern = r.pattern.pattern
63
+ temp = Contrast::Api::Dtm::RouteCoverage.from_grape_controller(c, r.request_method, pattern, r.path)
64
+ routes << temp
65
+ end
66
+ end
67
+ end
68
+ routes
69
+ end
70
+
71
+ # Given the current request return a RouteCoverage dtm.
72
+ #
73
+ # @param request [Contrast::Agent::Request] a contrast tracked request.
74
+ # @param controller [::Grape::API] optionally use this controller instead of global ::Grape::API.
75
+ # @return [Contrast::Api::Dtm::RouteCoverage, nil] a Dtm describing the route
76
+ # matched to the request if a match was found.
77
+ def current_route request, controller = ::Grape::API, full_route = nil
78
+ return unless grape_controller?(controller)
79
+
80
+ method = request.env[::Rack::REQUEST_METHOD] # GET, PUT, POST, etc...
81
+
82
+ # Find final controller - actually we gotta match the route to the scanned application
83
+ # Initially Grape compiles all routes on startup, so we can use the url from the request
84
+ # and create the observed route
85
+ # Class < Grape::API, Grape::Router::Route
86
+ final_controller, route_pattern = _route_recurse(method, _cleaned_route(request), grape_controllers)
87
+ return unless final_controller
88
+
89
+ full_route ||= request.env[::Rack::PATH_INFO]
90
+
91
+ Contrast::Api::Dtm::RouteCoverage.from_grape_controller(final_controller, method, route_pattern, full_route)
92
+ end
93
+
94
+ # Search object space for grape controllers--any class that subclasses ::Grape::API.
95
+ #
96
+ # @return [Array<::Grape::API>] grape controllers
97
+ def grape_controllers
98
+ ObjectSpace.each_object(Class).select { |klass| klass < ::Grape::API }
99
+ end
100
+
101
+ # Grape Request inherits the same as the Sinatra, so we can easily call it as it's called in Sinatra
102
+ def retrieve_request env
103
+ ::Grape::Request.new(env)
104
+ end
105
+
106
+ private
107
+
108
+ # Determine if, at the time of our Framework Support determination, Grape has been defined.
109
+ #
110
+ # @return [Boolean]
111
+ def grape_defined?
112
+ @_grape_defined = !!(defined?(::Grape) && defined?(::Grape::API)) if @_grape_defined.nil?
113
+ @_grape_defined
114
+ end
115
+
116
+ # @param method [::Rack::REQUEST_METHOD] GET, POST, PUT, etc...
117
+ # @param route [String] the relative route passed from Rack.
118
+ # @param controllers [Array<::Grape::API>] All Grape controllers found
119
+ # @return [Array[::Grape::API]], nil] Either the controller that
120
+ # will handle the route along with the route pattern or nil if no match.
121
+ def _route_recurse method, route, controllers = grape_controllers
122
+ # return if there aren't any controllers
123
+ return unless controllers&.any?
124
+
125
+ # Here we can go through the all detected controllers
126
+ # and find the one that's routes include the current one
127
+ # Grape controller actually has endpoints and each endpoint
128
+ # has routes and that's why we need to do it that way
129
+ controller = controllers.pop
130
+ return _route_recurse method, route, controllers unless controller
131
+
132
+ contr_routes = controller.endpoints&.map(&:routes)&.flatten || []
133
+ route_pattern = contr_routes&.find do |r|
134
+ r.pattern.to_regexp.match(route) # ::Mustermann::Grape match
135
+ end
136
+ return controller, route_pattern unless route_pattern.nil?
137
+
138
+ _route_recurse method, route, controllers
139
+ end
140
+
141
+ # Get route and do some cleaning
142
+ #
143
+ # @param request [Contrast::Agent::Request] a contrast tracked request.
144
+ # @return [String] the extracted and cleaned relative route.
145
+ def _cleaned_route request
146
+ route = request.env[::Rack::PATH_INFO]
147
+ return '/' if route.empty?
148
+
149
+ route.end_with?('/') ? route[0..-2] : route
150
+ end
151
+
152
+ def app_class
153
+ return unless grape_defined?
154
+
155
+ app_instance.cs__class
156
+ end
157
+
158
+ # Search the object space for the controller handling this request which will be
159
+ # the class inheriting from ::Grape::API with @app=nil
160
+ #
161
+ # @return [::Grape::API] the current controller as routed by Rack.
162
+ def app_instance
163
+ return unless grape_defined?
164
+
165
+ @_app_instance ||= begin
166
+ grape_layers = ObjectSpace.each_object(::Grape::API).to_a
167
+ grape_layers.find { |layer| layer.app.nil? }
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -2,9 +2,11 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/components/logger'
5
+ require 'contrast/extension/module'
5
6
  require 'contrast/framework/platform_version'
6
7
  require 'contrast/framework/rack/support'
7
8
  require 'contrast/framework/rails/support'
9
+ require 'contrast/framework/grape/support'
8
10
  require 'contrast/framework/sinatra/support'
9
11
  require 'contrast/utils/class_util'
10
12
 
@@ -14,12 +16,12 @@ module Contrast
14
16
  class Manager
15
17
  include Contrast::Components::Logger::InstanceMethods
16
18
 
17
- # Order here does matter as the first framework listed will be the first one we pull information from
18
- # Rack will be a special case that may involve updating some logic to handle only applying Rack if Rails/Sinatra
19
- # do not exist
19
+ # Order here does matter as the first framework listed will be the first one we pull information from Rack will
20
+ # be a special case that may involve updating some logic to handle only applying Rack if Rails/Sinatra do not
21
+ # exist
20
22
  SUPPORTED_FRAMEWORKS = [
21
23
  Contrast::Framework::Rails::Support, Contrast::Framework::Sinatra::Support,
22
- Contrast::Framework::Rack::Support
24
+ Contrast::Framework::Grape::Support, Contrast::Framework::Rack::Support
23
25
  ].cs__freeze
24
26
 
25
27
  def initialize
@@ -32,9 +34,8 @@ module Contrast
32
34
  @_frameworks.compact!
33
35
  end
34
36
 
35
- # Patches that have to be applied as early as possible to catch calls
36
- # that happen prior to the first Request, typically those around
37
- # configuration.
37
+ # Patches that have to be applied as early as possible to catch calls that happen prior to the first Request,
38
+ # typically those around configuration.
38
39
  def before_load_patches!
39
40
  @_before_load_patches ||= begin
40
41
  SUPPORTED_FRAMEWORKS.each(&:before_load_patches!)
@@ -77,19 +78,28 @@ module Contrast
77
78
  found || ::Rack::Directory.new('').root
78
79
  end
79
80
 
80
- # If we have 0 or n > 1 frameworks, we need to use the default rack request
81
+ # Build a request from the provided env, based on the framework(s) we're currently supporting.
81
82
  #
82
- # @param env [Hash] the various variables stored by this and other Middlewares to know the state and values
83
- # of this particular Request
83
+ # @param env [Hash] the various variables stored by this and other Middlewares to know the state and values of
84
+ # this particular Request
84
85
  # @return [::Rack::Request] either a rack request or subclass thereof.
85
86
  def retrieve_request env
87
+ # If we're mounted on Rails, use Rails.
88
+ if @_frameworks.include?(Contrast::Framework::Rails::Support)
89
+ return Contrast::Framework::Rails::Support.retrieve_request(env)
90
+ end
91
+
92
+ # If we know the framework, use it.
86
93
  return @_frameworks[0].retrieve_request(env) if @_frameworks.length == 1
87
94
 
95
+ # Fall back on a regular Rack::Request
88
96
  ::Rack::Request.new(env)
97
+ rescue StandardError => e
98
+ logger.warn('Unable to retrieve_request', e)
89
99
  end
90
100
 
91
- # @param env [Hash] the various variables stored by this and other Middlewares to know the state
92
- # and values of this particular Request
101
+ # @param env [Hash] the various variables stored by this and other Middlewares to know the state and values of
102
+ # this particular Request
93
103
  # @return [Boolean] true if at least one framework is streaming the response; false if none are streaming
94
104
  def streaming? env
95
105
  result = false
@@ -100,8 +110,8 @@ module Contrast
100
110
  result
101
111
  end
102
112
 
103
- # Iterate through current frameworks and return the current request's route. This will be the first
104
- # non-nil result.
113
+ # Iterate through current frameworks and return the current request's route. This will be the first non-nil
114
+ # result.
105
115
  #
106
116
  # @param request [Contrast::Agent::Request] the current request.
107
117
  # @return [Contrast::Api::Dtm::RouteCoverage] the current route as a Dtm.
@@ -109,6 +119,37 @@ module Contrast
109
119
  @_frameworks.lazy.map { |framework_support| framework_support.current_route(request) }.reject(&:nil?).first
110
120
  end
111
121
 
122
+ # Sometimes the framework we want to instrument is loaded after our agent code. To catch that case, we'll detect
123
+ # if the loaded_module is the marker class for any of our supported frameworks. If it is, and we don't already
124
+ # have support enabled, we'll enable it now. We'll also need to catch up on any other startup actions that we've
125
+ # missed. Most likely, this is only necessary for those applications which have applications mounted on them.
126
+ #
127
+ # TODO: RUBY-1354
128
+ # TODO: RUBY-1356
129
+ #
130
+ # @param mod [Module] the module or class that was just loaded
131
+ def register_late_framework mod
132
+ return unless mod
133
+
134
+ module_name = mod.cs__name
135
+ # Otherwise, check if the provided module_name requires us to register a new support
136
+ SUPPORTED_FRAMEWORKS.each do |framework|
137
+ next if @_frameworks.include?(framework)
138
+ next unless module_name == framework.detection_class
139
+
140
+ @_frameworks << framework
141
+ # Report the registered routes of that framework now that we know we need to find them
142
+ app_update_msg = Contrast::Api::Dtm::ApplicationUpdate.build
143
+ Contrast::Agent.messaging_queue.send_event_eventually(app_update_msg)
144
+ logger.info('Framework detected after initialization. Enabling support.',
145
+ framework: framework.detection_class,
146
+ frameworks: @_frameworks)
147
+ break
148
+ end
149
+ rescue StandardError => e
150
+ logger.warn('Unable to register a late framework', e, module: mod.cs__name)
151
+ end
152
+
112
153
  private
113
154
 
114
155
  def enable_framework_support? klass
@@ -124,10 +165,7 @@ module Contrast
124
165
  # @param method_name [Symbol] the method to call on each FrameworkSupport class
125
166
  # @return [Array]
126
167
  def data_for_all_frameworks method_name
127
- data = @_frameworks.flat_map do |framework|
128
- framework.send(method_name)
129
- end
130
- data.compact
168
+ @_frameworks.flat_map { |framework| framework.send(method_name) }.compact
131
169
  end
132
170
 
133
171
  # This returns a single object from the first framework to successfully respond
@@ -14,7 +14,7 @@ module Contrast
14
14
  extend Contrast::Framework::Rack::Patch::Support
15
15
  class << self
16
16
  def detection_class
17
- 'don\'t let me be detected'
17
+ 'rack -- don\'t let me be detected'
18
18
  end
19
19
  end
20
20
  end
@@ -5,10 +5,14 @@ module Contrast
5
5
  module Framework
6
6
  module Rails
7
7
  module Patch
8
- # This class acts as our patch into the ActionController::Live::Buffer
9
- # class, allowing us to track the close event on streamed responses.
8
+ # This class acts as our patch into the ActionController::Live::Buffer class, allowing us to track the close
9
+ # event on streamed responses.
10
10
  module ActionControllerLiveBuffer
11
11
  class << self
12
+ # TODO: RUBY-1353
13
+ # TODO: RUBY-1355
14
+ # TODO: RUBY-1357
15
+ # TODO: RUBY-1357
12
16
  def send_messages
13
17
  return unless (context = Contrast::Agent::REQUEST_TRACKER.current)
14
18
 
@@ -20,10 +24,9 @@ module Contrast
20
24
  def instrument
21
25
  @_instrument ||= begin
22
26
  ::ActionController::Live::Buffer.class_eval do
23
- # normally pre->in->post filters are applied however, in a streamed response
24
- # we can run into a case where it's pre -> in -> post -> more infilters
25
- # in order to submit anything found during the infilters after the response has
26
- # been written we need to explicitly send them
27
+ # normally pre->in->post filters are applied however, in a streamed response we can run into a case
28
+ # where it's pre -> in -> post -> more infilters in order to submit anything found during the
29
+ # infilters after the response has been written we need to explicitly send them
27
30
  alias_method :cs__close, :close
28
31
  def close
29
32
  Contrast::Framework::Rails::Patch::ActionControllerLiveBuffer.send_messages
@@ -11,7 +11,6 @@ module Contrast
11
11
  module AssessConfiguration
12
12
  include Contrast::Components::Logger::InstanceMethods
13
13
 
14
-
15
14
  CS__SESSION_TIMEOUT_NAME = 'session-timeout'
16
15
  SAFE_SESSION_TIMEOUT = (30 * 60 * 1000)
17
16
  CS__SECURE_RULE_NAME = 'secure-flag-missing'
@@ -29,43 +29,48 @@ module Contrast
29
29
  Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
30
30
  'ActionController::Live::Buffer',
31
31
  'contrast/framework/rails/patch/action_controller_live_buffer',
32
- instrumenting_module: 'Contrast::Framework::Rails::Patch::ActionControllerLiveBuffer'),
32
+ instrumenting_module:
33
+ 'Contrast::Framework::Rails::Patch::ActionControllerLiveBuffer'),
33
34
  Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
34
35
  'Rails::Application::Configuration',
35
36
  'contrast/framework/rails/patch/rails_application_configuration',
36
37
  method_to_instrument: :session_store,
37
- instrumenting_module: 'Contrast::Framework::Rails::Patch::RailsApplicationConfiguration')
38
+ instrumenting_module:
39
+ 'Contrast::Framework::Rails::Patch::RailsApplicationConfiguration')
38
40
  ])
39
- if RUBY_VERSION < '2.6.0'
40
- patches.merge([
41
- # TODO: RUBY-714 remove w/ EOL of 2.5
42
- #
43
- # @deprecated Everything past here is used for Rewriting and can
44
- # be removed once we no longer support 2.5.
45
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
46
- 'ActionController::Railties::Helper::ClassMethods',
47
- 'contrast/framework/rails/rewrite/action_controller_railties_helper_inherited',
48
- method_to_instrument: :inherited,
49
- instrumenting_module:
50
- 'Contrast::Framework::Rails::Rewrite::ActionControllerRailtiesHelperInherited'),
51
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
52
- 'ActiveRecord::AttributeMethods::Read::ClassMethods',
53
- 'contrast/framework/rails/rewrite/active_record_attribute_methods_read',
54
- instrumenting_module:
55
- 'Contrast::Framework::Rails::Rewrite::ActiveRecordAttributeMethodsRead'),
56
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
57
- 'ActiveRecord::Scoping::Named::ClassMethods',
58
- 'contrast/framework/rails/rewrite/active_record_named',
59
- instrumenting_module: 'Contrast::Framework::Rails::Rewrite::ActiveRecordNamed'),
60
- Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
61
- 'ActiveRecord::AttributeMethods::TimeZoneConversion::ClassMethods',
62
- 'contrast/framework/rails/rewrite/active_record_time_zone_inherited',
63
- method_to_instrument: :inherited,
64
- instrumenting_module: 'Contrast::Framework::Rails::Rewrite::ActiveRecordTimeZoneInherited')
65
- ])
66
- end
41
+ patches.merge(special_after_load_patches) if RUBY_VERSION < '2.6.0'
67
42
  patches
68
43
  end
44
+
45
+ def special_after_load_patches
46
+ [
47
+ # TODO: RUBY-714 remove w/ EOL of 2.5
48
+ #
49
+ # @deprecated Everything past here is used for Rewriting and can
50
+ # be removed once we no longer support 2.5.
51
+ Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
52
+ 'ActionController::Railties::Helper::ClassMethods',
53
+ 'contrast/framework/rails/rewrite/action_controller_railties_helper_inherited',
54
+ method_to_instrument: :inherited,
55
+ instrumenting_module:
56
+ 'Contrast::Framework::Rails::Rewrite::ActionControllerRailtiesHelperInherited'),
57
+ Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
58
+ 'ActiveRecord::AttributeMethods::Read::ClassMethods',
59
+ 'contrast/framework/rails/rewrite/active_record_attribute_methods_read',
60
+ instrumenting_module:
61
+ 'Contrast::Framework::Rails::Rewrite::ActiveRecordAttributeMethodsRead'),
62
+ Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
63
+ 'ActiveRecord::Scoping::Named::ClassMethods',
64
+ 'contrast/framework/rails/rewrite/active_record_named',
65
+ instrumenting_module: 'Contrast::Framework::Rails::Rewrite::ActiveRecordNamed'),
66
+ Contrast::Agent::Patching::Policy::AfterLoadPatch.new(
67
+ 'ActiveRecord::AttributeMethods::TimeZoneConversion::ClassMethods',
68
+ 'contrast/framework/rails/rewrite/active_record_time_zone_inherited',
69
+ method_to_instrument: :inherited,
70
+ instrumenting_module:
71
+ 'Contrast::Framework::Rails::Rewrite::ActiveRecordTimeZoneInherited')
72
+ ]
73
+ end
69
74
  end
70
75
  end
71
76
  end
@@ -12,7 +12,7 @@ module Contrast
12
12
  include Contrast::Components::Logger::InstanceMethods
13
13
 
14
14
  initializer 'Contrast Ruby Agent Initializer' do |app|
15
- log_rails = defined?(Rails) && defined?(Rails.logger)
15
+ log_rails = defined?(Rails) && defined?(Rails.logger)
16
16
 
17
17
  Rails.logger.debug("In railtie ::#{ app.middleware.inspect }") if log_rails
18
18
 
@@ -18,6 +18,7 @@ module Contrast
18
18
  # being phased out with support for those language versions.
19
19
  class ActiveRecordNamed
20
20
  include Contrast::Components::Logger::InstanceMethods
21
+ extend Contrast::Components::Logger::InstanceMethods
21
22
 
22
23
  class << self
23
24
  def rewrite mod, method_name, body