contrast-agent 3.16.0 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -0
  3. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +22 -10
  4. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +4 -3
  5. data/lib/contrast/agent.rb +2 -3
  6. data/lib/contrast/agent/assess/contrast_event.rb +49 -130
  7. data/lib/contrast/agent/assess/contrast_object.rb +51 -0
  8. data/lib/contrast/agent/assess/events/source_event.rb +4 -9
  9. data/lib/contrast/agent/assess/policy/patcher.rb +4 -3
  10. data/lib/contrast/agent/assess/policy/policy_node.rb +31 -59
  11. data/lib/contrast/agent/assess/policy/policy_scanner.rb +17 -6
  12. data/lib/contrast/agent/assess/policy/preshift.rb +3 -3
  13. data/lib/contrast/agent/assess/policy/propagation_method.rb +13 -19
  14. data/lib/contrast/agent/assess/policy/propagation_node.rb +12 -24
  15. data/lib/contrast/agent/assess/policy/propagator/append.rb +1 -2
  16. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -2
  17. data/lib/contrast/agent/assess/policy/propagator/custom.rb +1 -1
  18. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +1 -3
  19. data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -2
  20. data/lib/contrast/agent/assess/policy/propagator/keep.rb +1 -2
  21. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +3 -2
  22. data/lib/contrast/agent/assess/policy/propagator/next.rb +1 -2
  23. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +1 -2
  24. data/lib/contrast/agent/assess/policy/propagator/remove.rb +2 -4
  25. data/lib/contrast/agent/assess/policy/propagator/replace.rb +1 -2
  26. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +1 -2
  27. data/lib/contrast/agent/assess/policy/propagator/select.rb +3 -4
  28. data/lib/contrast/agent/assess/policy/propagator/splat.rb +2 -4
  29. data/lib/contrast/agent/assess/policy/propagator/split.rb +73 -117
  30. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +11 -11
  31. data/lib/contrast/agent/assess/policy/propagator/trim.rb +3 -7
  32. data/lib/contrast/agent/assess/policy/source_method.rb +2 -14
  33. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +5 -8
  34. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
  35. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
  36. data/lib/contrast/agent/assess/property/tagged.rb +21 -15
  37. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +3 -2
  38. data/lib/contrast/agent/assess/rule/redos.rb +1 -1
  39. data/lib/contrast/agent/assess/tracker.rb +16 -18
  40. data/lib/contrast/agent/deadzone/policy/deadzone_node.rb +7 -0
  41. data/lib/contrast/agent/inventory.rb +15 -0
  42. data/lib/contrast/agent/inventory/dependencies.rb +50 -0
  43. data/lib/contrast/agent/inventory/dependency_analysis.rb +37 -0
  44. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +104 -0
  45. data/lib/contrast/agent/inventory/gemfile_digest_cache.rb +38 -0
  46. data/lib/contrast/agent/middleware.rb +51 -3
  47. data/lib/contrast/agent/patching/policy/method_policy.rb +1 -1
  48. data/lib/contrast/agent/patching/policy/patch.rb +6 -0
  49. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +47 -1
  50. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +4 -3
  51. data/lib/contrast/agent/protect/policy/rule_applicator.rb +53 -0
  52. data/lib/contrast/agent/protect/rule/base.rb +63 -14
  53. data/lib/contrast/agent/protect/rule/cmd_injection.rb +12 -28
  54. data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -4
  55. data/lib/contrast/agent/protect/rule/deserialization.rb +4 -1
  56. data/lib/contrast/agent/protect/rule/no_sqli.rb +3 -3
  57. data/lib/contrast/agent/protect/rule/sqli.rb +3 -3
  58. data/lib/contrast/agent/protect/rule/xxe.rb +32 -11
  59. data/lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb +10 -6
  60. data/lib/contrast/agent/reaction_processor.rb +1 -1
  61. data/lib/contrast/agent/request_handler.rb +1 -1
  62. data/lib/contrast/agent/response.rb +5 -5
  63. data/lib/contrast/agent/rewriter.rb +3 -3
  64. data/lib/contrast/agent/scope.rb +81 -55
  65. data/lib/contrast/agent/static_analysis.rb +14 -8
  66. data/lib/contrast/agent/tracepoint_hook.rb +1 -1
  67. data/lib/contrast/agent/version.rb +1 -1
  68. data/lib/contrast/api/decorators.rb +3 -0
  69. data/lib/contrast/api/decorators/address.rb +0 -1
  70. data/lib/contrast/api/decorators/application_update.rb +1 -1
  71. data/lib/contrast/api/decorators/library.rb +54 -0
  72. data/lib/contrast/api/decorators/library_usage_update.rb +31 -0
  73. data/lib/contrast/api/decorators/trace_event.rb +19 -31
  74. data/lib/contrast/api/decorators/trace_event_object.rb +11 -3
  75. data/lib/contrast/api/decorators/trace_event_signature.rb +27 -5
  76. data/lib/contrast/api/decorators/user_input.rb +2 -1
  77. data/lib/contrast/common_agent_configuration.rb +2 -1
  78. data/lib/contrast/components/agent.rb +6 -5
  79. data/lib/contrast/components/assess.rb +36 -0
  80. data/lib/contrast/components/config.rb +29 -37
  81. data/lib/contrast/components/interface.rb +30 -6
  82. data/lib/contrast/components/inventory.rb +6 -1
  83. data/lib/contrast/components/scope.rb +72 -6
  84. data/lib/contrast/components/settings.rb +6 -3
  85. data/lib/contrast/config/assess_configuration.rb +2 -1
  86. data/lib/contrast/config/inventory_configuration.rb +2 -2
  87. data/lib/contrast/extension/assess/array.rb +2 -3
  88. data/lib/contrast/extension/assess/erb.rb +1 -3
  89. data/lib/contrast/extension/assess/exec_trigger.rb +1 -4
  90. data/lib/contrast/extension/assess/fiber.rb +2 -3
  91. data/lib/contrast/extension/assess/hash.rb +4 -2
  92. data/lib/contrast/extension/assess/kernel.rb +1 -2
  93. data/lib/contrast/extension/assess/marshal.rb +34 -26
  94. data/lib/contrast/extension/assess/regexp.rb +3 -8
  95. data/lib/contrast/extension/assess/string.rb +1 -2
  96. data/lib/contrast/framework/base_support.rb +51 -53
  97. data/lib/contrast/framework/manager.rb +3 -2
  98. data/lib/contrast/framework/rack/patch/session_cookie.rb +2 -2
  99. data/lib/contrast/framework/rack/support.rb +2 -1
  100. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +1 -1
  101. data/lib/contrast/framework/rails/patch/assess_configuration.rb +1 -1
  102. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +1 -1
  103. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +1 -1
  104. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +1 -1
  105. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +1 -1
  106. data/lib/contrast/framework/rails/support.rb +5 -1
  107. data/lib/contrast/framework/sinatra/support.rb +3 -2
  108. data/lib/contrast/logger/application.rb +1 -4
  109. data/lib/contrast/utils/duck_utils.rb +1 -1
  110. data/lib/contrast/utils/heap_dump_util.rb +1 -1
  111. data/lib/contrast/utils/inventory_util.rb +0 -7
  112. data/lib/contrast/utils/object_share.rb +3 -3
  113. data/lib/contrast/utils/preflight_util.rb +1 -1
  114. data/lib/contrast/utils/prevent_serialization.rb +1 -1
  115. data/lib/contrast/utils/resource_loader.rb +1 -1
  116. data/lib/contrast/utils/sha256_builder.rb +2 -14
  117. data/lib/contrast/utils/string_utils.rb +1 -1
  118. data/lib/contrast/utils/tag_util.rb +9 -13
  119. data/resources/assess/policy.json +9 -9
  120. data/resources/deadzone/policy.json +150 -0
  121. data/resources/protect/policy.json +12 -0
  122. data/ruby-agent.gemspec +10 -6
  123. data/service_executables/VERSION +1 -1
  124. data/service_executables/linux/contrast-service +0 -0
  125. data/service_executables/mac/contrast-service +0 -0
  126. metadata +76 -27
  127. data/lib/contrast/utils/boolean_util.rb +0 -30
  128. data/lib/contrast/utils/gemfile_reader.rb +0 -193
@@ -79,6 +79,10 @@ module Contrast
79
79
  # range : 5-10
80
80
  # result : 0-05
81
81
  #
82
+ # Note that we disable Lint/DuplicateBranch in this branch in order
83
+ # list out all tag range cases in the proper order to make this
84
+ # easier to understand
85
+ #
82
86
  # @param range [Range] the span to check, inclusive to exclusive
83
87
  # @return [Hash{String => Contrast::Agent::Assess::Tag}] the hash of
84
88
  # key to tags
@@ -101,10 +105,10 @@ module Contrast
101
105
  finish = range.size - start
102
106
  add << Contrast::Agent::Assess::Tag.new(tag.label, finish, start)
103
107
  # the tag spans the requested range.
104
- when Contrast::Agent::Assess::Tag::WITHOUT
108
+ when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
105
109
  add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
106
110
  # part of the tag is being selected
107
- when Contrast::Agent::Assess::Tag::HIGH_SPAN
111
+ when Contrast::Agent::Assess::Tag::HIGH_SPAN # rubocop:disable Lint/DuplicateBranch
108
112
  add << Contrast::Agent::Assess::Tag.new(tag.label, range.size)
109
113
  end
110
114
  end
@@ -173,14 +177,6 @@ module Contrast
173
177
  tags.fetch(label, nil) if tracked?
174
178
  end
175
179
 
176
- # Convert the tags of this object into the TraceTaintRange required
177
- # to be sent to the service
178
- #
179
- # @return [Array<Contrast::Api::Dtm::TraceTaintRange>]
180
- def tags_to_dtm
181
- Contrast::Api::Dtm::TraceTaintRange.build_for_event(tags)
182
- end
183
-
184
180
  # Remove all tags within the given ranges.
185
181
  # This does not delete an entire tag if part of that tag is
186
182
  # outside this range, meaning we may reduce sizes of tags
@@ -301,9 +297,19 @@ module Contrast
301
297
  end
302
298
  end
303
299
 
304
- # Shift the tag ranges covering the given range
305
- # We assume this is for a insertion, meaning we
306
- # have to move tags to the right
300
+ # Shift the tag ranges covering the given range to account for new
301
+ # data being added. We assume this is for a insertion, meaning we
302
+ # have to move tags out of the range and to the right. For example,
303
+ # given current tags: 0-15
304
+ # when we insert a range: 5-10
305
+ # then the result is: 0-5, 10-20
306
+ #
307
+ # Note that we disable Lint/DuplicateBranch in this branch in order
308
+ # list out all tag range cases in the proper order to make this
309
+ # easier to understand
310
+ #
311
+ # @param range [Range] the range of new information that's been
312
+ # inserted
307
313
  def shift_tags_for_insertion range
308
314
  return unless tracked?
309
315
 
@@ -325,13 +331,13 @@ module Contrast
325
331
  when Contrast::Agent::Assess::Tag::WITHIN
326
332
  tag.shift(length)
327
333
  # the tag spans the range. leave the part below alone
328
- when Contrast::Agent::Assess::Tag::WITHOUT
334
+ when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
329
335
  new_tag = tag.clone
330
336
  new_tag.update_start(range.begin)
331
337
  new_tag.shift(length)
332
338
  add << new_tag
333
339
  tag.update_end(range.begin)
334
- when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE
340
+ when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE # rubocop:disable Lint/DuplicateBranch
335
341
  tag.shift(length)
336
342
  end
337
343
  end
@@ -85,10 +85,11 @@ module Contrast
85
85
  #
86
86
  # @param trace_point [TracePoint] the TracePoint event created on
87
87
  # the :end of a Module being loaded
88
- def parse trace_point
88
+ # @param ast [RubyVM::AbstractSyntaxTree::Node] the abstract syntax
89
+ # tree of the Module defined in the TracePoint end event
90
+ def parse trace_point, ast
89
91
  return if disabled?
90
92
 
91
- ast = RubyVM::AbstractSyntaxTree.parse_file(trace_point.path)
92
93
  parse_ast(trace_point.self, ast)
93
94
  rescue StandardError => e
94
95
  logger.error('Unable to parse AST for hardcoded keys', e, module: trace_point.self)
@@ -33,7 +33,7 @@ module Contrast
33
33
  # (2) regexp must evaluate against user input
34
34
  return unless trigger_node.violated?(string)
35
35
 
36
- Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(context, trigger_node, source, object, ret, args)
36
+ Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(context, trigger_node, source, object, ret, *args)
37
37
  end
38
38
 
39
39
  protected
@@ -14,7 +14,20 @@ module Contrast
14
14
  PROPERTIES_HASH = Contrast::Agent::Assess::Finalizers::Hash.new
15
15
 
16
16
  class << self
17
+ # Retrieve the properties of the given Object, iff they exist.
18
+ #
19
+ # @param source [Object] the thing for which to look up properties.
20
+ # @return [Contrast::Agent::Assess::Properties, nil]
17
21
  def properties source
22
+ PROPERTIES_HASH[source]
23
+ end
24
+
25
+ # Retrieve the properties of the given Object, returning a new
26
+ # instance if one does not already exist and the source is trackable.
27
+ #
28
+ # @param source [Object] the thing for which to look up properties.
29
+ # @return [Contrast::Agent::Assess::Properties, nil]
30
+ def properties! source
18
31
  return unless trackable?(source)
19
32
 
20
33
  PROPERTIES_HASH[source] ||= Contrast::Agent::Assess::Properties.new
@@ -33,30 +46,15 @@ module Contrast
33
46
  end
34
47
 
35
48
  # Copy the properties from one object to the next, assuming the
36
- # target does not already have its own properties.
49
+ # target does not already have its own properties. This should only
50
+ # ever be called when building PreShift for those objects sources
51
+ # which are already tracked.
37
52
  #
38
53
  # @param source [Object] the instance from which to copy properties
39
54
  # @param target [Object] the instance to which to copy properties
40
55
  def copy source, target
41
56
  PROPERTIES_HASH[target] ||= properties(source).dup
42
57
  end
43
-
44
- # Duplicate the given object, returning the duplicate after copying
45
- # the properties of the original and storing them as the properties
46
- # of the duplicate.
47
- #
48
- # @param source [Object] the thing to duplicate
49
- # @return [Object] the duplicate of the original, or the original if
50
- # it does not respond to duplication
51
- def duplicate source
52
- return source unless source
53
-
54
- duplicate = source.dup
55
- PROPERTIES_HASH[duplicate] ||= PROPERTIES_HASH[source].dup
56
- duplicate
57
- rescue StandardError
58
- source
59
- end
60
58
  end
61
59
  end
62
60
  end
@@ -19,6 +19,13 @@ module Contrast
19
19
  def feature
20
20
  'Deadzone'
21
21
  end
22
+
23
+ # Deadzone means place the code inside of the contrast scope so that
24
+ # none of our code executes. As such, all DeadZone nodes have that as
25
+ # their method scope
26
+ def method_scope
27
+ :contrast
28
+ end
22
29
  end
23
30
  end
24
31
  end
@@ -0,0 +1,15 @@
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 Agent
6
+ # Namespace used for inventory behavior
7
+ module Inventory
8
+ end
9
+ end
10
+ end
11
+
12
+ require 'contrast/agent/inventory/dependencies'
13
+ require 'contrast/agent/inventory/gemfile_digest_cache'
14
+ require 'contrast/agent/inventory/dependency_usage_analysis'
15
+ require 'contrast/agent/inventory/dependency_analysis'
@@ -0,0 +1,50 @@
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 Agent
6
+ module Inventory
7
+ # this module is included in classes that need access to the applications dependencies
8
+ module Dependencies
9
+ CONTRAST_AGENT = 'contrast-agent'
10
+
11
+ # the #clone is necessary here, as a require in another thread could
12
+ # potentially result in adding a key to the loaded_specs hash during
13
+ # iteration. (as in RUBY-330)
14
+ # this takes care of filtering out contrast-only dependencies
15
+ def loaded_specs
16
+ specs = Gem.loaded_specs.clone
17
+ specs.delete_if { |name, _v| contrast?(name) }
18
+ end
19
+
20
+ private
21
+
22
+ def contrast_gems
23
+ @_contrast_gems ||= find_contrast_gems
24
+ end
25
+
26
+ def contrast? name
27
+ contrast_gems.include?(name)
28
+ end
29
+
30
+ # Go through all dependents, given as a pair from the DependencyList: `dependency`
31
+ # is the dependency itself, filled with all its specs. `dependents` is the array of reverse
32
+ # dependencies for the aforementioned dependency. If the dependency is also in contrast_dep_set,
33
+ # then contrast depends on it. If its array of dependents is 1, then contrast is the
34
+ # only dependency in that list. Since only contrast depends on it, we should ignore it.
35
+ def find_contrast_gems
36
+ ignore = Set.new([CONTRAST_AGENT])
37
+ contrast_specs = Gem::DependencyList.from_specs.specs.find do |dependency|
38
+ dependency.name == CONTRAST_AGENT
39
+ end
40
+ contrast_dep_set = contrast_specs.dependencies.map(&:name).to_set
41
+
42
+ Gem::DependencyList.from_specs.spec_predecessors.each_pair do |dependency, dependents|
43
+ ignore.add(dependency.name) if contrast_dep_set.include?(dependency.name) && dependents.length == 1
44
+ end
45
+ ignore
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,37 @@
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
+ require 'contrast/agent/inventory/dependencies'
5
+ require 'contrast/components/interface'
6
+ require 'contrast/utils/object_share'
7
+
8
+ module Contrast
9
+ module Agent
10
+ module Inventory
11
+ # Used to collect dependencies of the application for reporting
12
+ class DependencyAnalysis
13
+ include Singleton
14
+ include Contrast::Agent::Inventory::Dependencies
15
+ include Contrast::Components::Interface
16
+
17
+ access_component :analysis
18
+
19
+ # Report the dependencies of this application
20
+ #
21
+ # @return [Array<Contrast::Api::Dtm::Library>] protobuf form of the
22
+ # Gem::Specification that have been loaded for this application.
23
+ def library_pb_list
24
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless INVENTORY.enabled?
25
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless INVENTORY.analyze_libraries?
26
+
27
+ loaded_specs.each_with_object([]) do |(_name, spec), reported_lib_list|
28
+ next unless spec
29
+ next unless (digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec))
30
+
31
+ reported_lib_list << Contrast::Api::Dtm::Library.build(digest, spec)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,104 @@
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
+ require 'contrast/agent/inventory/gemfile_digest_cache'
5
+ require 'contrast/agent/inventory/dependencies'
6
+ require 'contrast/components/interface'
7
+ require 'contrast/utils/object_share'
8
+
9
+ module Contrast
10
+ module Agent
11
+ module Inventory
12
+ # Used to analyze class usage for reporting
13
+ class DependencyUsageAnalysis
14
+ include Singleton
15
+ include Contrast::Components::Interface
16
+ include Contrast::Agent::Inventory::Dependencies
17
+
18
+ access_component :analysis, :config, :logging
19
+
20
+ def initialize
21
+ return unless enabled?
22
+
23
+ @gemdigest_cache = Contrast::Agent::Inventory::GemfileDigestCache.new
24
+ end
25
+
26
+ # This method is invoked once, along with the rest of our catchup code
27
+ # to report libraries and their associated files that have already been loaded pre-contrast
28
+ def catchup
29
+ return unless enabled?
30
+
31
+ loaded_specs.each do |_name, spec|
32
+ # Get a digest of the Gem file itself
33
+ next unless (digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec))
34
+
35
+ @gemdigest_cache.use_cache(digest) do |existing_files|
36
+ loaded_files_from_gem = $LOADED_FEATURES.select { |f| f.start_with?(spec.full_gem_path) }
37
+ loaded_files_from_gem.each do |file_path|
38
+ logger.trace('Recording loaded file for inventory analysis', line: file_path)
39
+ existing_files << adjust_path_for_reporting(file_path, spec)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ # This method is invoked once per TracePoint :end - to map a specific
46
+ # file being required to the gem it belongs to
47
+ #
48
+ # @param path [String] the result of TracePoint#path from the :end
49
+ # event in which the Module was defined
50
+ def associate_file path
51
+ return unless enabled?
52
+
53
+ spec_lookup_path = adjust_path_for_spec_lookup(path)
54
+
55
+ spec = Gem::Specification.find_by_path(spec_lookup_path)
56
+ unless spec
57
+ logger.debug('Unable to resolve gem spec for path', path: path)
58
+ return
59
+ end
60
+
61
+ digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec)
62
+ unless digest
63
+ logger.debug('Unable to resolve digest for gem spec', spec: spec.to_s)
64
+ return
65
+ end
66
+ report_path = adjust_path_for_reporting(path, spec)
67
+ @gemdigest_cache.get(digest) << report_path
68
+ rescue StandardError => e
69
+ logger.error('Unable to inventory file path', e, path: path)
70
+ end
71
+
72
+ # Populate the library_usages filed of the Activity message using the
73
+ # data stored in the @gemdigest_cache
74
+ #
75
+ # @param activity [Contrast::Api::Dtm::Activity] the message to which
76
+ # to append the usage data
77
+ def generate_library_usage activity
78
+ return unless enabled?
79
+ return if @gemdigest_cache.empty?
80
+
81
+ @gemdigest_cache.generate_usage_data(activity)
82
+ end
83
+
84
+ private
85
+
86
+ def adjust_path_for_spec_lookup path
87
+ idx = path.index('/lib/')
88
+ path = path[(idx + 4)..-1] if idx
89
+ path
90
+ end
91
+
92
+ def adjust_path_for_reporting path, gem_spec
93
+ path.delete_prefix(gem_spec.full_gem_path)
94
+ end
95
+
96
+ # We only use this if inventory and library analysis are enabled
97
+ def enabled?
98
+ @_enabled = INVENTORY.enabled? && INVENTORY.analyze_libraries? if @_enabled.nil?
99
+ @_enabled
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,38 @@
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 Agent
6
+ module Inventory
7
+ # Keeps a map of gem digest to files for reporting file usage
8
+ class GemfileDigestCache
9
+ extend Forwardable
10
+ def_delegator :@gem_spec_digest_to_files,
11
+ :empty?
12
+
13
+ def initialize
14
+ @gem_spec_digest_to_files = {}
15
+ end
16
+
17
+ def generate_usage_data activity
18
+ return unless activity
19
+
20
+ @gem_spec_digest_to_files.each_pair do |digest, files|
21
+ usage = Contrast::Api::Dtm::LibraryUsageUpdate.build(digest, files)
22
+ activity.library_usages[usage.hash_code] = usage if activity
23
+ end
24
+ @gem_spec_digest_to_files.clear
25
+ end
26
+
27
+ def use_cache digest
28
+ yield get(digest)
29
+ end
30
+
31
+ def get digest
32
+ @gem_spec_digest_to_files[digest] = Set.new unless @gem_spec_digest_to_files.key?(digest)
33
+ @gem_spec_digest_to_files[digest]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -79,7 +79,7 @@ module Contrast
79
79
  Contrast::Utils::HeapDumpUtil.run
80
80
 
81
81
  if AGENT.enabled?
82
- Contrast::Agent::StaticAnalysis.catchup
82
+ handle_first_request
83
83
  call_with_agent(env)
84
84
  else
85
85
  app.call(env)
@@ -91,17 +91,47 @@ module Contrast
91
91
  def setup_agent
92
92
  SETTINGS.reset_state
93
93
 
94
+ inform_deprecations
95
+
94
96
  if CONFIG.invalid?
95
97
  AGENT.disable!
96
98
  logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
97
- elsif CONFIG.disabled?
98
- AGENT.disable!
99
+ elsif AGENT.disabled?
99
100
  logger.warn('Contrast disabled by configuration. Continuing without instrumentation.')
100
101
  else
101
102
  AGENT.enable!
102
103
  end
103
104
  end
104
105
 
106
+ # Some things have to wait until first request to happen, either because
107
+ # resolution is not complete or because the framework will preload
108
+ # classes, which confuses some of our instrumentation.
109
+ def handle_first_request
110
+ @_handle_first_request ||= begin
111
+ Contrast::Agent::StaticAnalysis.catchup
112
+ force_patching
113
+ true
114
+ end
115
+ end
116
+
117
+ # TODO: RUBY-1090 remove this method and those it calls.
118
+ #
119
+ # These modules are auto-loaded by Rails, meaning they are defined at
120
+ # startup, but that they don't actually exist. We account for this in
121
+ # most cases by using the ClassUtil.truly_defined? method, but it appears
122
+ # to fail for these Modules. In the short term, we can forcing a re-patch
123
+ # so that their dead zones apply.
124
+ def force_patching
125
+ force_patch(ActionDispatch::FileHandler) if defined?(ActionDispatch::FileHandler)
126
+ force_patch(ActionDispatch::Http::MimeNegotiation) if defined?(ActionDispatch::Http::MimeNegotiation)
127
+ force_patch(ActionView::Template) if defined?(ActionView::Template)
128
+ end
129
+
130
+ def force_patch mod
131
+ data = Contrast::Agent::ModuleData.new(mod)
132
+ Contrast::Agent::Patching::Policy::Patcher.send(:patch_into_module, data, true)
133
+ end
134
+
105
135
  # This is where we process each request we intercept as a middleware. We make the request context
106
136
  # available globally so that it can be accessed from anywhere. A RequestHandler object is made
107
137
  # for each request, which handles prefilter and postfilter operations.
@@ -174,6 +204,24 @@ module Contrast
174
204
  raise exception
175
205
  end
176
206
  end
207
+
208
+ # As we deprecate support to prepare to remove dead code, we need to
209
+ # inform our users still relying on the now deprecated and soon to be
210
+ # removed functionality. This method handles doing that by leveraging the
211
+ # standard Kernel#warn approach
212
+ def inform_deprecations
213
+ # Ruby 2.5 is currently in security maintenance, meaning int is only
214
+ # receiving updates for security issues. It will move to eol on 31
215
+ # March 2021. As such, we can remove support for it in Q2. We'll begin
216
+ # the deprecation warnings now so that customers have time to reach out
217
+ # if they'll be impacted.
218
+ # TODO: RUBY-715 remove this part of the method, leaving it empty if
219
+ # there are no other deprecations, when we drop 2.5 support.
220
+ return unless RUBY_VERSION < '2.6.0'
221
+
222
+ Kernel.warn('[Contrast Security] [DEPRECATION] Support for Ruby 2.5 will be removed in April 2021. '\
223
+ 'Please contact Customer Support prior if you require continued support.')
224
+ end
177
225
  end
178
226
  end
179
227
  end