contrast-agent 4.8.0 → 4.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -1
  3. data/.rspec +0 -1
  4. data/.rspec_parallel +6 -0
  5. data/.simplecov +1 -0
  6. data/ext/cs__contrast_patch/cs__contrast_patch.c +0 -1
  7. data/ext/cs__contrast_patch/cs__contrast_patch.h +0 -2
  8. data/lib/contrast/agent/assess/contrast_event.rb +1 -5
  9. data/lib/contrast/agent/assess/contrast_object.rb +0 -3
  10. data/lib/contrast/agent/assess/finalizers/hash.rb +2 -5
  11. data/lib/contrast/agent/assess/policy/patcher.rb +5 -4
  12. data/lib/contrast/agent/assess/policy/policy.rb +1 -1
  13. data/lib/contrast/agent/assess/policy/policy_scanner.rb +2 -6
  14. data/lib/contrast/agent/assess/policy/preshift.rb +16 -12
  15. data/lib/contrast/agent/assess/policy/propagation_method.rb +102 -59
  16. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -7
  17. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +31 -11
  18. data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -9
  19. data/lib/contrast/agent/assess/policy/propagator/split.rb +10 -6
  20. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +3 -3
  21. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +6 -7
  22. data/lib/contrast/agent/assess/policy/source_method.rb +18 -22
  23. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -4
  24. data/lib/contrast/agent/assess/policy/trigger_method.rb +62 -88
  25. data/lib/contrast/agent/assess/policy/trigger_node.rb +1 -1
  26. data/lib/contrast/agent/assess/property/evented.rb +2 -1
  27. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +3 -4
  28. data/lib/contrast/agent/at_exit_hook.rb +3 -3
  29. data/lib/contrast/agent/class_reopener.rb +6 -5
  30. data/lib/contrast/agent/disable_reaction.rb +4 -5
  31. data/lib/contrast/agent/exclusion_matcher.rb +2 -7
  32. data/lib/contrast/agent/inventory/database_config.rb +117 -0
  33. data/lib/contrast/agent/inventory/dependency_analysis.rb +2 -6
  34. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +9 -10
  35. data/lib/contrast/agent/inventory/policy/datastores.rb +5 -6
  36. data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
  37. data/lib/contrast/agent/middleware.rb +15 -13
  38. data/lib/contrast/agent/patching/policy/after_load_patch.rb +6 -3
  39. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +21 -16
  40. data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
  41. data/lib/contrast/agent/patching/policy/patch.rb +13 -8
  42. data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
  43. data/lib/contrast/agent/patching/policy/patcher.rb +14 -14
  44. data/lib/contrast/agent/patching/policy/policy.rb +2 -4
  45. data/lib/contrast/agent/patching/policy/policy_node.rb +2 -3
  46. data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
  47. data/lib/contrast/agent/protect/policy/policy.rb +1 -1
  48. data/lib/contrast/agent/protect/policy/rule_applicator.rb +3 -5
  49. data/lib/contrast/agent/protect/rule/base.rb +10 -10
  50. data/lib/contrast/agent/protect/rule/cmd_injection.rb +4 -5
  51. data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
  52. data/lib/contrast/agent/protect/rule/path_traversal.rb +1 -5
  53. data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
  54. data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
  55. data/lib/contrast/agent/reaction_processor.rb +3 -4
  56. data/lib/contrast/agent/request.rb +13 -7
  57. data/lib/contrast/agent/request_context.rb +36 -34
  58. data/lib/contrast/agent/request_handler.rb +5 -3
  59. data/lib/contrast/agent/response.rb +2 -3
  60. data/lib/contrast/agent/rewriter.rb +4 -3
  61. data/lib/contrast/agent/rule_set.rb +5 -4
  62. data/lib/contrast/agent/scope.rb +32 -20
  63. data/lib/contrast/agent/service_heartbeat.rb +2 -3
  64. data/lib/contrast/agent/static_analysis.rb +7 -6
  65. data/lib/contrast/agent/thread.rb +2 -4
  66. data/lib/contrast/agent/thread_watcher.rb +3 -4
  67. data/lib/contrast/agent/tracepoint_hook.rb +20 -7
  68. data/lib/contrast/agent/version.rb +1 -1
  69. data/lib/contrast/api/communication/messaging_queue.rb +16 -11
  70. data/lib/contrast/api/communication/response_processor.rb +11 -11
  71. data/lib/contrast/api/communication/service_lifecycle.rb +9 -5
  72. data/lib/contrast/api/communication/socket_client.rb +18 -14
  73. data/lib/contrast/api/communication/speedracer.rb +5 -6
  74. data/lib/contrast/api/decorators/address.rb +2 -3
  75. data/lib/contrast/api/decorators/agent_startup.rb +7 -9
  76. data/lib/contrast/api/decorators/application_startup.rb +9 -10
  77. data/lib/contrast/api/decorators/application_update.rb +0 -4
  78. data/lib/contrast/api/decorators/http_request.rb +3 -7
  79. data/lib/contrast/api/decorators/instrumentation_mode.rb +3 -5
  80. data/lib/contrast/api/decorators/message.rb +7 -7
  81. data/lib/contrast/api/decorators/route_coverage.rb +24 -1
  82. data/lib/contrast/api/decorators/trace_event_object.rb +2 -3
  83. data/lib/contrast/components/agent.rb +13 -15
  84. data/lib/contrast/components/app_context.rb +7 -11
  85. data/lib/contrast/components/assess.rb +19 -16
  86. data/lib/contrast/components/base.rb +40 -0
  87. data/lib/contrast/components/config.rb +1 -2
  88. data/lib/contrast/components/contrast_service.rb +8 -11
  89. data/lib/contrast/components/heap_dump.rb +5 -4
  90. data/lib/contrast/components/inventory.rb +2 -7
  91. data/lib/contrast/components/logger.rb +14 -10
  92. data/lib/contrast/components/protect.rb +10 -13
  93. data/lib/contrast/components/sampling.rb +5 -5
  94. data/lib/contrast/components/scope.rb +9 -32
  95. data/lib/contrast/components/settings.rb +1 -5
  96. data/lib/contrast/config/base_configuration.rb +14 -6
  97. data/lib/contrast/configuration.rb +22 -19
  98. data/lib/contrast/extension/assess/array.rb +3 -15
  99. data/lib/contrast/extension/assess/eval_trigger.rb +2 -23
  100. data/lib/contrast/extension/assess/fiber.rb +6 -16
  101. data/lib/contrast/extension/assess/hash.rb +3 -13
  102. data/lib/contrast/extension/assess/kernel.rb +3 -14
  103. data/lib/contrast/extension/assess/marshal.rb +6 -14
  104. data/lib/contrast/extension/assess/regexp.rb +5 -15
  105. data/lib/contrast/extension/assess/string.rb +6 -31
  106. data/lib/contrast/extension/extension.rb +61 -0
  107. data/lib/contrast/extension/kernel.rb +2 -4
  108. data/lib/contrast/extension/protect/kernel.rb +0 -15
  109. data/lib/contrast/framework/grape/support.rb +174 -0
  110. data/lib/contrast/framework/manager.rb +44 -9
  111. data/lib/contrast/framework/rack/patch/session_cookie.rb +6 -6
  112. data/lib/contrast/framework/rack/support.rb +1 -1
  113. data/lib/contrast/framework/rails/patch/assess_configuration.rb +5 -8
  114. data/lib/contrast/framework/rails/patch/support.rb +43 -36
  115. data/lib/contrast/framework/rails/railtie.rb +8 -6
  116. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +4 -4
  117. data/lib/contrast/framework/rails/support.rb +60 -13
  118. data/lib/contrast/framework/sinatra/support.rb +1 -1
  119. data/lib/contrast/funchook/funchook.rb +4 -3
  120. data/lib/contrast/logger/application.rb +1 -6
  121. data/lib/contrast/logger/log.rb +103 -13
  122. data/lib/contrast/logger/request.rb +0 -4
  123. data/lib/contrast/tasks/config.rb +0 -1
  124. data/lib/contrast/tasks/service.rb +1 -6
  125. data/lib/contrast/utils/assess/sampling_util.rb +2 -3
  126. data/lib/contrast/utils/assess/tracking_util.rb +2 -4
  127. data/lib/contrast/utils/class_util.rb +26 -19
  128. data/lib/contrast/utils/heap_dump_util.rb +5 -3
  129. data/lib/contrast/utils/invalid_configuration_util.rb +4 -3
  130. data/lib/contrast/utils/io_util.rb +46 -40
  131. data/lib/contrast/utils/job_servers_running.rb +4 -3
  132. data/lib/contrast/utils/lru_cache.rb +43 -0
  133. data/lib/contrast/utils/os.rb +2 -3
  134. data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
  135. data/lib/contrast/utils/string_utils.rb +2 -3
  136. data/lib/contrast/utils/tag_util.rb +26 -19
  137. data/lib/contrast.rb +24 -14
  138. data/resources/assess/policy.json +197 -2
  139. data/resources/deadzone/policy.json +10 -0
  140. data/ruby-agent.gemspec +13 -3
  141. data/service_executables/VERSION +1 -1
  142. data/service_executables/linux/contrast-service +0 -0
  143. data/service_executables/mac/contrast-service +0 -0
  144. metadata +91 -25
  145. data/lib/contrast/components/interface.rb +0 -196
  146. data/lib/contrast/delegators/input_analysis.rb +0 -12
  147. data/lib/contrast/utils/inventory_util.rb +0 -114
@@ -3,7 +3,7 @@
3
3
 
4
4
  require 'contrast/agent/patching/policy/patch'
5
5
  require 'contrast/agent/patching/policy/patcher'
6
- require 'contrast/components/interface'
6
+ require 'contrast/components/scope'
7
7
 
8
8
  module Contrast
9
9
  module Extension
@@ -11,10 +11,8 @@ module Contrast
11
11
  # This is our patch of the Array class required to handle propagation
12
12
  # Disclaimer: there may be a better way, but we're in a 'get it work' state.
13
13
  # Hopefully, we'll be in a 'get it right' state soon.
14
- class ArrayPropagator
15
- include Contrast::Components::Interface
16
-
17
- access_component :scope
14
+ class ArrayPropagator # rubocop:disable Style/StaticClass
15
+ extend Contrast::Components::Scope::InstanceMethods
18
16
 
19
17
  ARRAY_JOIN_HASH = {
20
18
  'class_name' => 'Array',
@@ -61,16 +59,6 @@ module Contrast
61
59
  ret
62
60
  end
63
61
  end
64
-
65
- def instrument_array_track
66
- @_instrument_array_track ||= begin
67
- require 'cs__assess_array/cs__assess_array'
68
- true
69
- end
70
- rescue StandardError, LoadError => e
71
- logger.error('Error loading assess track patch', e)
72
- false
73
- end
74
62
  end
75
63
  end
76
64
  end
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'contrast/components/interface'
4
+ require 'contrast/components/logger'
5
5
 
6
6
  module Contrast
7
7
  module Extension
@@ -11,8 +11,7 @@ module Contrast
11
11
  # apply the trigger in a custom patch over one of the generic triggers in
12
12
  # TriggerMethod.
13
13
  class EvalTrigger
14
- include Contrast::Components::Interface
15
- access_component :logging
14
+ include Contrast::Components::Logger::InstanceMethods
16
15
 
17
16
  class << self
18
17
  def instance_eval_trigger_check obj, source, ret
@@ -35,26 +34,6 @@ module Contrast
35
34
  ret, source)
36
35
  end
37
36
 
38
- def instrument_basic_object_track
39
- @_instrument_basic_object_track ||= begin
40
- require 'cs__assess_basic_object/cs__assess_basic_object'
41
- true
42
- end
43
- rescue StandardError, LoadError => e
44
- logger.error('Error loading basic object track patch', e)
45
- false
46
- end
47
-
48
- def instrument_module_track
49
- @_instrument_module_track ||= begin
50
- require 'cs__assess_module/cs__assess_module'
51
- true
52
- end
53
- rescue StandardError, LoadError => e
54
- logger.error('Error loading module track patch', e)
55
- false
56
- end
57
-
58
37
  private
59
38
 
60
39
  def trigger_node clazz, method
@@ -2,7 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/agent/assess/policy/propagation_node'
5
- require 'contrast/components/interface'
5
+ require 'contrast/components/logger'
6
+ require 'contrast/components/scope'
6
7
 
7
8
  # In order to instrument some difficult methods like String#gsub, as it
8
9
  # returns an enumerator, we need to instrument methods on Fiber.
@@ -16,9 +17,8 @@ module Contrast
16
17
  # Contrast::Agent::Assess::Policy::Propagator molds without cluttering up the
17
18
  # Fiber Class or exposing our methods there.
18
19
  class FiberPropagator
19
- include Contrast::Components::Interface
20
-
21
- access_component :analysis, :logging, :scope
20
+ extend Contrast::Components::Logger::InstanceMethods
21
+ extend Contrast::Components::Scope::InstanceMethods
22
22
 
23
23
  # we use funchook to patch rb_fiber_new the initialize method is not exposed by Ruby core
24
24
  FIBER_NEW_NODE_HASH = {
@@ -53,7 +53,7 @@ module Contrast
53
53
 
54
54
  class << self
55
55
  def track_rb_fiber_yield fiber, _method, results
56
- return unless ASSESS.enabled?
56
+ return unless ::Contrast::ASSESS.enabled?
57
57
 
58
58
  # results will be nil if StopIteration was raised,
59
59
  # otherwise an Array of the yielded arguments
@@ -72,7 +72,7 @@ module Contrast
72
72
  end
73
73
 
74
74
  def track_rb_fiber_new fiber, _enum, _enum_method, underlying, _underlying_method
75
- return unless ASSESS.enabled?
75
+ return unless ::Contrast::ASSESS.enabled?
76
76
  return unless underlying.is_a?(String) && !underlying.empty?
77
77
 
78
78
  with_contrast_scope do
@@ -85,16 +85,6 @@ module Contrast
85
85
  rescue Exception => e # rubocop:disable Lint/RescueException
86
86
  logger.error('Unable to propagate during Fiber.new', e)
87
87
  end
88
-
89
- def instrument_fiber_track
90
- @_instrument_fiber_variables ||= begin
91
- require 'cs__assess_fiber_track/cs__assess_fiber_track' if Funchook.available?
92
- true
93
- end
94
- rescue StandardError, LoadError => e
95
- logger.error('Error loading fiber track patch', e)
96
- false
97
- end
98
88
  end
99
89
  end
100
90
  end
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'contrast/components/interface'
4
+ require 'contrast/components/logger'
5
5
 
6
6
  module Contrast
7
7
  module Extension
@@ -10,8 +10,8 @@ module Contrast
10
10
  # methods which are too complex to fit into one of the standard
11
11
  # Contrast::Agent::Assess::Policy::Propagator molds.
12
12
  class HashPropagator
13
- include Contrast::Components::Interface
14
- access_component :logging
13
+ include Contrast::Components::Logger::InstanceMethods
14
+
15
15
  class << self
16
16
  def cs__duplicate_and_freeze object
17
17
  return object unless object.is_a?(String) && !object.cs__frozen?
@@ -25,16 +25,6 @@ module Contrast
25
25
  # result in a seg fault
26
26
  object
27
27
  end
28
-
29
- def instrument_hash_track
30
- @_instrument_hash_track ||= begin
31
- require 'cs__assess_hash/cs__assess_hash'
32
- true
33
- end
34
- rescue StandardError, LoadError => e
35
- logger.error('Error loading hash track patch', e)
36
- false
37
- end
38
28
  end
39
29
  end
40
30
  end
@@ -1,8 +1,8 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'contrast/components/interface'
5
4
  require 'contrast/extension/assess/exec_trigger'
5
+ require 'contrast/components/logger'
6
6
 
7
7
  module Contrast
8
8
  module Extension
@@ -13,11 +13,10 @@ module Contrast
13
13
  # Kernel Module or exposing our methods there.
14
14
  module KernelPropagator
15
15
  class << self
16
- include Contrast::Components::Interface
16
+ extend Contrast::Components::Logger::InstanceMethods
17
+ include Contrast::Components::Logger::InstanceMethods
17
18
  include Contrast::Extension::Assess::ExecTrigger
18
19
 
19
- access_component :logging
20
-
21
20
  # We're 'tracking' sprintf now, meaning if anything is tracked on the way
22
21
  # in, the entire result will be tracked out. We're going to take this
23
22
  # approach for now b/c it's fast and easy. I don't super love it, and by
@@ -67,16 +66,6 @@ module Contrast
67
66
  logger.error('Unable to track dataflow through sprintf', e)
68
67
  end
69
68
 
70
- def instrument_kernel_track
71
- @_instrument_fiber_variables ||= begin
72
- require 'cs__assess_kernel/cs__assess_kernel'
73
- true
74
- end
75
- rescue StandardError, LoadError => e
76
- logger.error('Error loading kernel track patch', e)
77
- false
78
- end
79
-
80
69
  private
81
70
 
82
71
  def handle_sprintf_value value, result, parent_events
@@ -1,7 +1,8 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'contrast/components/interface'
4
+ require 'contrast/components/logger'
5
+ require 'contrast/components/scope'
5
6
 
6
7
  module Contrast
7
8
  module Extension
@@ -11,11 +12,12 @@ module Contrast
11
12
  # Hopefully, we'll be in a 'get it right' state soon.
12
13
  # This module is used for our Marshal.load patches
13
14
  class MarshalPropagator
14
- include Contrast::Components::Interface
15
-
16
- access_component :logging, :scope
15
+ extend Contrast::Components::Scope::InstanceMethods
17
16
 
18
17
  class << self
18
+ extend Contrast::Components::Logger::InstanceMethods
19
+ include Contrast::Components::Logger::InstanceMethods
20
+
19
21
  def cs__load_protect arg
20
22
  return if in_contrast_scope?
21
23
 
@@ -44,16 +46,6 @@ module Contrast
44
46
  end
45
47
  end
46
48
 
47
- def instrument_marshal_load
48
- @_instrument_marshal_load ||= begin
49
- require 'cs__assess_marshal_module/cs__assess_marshal_module'
50
- true
51
- end
52
- rescue StandardError, LoadError => e
53
- logger.error('Error loading marshal load patch', e)
54
- false
55
- end
56
-
57
49
  def trigger_node clazz, method
58
50
  triggers = Contrast::Agent::Assess::Policy::Policy.instance.triggers
59
51
  return unless triggers
@@ -2,7 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/agent/assess/policy/propagation_node'
5
- require 'contrast/components/interface'
5
+ require 'contrast/components/logger'
6
+ require 'contrast/components/scope'
6
7
 
7
8
  module Contrast
8
9
  module Extension
@@ -12,9 +13,8 @@ module Contrast
12
13
  # Contrast::Agent::Assess::Policy::Propagator molds without cluttering up the
13
14
  # Regexp Class or exposing our methods there.
14
15
  class RegexpPropagator
15
- include Contrast::Components::Interface
16
-
17
- access_component :analysis, :logging, :scope
16
+ extend Contrast::Components::Logger::InstanceMethods
17
+ extend Contrast::Components::Scope::InstanceMethods
18
18
 
19
19
  REGEXP_EQUAL_SQUIGGLE_HASH = {
20
20
  'id' => 'regexp_100',
@@ -34,7 +34,7 @@ module Contrast
34
34
 
35
35
  class << self
36
36
  def track_equal_squiggle info_hash
37
- return unless ASSESS.enabled?
37
+ return unless ::Contrast::ASSESS.enabled?
38
38
 
39
39
  # Because we have a special case for this propagation,
40
40
  # it falls out of regular scoping. As such, any patch to the `=~` method
@@ -58,16 +58,6 @@ module Contrast
58
58
  rescue Exception => e # rubocop:disable Lint/RescueException
59
59
  logger.error('Unable to propagate during Regexp#=~', e)
60
60
  end
61
-
62
- def instrument_regexp_track
63
- @_instrument_regexp_track ||= begin
64
- require 'cs__assess_regexp/cs__assess_regexp'
65
- true
66
- end
67
- rescue StandardError, LoadError => e
68
- logger.error('Error loading regexp track patch', e)
69
- false
70
- end
71
61
  end
72
62
  end
73
63
  end
@@ -2,7 +2,8 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/agent/assess/policy/propagation_node'
5
- require 'contrast/components/interface'
5
+ require 'contrast/components/logger'
6
+ require 'contrast/components/scope'
6
7
 
7
8
  module Contrast
8
9
  module Extension
@@ -11,10 +12,9 @@ module Contrast
11
12
  # methods which are too complex to fit into one of the standard
12
13
  # Contrast::Agent::Assess::Policy::Propagator molds without cluttering up the
13
14
  # String Class or exposing our methods there.
14
- class StringPropagator
15
- include Contrast::Components::Interface
16
-
17
- access_component :agent, :analysis, :logging, :scope
15
+ class StringPropagator # rubocop:disable Style/StaticClass
16
+ extend Contrast::Components::Logger::InstanceMethods
17
+ extend Contrast::Components::Scope::InstanceMethods
18
18
 
19
19
  NODE_HASH = {
20
20
  'class_name' => 'String',
@@ -31,7 +31,7 @@ module Contrast
31
31
 
32
32
  class << self
33
33
  def track_interpolation inputs, result
34
- return unless AGENT.interpolation_enabled?
34
+ return unless ::Contrast::AGENT.interpolation_enabled?
35
35
  return if in_contrast_scope?
36
36
  return unless inputs.any? { |input| Contrast::Agent::Assess::Tracker.tracked?(input) }
37
37
 
@@ -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 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
@@ -41,15 +41,13 @@ module Kernel # :nodoc:
41
41
 
42
42
  def catch *args, &block
43
43
  # Save current scope level
44
- scope_level =
45
- Contrast::Components::Scope::COMPONENT_INTERFACE.scope_for_current_ec.instance_variable_get(:@contrast_scope)
44
+ scope_level = ::Contrast::SCOPE.scope_for_current_ec.instance_variable_get(:@contrast_scope)
46
45
 
47
46
  # Run original catch with block.
48
47
  retval = cs__catch(*args, &block)
49
48
 
50
49
  # Restore scope.
51
- Contrast::Components::Scope::COMPONENT_INTERFACE.scope_for_current_ec.instance_variable_set(:@contrast_scope,
52
- scope_level)
50
+ ::Contrast::SCOPE.scope_for_current_ec.instance_variable_set(:@contrast_scope, scope_level)
53
51
 
54
52
  retval
55
53
  end
@@ -1,8 +1,6 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'contrast/components/interface'
5
-
6
4
  module Contrast
7
5
  module Extension
8
6
  module Protect
@@ -10,9 +8,6 @@ module Contrast
10
8
  # allowing us to track activity as it crosses spawned processes.
11
9
  module Kernel
12
10
  class << self
13
- include Contrast::Components::Interface
14
- access_component :contrast_service
15
-
16
11
  def build_wrapper
17
12
  lambda {
18
13
  proc_start
@@ -27,16 +22,6 @@ module Contrast
27
22
 
28
23
  context.reset_activity
29
24
  end
30
-
31
- def instrument
32
- @_instrument ||= begin
33
- require 'cs__protect_kernel/cs__protect_kernel'
34
- true
35
- end
36
- rescue StandardError, LoadError => e
37
- logger.error('Error loading kernel protect patch', e)
38
- false
39
- end
40
25
  end
41
26
  end
42
27
  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