contrast-agent 3.8.5 → 3.9.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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_array/cs__assess_array.c +1 -1
  3. data/ext/cs__assess_module/cs__assess_module.c +0 -1
  4. data/ext/cs__assess_yield_track/cs__assess_yield_track.c +34 -0
  5. data/ext/cs__assess_yield_track/cs__assess_yield_track.h +12 -0
  6. data/ext/{cs__scope → cs__assess_yield_track}/extconf.rb +0 -0
  7. data/ext/cs__common/cs__common.c +6 -6
  8. data/ext/cs__common/cs__common.h +3 -1
  9. data/ext/cs__contrast_patch/cs__contrast_patch.c +142 -119
  10. data/ext/cs__contrast_patch/cs__contrast_patch.h +3 -0
  11. data/funchook/autom4te.cache/requests +48 -48
  12. data/funchook/config.log +2 -2
  13. data/lib/contrast/agent.rb +15 -5
  14. data/lib/contrast/agent/assess.rb +0 -1
  15. data/lib/contrast/agent/assess/contrast_event.rb +9 -8
  16. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +68 -18
  17. data/lib/contrast/agent/assess/policy/policy.rb +0 -14
  18. data/lib/contrast/agent/assess/policy/policy_scanner.rb +1 -1
  19. data/lib/contrast/agent/assess/policy/preshift.rb +1 -1
  20. data/lib/contrast/agent/assess/policy/propagation_method.rb +4 -2
  21. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  22. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -1
  23. data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -2
  24. data/lib/contrast/agent/assess/policy/propagator/split.rb +166 -1
  25. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +1 -0
  26. data/lib/contrast/agent/assess/policy/source_method.rb +199 -140
  27. data/lib/contrast/agent/assess/policy/source_validation/cross_site_validator.rb +30 -0
  28. data/lib/contrast/agent/assess/policy/source_validation/source_validation.rb +36 -0
  29. data/lib/contrast/agent/assess/policy/trigger_method.rb +238 -153
  30. data/lib/contrast/agent/assess/policy/trigger_node.rb +54 -9
  31. data/lib/contrast/agent/assess/policy/trigger_validation/trigger_validation.rb +13 -0
  32. data/lib/contrast/agent/assess/properties.rb +29 -0
  33. data/lib/contrast/agent/assess/rule/csrf/csrf_applicator.rb +35 -31
  34. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +1 -1
  35. data/lib/contrast/agent/class_reopener.rb +98 -55
  36. data/lib/contrast/agent/feature_state.rb +1 -1
  37. data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
  38. data/lib/contrast/agent/logger_manager.rb +2 -2
  39. data/lib/contrast/agent/middleware.rb +1 -3
  40. data/lib/contrast/agent/patching/policy/after_load_patch.rb +40 -4
  41. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +33 -8
  42. data/lib/contrast/agent/patching/policy/method_policy.rb +20 -7
  43. data/lib/contrast/agent/patching/policy/patch.rb +54 -23
  44. data/lib/contrast/agent/patching/policy/patch_status.rb +0 -2
  45. data/lib/contrast/agent/patching/policy/patcher.rb +10 -11
  46. data/lib/contrast/agent/patching/policy/policy.rb +4 -0
  47. data/lib/contrast/agent/patching/policy/policy_node.rb +14 -1
  48. data/lib/contrast/agent/patching/policy/trigger_node.rb +2 -1
  49. data/lib/contrast/agent/protect/policy/policy.rb +6 -6
  50. data/lib/contrast/agent/protect/rule/base.rb +1 -1
  51. data/lib/contrast/agent/protect/rule/deserialization.rb +3 -25
  52. data/lib/contrast/agent/protect/rule/sqli.rb +1 -1
  53. data/lib/contrast/agent/railtie.rb +11 -5
  54. data/lib/contrast/agent/request.rb +1 -19
  55. data/lib/contrast/agent/request_context.rb +1 -1
  56. data/lib/contrast/agent/rewriter.rb +4 -3
  57. data/lib/contrast/agent/scope.rb +116 -19
  58. data/lib/contrast/agent/service_heartbeat.rb +5 -2
  59. data/lib/contrast/agent/settings_state.rb +12 -8
  60. data/lib/contrast/agent/version.rb +1 -1
  61. data/lib/contrast/api.rb +1 -0
  62. data/lib/contrast/api/speedracer.rb +2 -2
  63. data/lib/contrast/components/agent.rb +26 -7
  64. data/lib/contrast/components/app_context.rb +8 -45
  65. data/lib/contrast/components/contrast_service.rb +3 -4
  66. data/lib/contrast/components/interface.rb +1 -1
  67. data/lib/contrast/components/scope.rb +56 -26
  68. data/lib/contrast/config/ruby_configuration.rb +8 -3
  69. data/lib/contrast/delegators.rb +9 -0
  70. data/lib/contrast/delegators/application_update.rb +32 -0
  71. data/lib/contrast/extensions/framework/rack/cookie.rb +24 -0
  72. data/lib/contrast/extensions/framework/rack/request.rb +24 -0
  73. data/lib/contrast/extensions/framework/rack/response.rb +23 -0
  74. data/lib/contrast/extensions/framework/rails/action_controller_railties_helper_inherited.rb +20 -0
  75. data/lib/contrast/extensions/framework/rails/active_record.rb +26 -0
  76. data/lib/contrast/extensions/framework/rails/active_record_named.rb +53 -0
  77. data/lib/contrast/extensions/framework/rails/active_record_time_zone_inherited.rb +21 -0
  78. data/lib/contrast/extensions/framework/rails/buffer.rb +28 -0
  79. data/lib/contrast/extensions/framework/rails/configuration.rb +27 -0
  80. data/lib/contrast/extensions/framework/sinatra/base.rb +59 -0
  81. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess.rb +12 -11
  82. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/array.rb +4 -3
  83. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/assess_extension.rb +0 -2
  84. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/basic_object.rb +1 -1
  85. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/erb.rb +0 -0
  86. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/exec_trigger.rb +0 -0
  87. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/fiber.rb +3 -4
  88. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/hash.rb +0 -0
  89. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/kernel.rb +1 -1
  90. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/module.rb +1 -1
  91. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/regexp.rb +0 -0
  92. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/string.rb +0 -0
  93. data/lib/contrast/{core_extensions → extensions/ruby_core}/assess/tilt_template_trigger.rb +0 -0
  94. data/lib/contrast/extensions/ruby_core/assess/xpath_library_trigger.rb +40 -0
  95. data/lib/contrast/{core_extensions → extensions/ruby_core}/delegator.rb +0 -0
  96. data/lib/contrast/{core_extensions → extensions/ruby_core}/eval_trigger.rb +1 -1
  97. data/lib/contrast/{core_extensions → extensions/ruby_core}/inventory.rb +0 -0
  98. data/lib/contrast/{core_extensions → extensions/ruby_core}/inventory/datastores.rb +1 -1
  99. data/lib/contrast/extensions/ruby_core/module.rb +17 -0
  100. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect.rb +0 -0
  101. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_command_injection_rule.rb +8 -6
  102. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_deserialization_rule.rb +7 -5
  103. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_no_sqli_rule.rb +5 -3
  104. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_path_traversal_rule.rb +31 -27
  105. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_sqli_rule.rb +5 -3
  106. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/applies_xxe_rule.rb +9 -7
  107. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/kernel.rb +0 -0
  108. data/lib/contrast/{core_extensions → extensions/ruby_core}/protect/psych.rb +1 -1
  109. data/lib/contrast/{core_extensions → extensions/ruby_core}/thread.rb +0 -0
  110. data/lib/contrast/framework/base_support.rb +63 -0
  111. data/lib/contrast/framework/manager.rb +109 -0
  112. data/lib/contrast/framework/platform_version.rb +21 -0
  113. data/lib/contrast/framework/rails_support.rb +88 -0
  114. data/lib/contrast/framework/sinatra_application_helper.rb +49 -0
  115. data/lib/contrast/framework/sinatra_support.rb +94 -0
  116. data/lib/contrast/framework/view_technologies_descriptor.rb +20 -0
  117. data/lib/contrast/utils/assess/tracking_util.rb +2 -4
  118. data/lib/contrast/utils/class_util.rb +92 -37
  119. data/lib/contrast/utils/duck_utils.rb +59 -39
  120. data/lib/contrast/utils/environment_util.rb +5 -75
  121. data/lib/contrast/utils/freeze_util.rb +3 -7
  122. data/lib/contrast/utils/invalid_configuration_util.rb +5 -5
  123. data/lib/contrast/utils/job_servers_running.rb +39 -0
  124. data/lib/contrast/utils/ruby_ast_rewriter.rb +2 -2
  125. data/lib/contrast/utils/service_response_util.rb +0 -6
  126. data/lib/contrast/utils/sinatra_helper.rb +6 -0
  127. data/lib/contrast/utils/stack_trace_utils.rb +1 -1
  128. data/resources/assess/policy.json +74 -23
  129. data/resources/inventory/policy.json +1 -1
  130. data/resources/protect/policy.json +11 -9
  131. data/resources/rubocops/object/frozen_cop.rb +1 -1
  132. data/ruby-agent.gemspec +2 -0
  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 +94 -57
  137. data/ext/cs__scope/cs__scope.c +0 -96
  138. data/ext/cs__scope/cs__scope.h +0 -33
  139. data/lib/contrast/agent/assess/class_reverter.rb +0 -82
  140. data/lib/contrast/agent/patching/policy/policy_unpatcher.rb +0 -28
  141. data/lib/contrast/core_extensions/module.rb +0 -42
  142. data/lib/contrast/core_extensions/object.rb +0 -27
  143. data/lib/contrast/rails_extensions/assess/action_controller_inheritance.rb +0 -48
  144. data/lib/contrast/rails_extensions/assess/active_record.rb +0 -32
  145. data/lib/contrast/rails_extensions/assess/active_record_named.rb +0 -61
  146. data/lib/contrast/rails_extensions/assess/configuration.rb +0 -26
  147. data/lib/contrast/rails_extensions/buffer.rb +0 -30
  148. data/lib/contrast/rails_extensions/rack.rb +0 -45
  149. data/lib/contrast/sinatra_extensions/assess/cookie.rb +0 -26
  150. data/lib/contrast/sinatra_extensions/inventory/sinatra_base.rb +0 -59
  151. data/lib/contrast/utils/operating_environment.rb +0 -38
  152. data/lib/contrast/utils/path_util.rb +0 -151
  153. data/lib/contrast/utils/scope_util.rb +0 -99
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Framework
6
+ # Used to map version strings from frameworks to ApplicationUpdate dtm
7
+ class PlatformVersion
8
+ attr_reader :major, :minor, :patch
9
+ def initialize major, minor, patch
10
+ @major = major || ''
11
+ @minor = minor || ''
12
+ @patch = patch || ''
13
+ end
14
+
15
+ def self.from_string platform_version_string
16
+ version_array = platform_version_string.split(Contrast::Utils::ObjectShare::PERIOD)
17
+ new(version_array[0], version_array[1], version_array[2])
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,88 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Framework
6
+ # Used when Rails is present to define framework specific behavior
7
+ class RailsSupport < BaseSupport
8
+ class << self
9
+ RAILS_VIEWS = [
10
+ Contrast::Framework::ViewTechnologiesDescriptor.new('app/assets', 'coffee', %w[CoffeeScript]),
11
+ Contrast::Framework::ViewTechnologiesDescriptor.new('app/assets', 'scss', %w[SASS]),
12
+ Contrast::Framework::ViewTechnologiesDescriptor.new('app/views', 'html', %w[HTML5]),
13
+ Contrast::Framework::ViewTechnologiesDescriptor.new('app/views', 'html.erb', %w[HTML5 ERB]),
14
+ Contrast::Framework::ViewTechnologiesDescriptor.new('app/views', 'html.haml', %w[HTML5 HAML]),
15
+ Contrast::Framework::ViewTechnologiesDescriptor.new('public', 'html', %w[HTML5])
16
+ ].cs__freeze
17
+
18
+ RAILS_MODULE_NAME_VERSION = Gem::Version.new('6.0.0')
19
+
20
+ def detection_class
21
+ 'Rails'
22
+ end
23
+
24
+ def version
25
+ Rails.version
26
+ end
27
+
28
+ def application_name
29
+ # Rails version 6.0.0 deprecated Rails::Application#parent_name, in Rails 6.1.0 that method will be removed entirely
30
+ # and instead we need to use parent_module_name
31
+ return Rails.application.cs__class.parent_module_name if Gem::Version.new(Rails.version) >= RAILS_MODULE_NAME_VERSION
32
+
33
+ Rails.application.cs__class.parent_name
34
+ end
35
+
36
+ def server_type
37
+ 'rails'
38
+ end
39
+
40
+ def scan_views
41
+ scan_view_directories(RAILS_VIEWS)
42
+ end
43
+
44
+ def collect_routes
45
+ routes = Rails.application.routes.routes.map do |route|
46
+ route_to_coverage(route)
47
+ end
48
+ routes
49
+ end
50
+
51
+ def current_route request
52
+ return unless Rails.cs__respond_to?(:application)
53
+
54
+ # returns array of arrays [[match_data, path_parameters, route]], sorted by
55
+ # precedence
56
+ # match_data: ActionDispatch::Journey::Path::Pattern::MatchData
57
+ # path_parameters: hash of various things
58
+ # route: ActionDispatch::Journey::Route
59
+ full_routes = Rails.application.routes.router.send(:find_routes, request.rack_request)
60
+ return if full_routes.empty?
61
+
62
+ full_route = full_routes[0] # [match_data, path_parameters, route]
63
+ return unless full_route
64
+
65
+ route = full_route[2] # route w/ highest precedence
66
+ return unless route
67
+
68
+ route_to_coverage(route)
69
+ end
70
+
71
+ private
72
+
73
+ # Convert ActionDispatch::Journey::Route to Contrast::Api::Dtm::RouteCoverage
74
+ def route_to_coverage route
75
+ route_coverage = Contrast::Api::Dtm::RouteCoverage.new
76
+ route_coverage.route = "#{ route.defaults[:controller] }##{ route.defaults[:action] }"
77
+
78
+ verb = source_or_string(route.verb)
79
+ route_coverage.verb = Contrast::Utils::StringUtils.force_utf8(verb)
80
+
81
+ url = source_or_string(route.path.spec)
82
+ route_coverage.url = Contrast::Utils::StringUtils.force_utf8(url)
83
+ route_coverage
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,49 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Framework
6
+ # This module is to help perform sometimes necessary tasks specific to Sinatra
7
+ module SinatraApplicationHelper
8
+ SINATRA_VIEWS = [
9
+ Contrast::Framework::ViewTechnologiesDescriptor.new('public/stylesheets', 'scss', %w[SASS]),
10
+ Contrast::Framework::ViewTechnologiesDescriptor.new('views', 'html', %w[HTML5]),
11
+ Contrast::Framework::ViewTechnologiesDescriptor.new('views', 'html.erb', %w[HTML5 ERB]),
12
+ Contrast::Framework::ViewTechnologiesDescriptor.new('views', 'html.haml', %w[HTML5 HAML]),
13
+ Contrast::Framework::ViewTechnologiesDescriptor.new('public', 'html', %w[HTML5])
14
+ ].cs__freeze
15
+
16
+ def app_class
17
+ @_app_class ||= begin
18
+ return nil unless defined?(Sinatra) && defined?(Sinatra::Base)
19
+
20
+ sinatra_layers = ObjectSpace.each_object(Sinatra::Base).to_a
21
+ result_layer = sinatra_layers.find { |layer| layer.app.nil? }
22
+ result_layer
23
+ end
24
+ end
25
+
26
+ def scannable_view_dirs
27
+ @_scannable_view_dirs ||= begin
28
+ views = SINATRA_VIEWS.dup
29
+ views << Contrast::Framework::ViewTechnologiesDescriptor.new(view_directory, 'erb', %w[HTML5 ERB]) if view_directory
30
+ views << Contrast::Framework::ViewTechnologiesDescriptor.new(view_directory, 'haml', %w[HTML5 HAML]) if view_directory
31
+ views << Contrast::Framework::ViewTechnologiesDescriptor.new(public_directory, 'html', %w[HTML5]) if public_directory
32
+ views
33
+ end
34
+ end
35
+
36
+ def view_directory
37
+ @_view_directory ||= begin
38
+ app_class&.settings&.views
39
+ end
40
+ end
41
+
42
+ def public_directory
43
+ @_public_directory ||= begin
44
+ app_class&.settings&.public_dir
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,94 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ cs__scoped_require 'contrast/framework/sinatra_application_helper'
5
+
6
+ module Contrast
7
+ module Framework
8
+ # Used when Sinatra is present to define framework specific behavior
9
+ class SinatraSupport < BaseSupport
10
+ extend Contrast::Framework::SinatraApplicationHelper
11
+ class << self
12
+ def detection_class
13
+ 'Sinatra'
14
+ end
15
+
16
+ def version
17
+ Sinatra::VERSION
18
+ end
19
+
20
+ def application_name
21
+ return unless app_class
22
+
23
+ app_class.cs__class.cs__name
24
+ end
25
+
26
+ def server_type
27
+ 'sinatra'
28
+ end
29
+
30
+ def scan_views
31
+ return unless app_class
32
+
33
+ scan_view_directories(scannable_view_dirs)
34
+ end
35
+
36
+ # Iterate over every class that extends Sinatra::Base, pull out its routes
37
+ # (array of arrays with Mustermann::Sinatra as [][0]) and convert them into
38
+ # Contrast::Api::Dtm::RouteCoverage
39
+ def collect_routes
40
+ routes = []
41
+ controllers = sinatra_controllers
42
+ controllers.each do |clazz|
43
+ class_routes = sinatra_class_routes(clazz)
44
+ next unless class_routes
45
+
46
+ class_routes.each_pair do |method, list|
47
+ # item: [ Mustermann::Sinatra, [], Proc]
48
+ list.each do |item|
49
+ routes << route_to_coverage(clazz, method, item[0])
50
+ end
51
+ end
52
+ end
53
+ routes
54
+ end
55
+
56
+ # TODO: RUBY-763
57
+ def current_route _request
58
+ nil
59
+ end
60
+
61
+ private
62
+
63
+ # Iterate over every class that extends Sinatra::Base, pull out its routes
64
+ # (array of arrays with Mustermann::Sinatra as [][0]) and convert them into
65
+ # Contrast::Api::Dtm::RouteCoverage
66
+ def sinatra_controllers
67
+ return [] unless defined?(Sinatra) && defined?(Sinatra::Base)
68
+
69
+ Contrast::Utils::ClassUtil.descendants(Sinatra::Base)
70
+ end
71
+
72
+ def sinatra_class_routes controller
73
+ controller.instance_variable_get(:@routes)
74
+ rescue StandardError
75
+ logger.debug(nil, "#{ controller } has no route instances")
76
+ nil
77
+ end
78
+
79
+ # TODO: RUBY-763
80
+ # given clazz, method, and Mustermann::Sinatra, build a
81
+ # Contrast::Api::Dtm::RouteCoverage
82
+ def route_to_coverage clazz, method, pattern
83
+ safe_pattern = source_or_string(pattern)
84
+
85
+ route_coverage = Contrast::Api::Dtm::RouteCoverage.new
86
+ route_coverage.route = "#{ clazz }##{ method } #{ safe_pattern }"
87
+ route_coverage.verb = Contrast::Utils::StringUtils.force_utf8(method)
88
+ route_coverage.url = Contrast::Utils::StringUtils.force_utf8(safe_pattern)
89
+ route_coverage
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,20 @@
1
+ # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ module Contrast
5
+ module Framework
6
+ # Used to define common locations and extensions for specific technologies
7
+ class ViewTechnologiesDescriptor
8
+ attr_reader :path, :extension, :technology_names
9
+ def initialize path, extension, technology_names
10
+ @path = path
11
+ @extension = extension
12
+ @technology_names = technology_names
13
+ end
14
+
15
+ def empty?
16
+ Dir["#{ path }/**/*#{ extension }"].empty?
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,8 +1,6 @@
1
1
  # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- cs__scoped_require 'contrast/core_extensions/object'
5
-
6
4
  module Contrast
7
5
  module Utils
8
6
  module Assess
@@ -11,13 +9,13 @@ module Contrast
11
9
  def self.tracked? obj
12
10
  return false if obj.nil?
13
11
 
14
- if Contrast::Utils::DuckUtils.quacks_like_tracked_hash?(obj)
12
+ if Contrast::Utils::DuckUtils.iterable_hash?(obj)
15
13
  obj.each_pair do |k, v|
16
14
  return true if tracked?(k)
17
15
  return true if tracked?(v)
18
16
  end
19
17
  false
20
- elsif Contrast::Utils::DuckUtils.quacks_like_tracked_enumerable?(obj)
18
+ elsif Contrast::Utils::DuckUtils.iterable_enumerable?(obj)
21
19
  obj.any? do |ele|
22
20
  tracked?(ele) unless obj == ele
23
21
  end
@@ -1,57 +1,112 @@
1
1
  # Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
- cs__scoped_require 'contrast/core_extensions/object'
4
+ cs__scoped_require 'contrast/extensions/ruby_core/module'
5
5
 
6
6
  module Contrast
7
7
  module Utils
8
- # Utility methods for exploring the complete space of objects
8
+ # Utility methods for exploring the complete space of Objects
9
9
  class ClassUtil
10
- def self.ancestors_of clazz
11
- arr = []
12
- ObjectSpace.each_object(Module) do |obj|
13
- next unless obj <= clazz
10
+ class << self
11
+ # Given a module, return all of its descendants
12
+ #
13
+ # @param mod [Module] the module whose descendants you want to find.
14
+ # @return [Array<Module>] those Modules that inherit from the given.
15
+ def descendants mod
16
+ ObjectSpace.each_object(mod).to_a
17
+ end
14
18
 
15
- arr << obj
19
+ # some classes have had things prepended to them, like Marshal in Rails
20
+ # 5 and higher. Their ActiveSupport::MarshalWithAutoloading will break
21
+ # our alias patching approach, as will any other prepend on something
22
+ # that we touch. Prepend and Alias are inherently incompatible monkey
23
+ # patching approaches. As such, we need to know if something has been
24
+ # prepended to.
25
+ #
26
+ # @param mod [Module] the Module to check to see if it has had something
27
+ # prepended
28
+ # @param ancestors [Array<Module>] the array of ancestors for the mod
29
+ # @return [Boolean] if the mod has been prepended or not
30
+ def prepended? mod, ancestors = nil
31
+ ancestors ||= mod.ancestors
32
+ ancestors[0] != mod
16
33
  end
17
- arr
18
- end
19
34
 
20
- # some classes have had things prepended to them, like Marshal in Rails
21
- # 5 and higher. Their ActiveSupport::MarshalWithAutoloading will break
22
- # our alias patching approach, as will any other prepend on something
23
- # that we touch. Prepend and Alias are inherently incompatible monkey
24
- # patching approaches. As such, we need to know if something has been
25
- # prepended to.
26
- #
27
- # return true if the given module is not its first ancestor
28
- def self.prepended? mod, ancestors = nil
29
- ancestors ||= mod.ancestors
30
- ancestors[0] != mod
31
- end
35
+ # return true if the given method is overwritten by one of the ancestors
36
+ # in the ancestor change that comes before the given module
37
+ def prepended_method? mod, method_policy
38
+ target_module = determine_target_class mod, method_policy.instance_method
39
+ ancestors = target_module.ancestors
40
+ return false unless prepended?(target_module, ancestors)
32
41
 
33
- def self.determine_target_class mod, is_instance
34
- return mod if mod.singleton_class?
42
+ ancestors.each do |ancestor|
43
+ break if ancestor == target_module
35
44
 
36
- return mod.cs__singleton_class unless is_instance
45
+ methods = ancestor.instance_methods(false)
46
+ return true if methods.include?(method_policy.method_name)
47
+ end
48
+ false
49
+ end
37
50
 
38
- mod
39
- end
51
+ # Return a String representing the object invoking this method in the
52
+ # form expected by our dataflow events.
53
+ #
54
+ # @param object [Object] the entity to convert to a String
55
+ # @return [String] the human readable form of the String, as defined by
56
+ # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/vulnerability/capture-snapshot.md
57
+ def to_contrast_string object
58
+ if object.cs__is_a?(String)
59
+ return Contrast::Utils::ObjectShare::EMPTY_STRING if object.empty?
60
+
61
+ object.dup
62
+ elsif object.cs__is_a?(Symbol)
63
+ ":#{ object }"
64
+ elsif object.cs__is_a?(Numeric)
65
+ object.to_s
66
+ elsif object.cs__is_a?(Module) || object.cs__is_a?(Class)
67
+ "#{ object.cs__name }@#{ object.__id__ }"
68
+ else
69
+ "#{ object.cs__class.cs__name }@#{ object.__id__ }"
70
+ end
71
+ end
72
+
73
+ # The method const_defined? can cause autoload, which is bad for us.
74
+ # The method autoload? doesn't traverse namespaces. This method lets us
75
+ # provide a constant, as a String, and parse it to determine if it has
76
+ # been truly truly defined, meaning it existed before this method was
77
+ # invoked, not as a result of it.
78
+ #
79
+ # This is required to handle a bug in Ruby prior to 2.7.0. When we drop
80
+ # support for 2.6.X, we should remove this code.
81
+ # https://bugs.ruby-lang.org/issues/10741
82
+ # @param name [String] the name of the constant to look up
83
+ # @return [Boolean]
84
+ def truly_defined? name
85
+ return false unless name
86
+
87
+ segments = name.split(Contrast::Utils::ObjectShare::DOUBLE_COLON)
88
+ previous_module = Module
89
+ segments.each do |segment|
90
+ return false if previous_module.cs__autoload?(segment)
91
+ return false unless previous_module.cs__const_defined?(segment)
92
+
93
+ previous_module = previous_module.cs__const_get(segment)
94
+ end
95
+
96
+ true
97
+ rescue NameError # account for nonsense / poorly formatted constants
98
+ false
99
+ end
100
+
101
+ private
40
102
 
41
- # return true if the given method is overwritten by one of the ancestors
42
- # in the ancestor change that comes before the given module
43
- def self.prepended_method? mod, method_policy
44
- target_module = determine_target_class mod, method_policy.instance_method
45
- ancestors = target_module.ancestors
46
- return false unless prepended?(target_module, ancestors)
103
+ def determine_target_class mod, is_instance
104
+ return mod if mod.singleton_class?
47
105
 
48
- ancestors.each do |ancestor|
49
- break if ancestor == target_module
106
+ return mod.cs__singleton_class unless is_instance
50
107
 
51
- methods = ancestor.instance_methods(false)
52
- return true if methods.include?(method_policy.method_name)
108
+ mod
53
109
  end
54
- false
55
110
  end
56
111
  end
57
112
  end