contrast-agent 6.2.0 → 6.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__assess_basic_object/cs__assess_basic_object.c +7 -5
  3. data/ext/cs__assess_kernel/cs__assess_kernel.c +14 -3
  4. data/ext/cs__assess_kernel/cs__assess_kernel.h +2 -0
  5. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +10 -3
  6. data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +2 -1
  7. data/ext/cs__assess_regexp/cs__assess_regexp.c +9 -7
  8. data/ext/{cs__assess_string_interpolation26/cs__assess_string_interpolation26.c → cs__assess_string_interpolation/cs__assess_string_interpolation.c} +14 -3
  9. data/ext/{cs__assess_string_interpolation26/cs__assess_string_interpolation26.h → cs__assess_string_interpolation/cs__assess_string_interpolation.h} +1 -1
  10. data/ext/{cs__assess_string_interpolation26 → cs__assess_string_interpolation}/extconf.rb +0 -0
  11. data/ext/cs__common/cs__common.c +5 -4
  12. data/ext/cs__contrast_patch/cs__contrast_patch.c +3 -10
  13. data/lib/contrast/agent/assess/events/source_event.rb +16 -12
  14. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -0
  15. data/lib/contrast/agent/assess/policy/propagation_method.rb +3 -39
  16. data/lib/contrast/agent/assess/policy/propagation_node.rb +8 -0
  17. data/lib/contrast/agent/assess/policy/propagator/base.rb +2 -0
  18. data/lib/contrast/agent/assess/policy/source_method.rb +2 -47
  19. data/lib/contrast/agent/assess/policy/source_node.rb +1 -0
  20. data/lib/contrast/agent/assess/policy/trigger_node.rb +8 -0
  21. data/lib/contrast/agent/assess/property/evented.rb +4 -18
  22. data/lib/contrast/agent/assess/tag.rb +19 -0
  23. data/lib/contrast/agent/at_exit_hook.rb +8 -8
  24. data/lib/contrast/agent/inventory/database_config.rb +6 -3
  25. data/lib/contrast/agent/inventory/dependency_analysis.rb +3 -2
  26. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +10 -10
  27. data/lib/contrast/agent/middleware.rb +4 -0
  28. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +27 -2
  29. data/lib/contrast/agent/patching/policy/policy.rb +5 -0
  30. data/lib/contrast/agent/patching/policy/policy_node.rb +6 -0
  31. data/lib/contrast/agent/patching/policy/trigger_node.rb +3 -0
  32. data/lib/contrast/agent/protect/policy/applies_deserialization_rule.rb +3 -4
  33. data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +1 -0
  34. data/lib/contrast/agent/protect/policy/rule_applicator.rb +2 -2
  35. data/lib/contrast/agent/protect/rule/base.rb +1 -0
  36. data/lib/contrast/agent/protect/rule/no_sqli.rb +2 -0
  37. data/lib/contrast/agent/reporting/reporter.rb +32 -7
  38. data/lib/contrast/agent/reporting/reporter_heartbeat.rb +21 -15
  39. data/lib/contrast/agent/reporting/reporting_events/application_update.rb +5 -24
  40. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +8 -1
  41. data/lib/contrast/agent/reporting/reporting_events/discovered_route.rb +8 -1
  42. data/lib/contrast/agent/reporting/reporting_events/finding.rb +7 -1
  43. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +10 -1
  44. data/lib/contrast/agent/reporting/reporting_events/finding_event_object.rb +11 -1
  45. data/lib/contrast/agent/reporting/reporting_events/finding_event_parent_object.rb +11 -1
  46. data/lib/contrast/agent/reporting/reporting_events/finding_event_property.rb +12 -1
  47. data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +10 -1
  48. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +11 -1
  49. data/lib/contrast/agent/reporting/reporting_events/finding_event_stack.rb +11 -1
  50. data/lib/contrast/agent/reporting/reporting_events/finding_event_taint_range.rb +11 -1
  51. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +11 -1
  52. data/lib/contrast/agent/reporting/reporting_events/library_discovery.rb +29 -32
  53. data/lib/contrast/agent/reporting/reporting_events/library_usage_observation.rb +13 -1
  54. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +11 -8
  55. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +12 -5
  56. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +8 -1
  57. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +9 -1
  58. data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +10 -1
  59. data/lib/contrast/agent/reporting/reporting_events/route_discovery_observation.rb +11 -4
  60. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +0 -8
  61. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -4
  62. data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -22
  63. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -3
  64. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -11
  65. data/lib/contrast/agent/request.rb +5 -7
  66. data/lib/contrast/agent/request_context.rb +8 -17
  67. data/lib/contrast/agent/request_context_extend.rb +8 -9
  68. data/lib/contrast/agent/request_handler.rb +9 -38
  69. data/lib/contrast/agent/rule_set.rb +4 -0
  70. data/lib/contrast/agent/service_heartbeat.rb +1 -1
  71. data/lib/contrast/agent/static_analysis.rb +6 -11
  72. data/lib/contrast/agent/telemetry/base.rb +35 -35
  73. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_base.rb +2 -0
  74. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +2 -0
  75. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message.rb +5 -2
  76. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_message_exception.rb +3 -0
  77. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_stack_frame.rb +3 -0
  78. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions.rb +0 -1
  79. data/lib/contrast/agent/thread_watcher.rb +1 -4
  80. data/lib/contrast/agent/version.rb +1 -1
  81. data/lib/contrast/api/communication/socket.rb +1 -0
  82. data/lib/contrast/api/decorators/message.rb +0 -6
  83. data/lib/contrast/api/decorators.rb +0 -2
  84. data/lib/contrast/components/assess.rb +0 -6
  85. data/lib/contrast/components/config.rb +18 -2
  86. data/lib/contrast/config/base_configuration.rb +0 -13
  87. data/lib/contrast/config/root_configuration.rb +1 -0
  88. data/lib/contrast/config/ruby_configuration.rb +2 -9
  89. data/lib/contrast/configuration.rb +0 -2
  90. data/lib/contrast/extension/assess/eval_trigger.rb +0 -4
  91. data/lib/contrast/extension/assess/hash.rb +3 -2
  92. data/lib/contrast/extension/assess/kernel.rb +22 -0
  93. data/lib/contrast/extension/assess/marshal.rb +16 -0
  94. data/lib/contrast/extension/assess/string.rb +21 -20
  95. data/lib/contrast/framework/base_support.rb +8 -0
  96. data/lib/contrast/framework/manager.rb +6 -20
  97. data/lib/contrast/framework/manager_extend.rb +0 -1
  98. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +11 -16
  99. data/lib/contrast/logger/aliased_logging.rb +2 -0
  100. data/lib/contrast/utils/assess/source_method_utils.rb +0 -9
  101. data/lib/contrast/utils/lru_cache.rb +3 -0
  102. data/lib/contrast/utils/middleware_utils.rb +2 -0
  103. data/lib/contrast/utils/telemetry_client.rb +7 -7
  104. data/resources/assess/policy.json +2 -11
  105. data/ruby-agent.gemspec +1 -1
  106. metadata +22 -20
  107. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exceptions_report.rb +0 -30
  108. data/lib/contrast/api/decorators/application_update.rb +0 -44
  109. data/lib/contrast/api/decorators/library.rb +0 -56
  110. data/lib/contrast/framework/platform_version.rb +0 -22
@@ -1,12 +1,16 @@
1
1
  # Copyright (c) 2022 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/logger'
5
+
4
6
  module Contrast
5
7
  module Agent
6
8
  module Assess
7
9
  # A Tag represents a range in a given piece of data. It is used by the
8
10
  # Agent to determine if a vulnerable dataflow has occurred.
9
11
  class Tag
12
+ include Contrast::Components::Logger::InstanceMethods
13
+
10
14
  attr_reader :label, # the label of this tag
11
15
  :length, # length of tagged text within string
12
16
  :start_idx, # start of range
@@ -35,6 +39,8 @@ module Contrast
35
39
  def initialize label, length, start_idx = 0
36
40
  @label = label
37
41
  update_range(start_idx, start_idx + length)
42
+ rescue ArgumentError => e
43
+ logger.error('Range update for Tag failed with: ', e)
38
44
  end
39
45
 
40
46
  # Return true if the tag covers the given position in the string
@@ -74,22 +80,32 @@ module Contrast
74
80
 
75
81
  def shift idx
76
82
  update_range(@start_idx + idx, @end_idx + idx)
83
+ rescue ArgumentError => e
84
+ logger.error('Range update for Tag failed with: ', e)
77
85
  end
78
86
 
79
87
  def shift_end idx
80
88
  update_range(@start_idx, @end_idx + idx)
89
+ rescue ArgumentError => e
90
+ logger.error('Range update for Tag failed with: ', e)
81
91
  end
82
92
 
83
93
  def update_start start_idx
84
94
  update_range(start_idx, @end_idx)
95
+ rescue ArgumentError => e
96
+ logger.error('Range update for Tag failed with: ', e)
85
97
  end
86
98
 
87
99
  def update_end end_idx
88
100
  update_range(@start_idx, end_idx)
101
+ rescue ArgumentError => e
102
+ logger.error('Range update for Tag failed with: ', e)
89
103
  end
90
104
 
91
105
  def repurpose start_idx, end_idx
92
106
  update_range(start_idx, end_idx)
107
+ rescue ArgumentError => e
108
+ logger.error('Range update for Tag failed with: ', e)
93
109
  end
94
110
 
95
111
  # Given a tag, merge its ranges with this one
@@ -104,6 +120,8 @@ module Contrast
104
120
  start = other.start_idx < @start_idx ? other.start_idx : @start_idx
105
121
  finish = other.end_idx > @end_idx ? other.end_idx : @end_idx
106
122
  update_range(start, finish)
123
+ rescue ArgumentError => e
124
+ logger.error('Range update for Tag failed with: ', e)
107
125
  end
108
126
 
109
127
  # Modification to tracked String can change the position and length of the tracked tag
@@ -160,6 +178,7 @@ module Contrast
160
178
 
161
179
  private
162
180
 
181
+ # @raise[ArgumentError] raises if start_idx is negative or the end_idx is smaller than the start_idx
163
182
  def update_range start_idx, end_idx
164
183
  raise(ArgumentError, ERROR_NEGATIVE_START) if start_idx.negative?
165
184
  raise(ArgumentError, ERROR_END_BEFORE_START) if end_idx < start_idx
@@ -31,16 +31,16 @@ module Contrast
31
31
  context = Contrast::Agent::REQUEST_TRACKER.current
32
32
  return unless context
33
33
 
34
- Contrast::Agent.reporter.send_event_immediately(context.observed_library_usage)
34
+ [
35
+ Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage,
36
+ context.observed_route
37
+ ].compact.each do |event|
38
+ Contrast::Agent.reporter&.send_event_immediately(event)
39
+ end
35
40
 
36
41
  if Contrast::Agent::Reporter.enabled?
37
- [
38
- context.new_observed_route,
39
- Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.server_activity),
40
- Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.activity)
41
- ].each do |event|
42
- Contrast::Agent.reporter&.send_event_immediately(event)
43
- end
42
+ event = Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.activity)
43
+ Contrast::Agent.reporter&.send_event_immediately(event)
44
44
  else
45
45
  Contrast::Agent.messaging_queue&.send_event_immediately(context.activity)
46
46
  end
@@ -1,6 +1,7 @@
1
1
  # Copyright (c) 2022 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/agent/reporting/reporting_events/architecture_component'
4
5
  require 'contrast/api/decorators/architecture_component'
5
6
  require 'contrast/components/logger'
6
7
  require 'contrast/utils/object_share'
@@ -25,10 +26,11 @@ module Contrast
25
26
 
26
27
  class << self
27
28
  # Append the available database connection information to the message being sent to TeamServer. This message
28
- # may be a Contrast::Api::Dtm::Activity or Contrast::Api::Dtm::ApplicationUpdate. Both report the same
29
+ # may be a Contrast::Api::Dtm::Activity or Contrast::Agent::Reporting::ApplicationUpdate.
30
+ # Both report the same
29
31
  # Contrast::Api::Dtm::ArchitectureComponent, but have different names for their fields.
30
32
  #
31
- # @param activity_or_update [Contrast::Api::Dtm::Activity, Contrast::Api::Dtm::ApplicationUpdate]
33
+ # @param activity_or_update [Contrast::Api::Dtm::Activity, Contrast::Agent::Reporting::ApplicationUpdate]
32
34
  # @param hash_or_str [Hash, String] the database connection information
33
35
  def append_db_config activity_or_update, hash_or_str = active_record_config
34
36
  arr = build_from_db_config(hash_or_str)
@@ -40,7 +42,8 @@ module Contrast
40
42
  if activity_or_update.is_a?(Contrast::Api::Dtm::Activity)
41
43
  activity_or_update.architectures << a
42
44
  else
43
- activity_or_update.components << a
45
+ converted_comp = Contrast::Agent::Reporting::ArchitectureComponent.convert(a)
46
+ activity_or_update.components << converted_comp
44
47
  end
45
48
  end
46
49
  rescue StandardError => e
@@ -1,6 +1,7 @@
1
1
  # Copyright (c) 2022 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/agent/reporting/reporting_events/library_discovery'
4
5
  require 'contrast/agent/inventory/dependencies'
5
6
  require 'contrast/utils/object_share'
6
7
 
@@ -14,7 +15,7 @@ module Contrast
14
15
 
15
16
  # Report the dependencies of this application
16
17
  #
17
- # @return [Array<Contrast::Api::Dtm::Library>] protobuf form of the
18
+ # @return [Array<Contrast::Agent::Reporting::LibraryDiscovery>] direct report form of
18
19
  # Gem::Specification that have been loaded for this application.
19
20
  def library_pb_list
20
21
  return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless ::Contrast::INVENTORY.enabled?
@@ -24,7 +25,7 @@ module Contrast
24
25
  next unless spec
25
26
  next unless (digest = Contrast::Utils::Sha256Builder.instance.build_from_spec(spec))
26
27
 
27
- reported_lib_list << Contrast::Api::Dtm::Library.build(digest, spec)
28
+ reported_lib_list << Contrast::Agent::Reporting::LibraryDiscovery.new(digest, spec)
28
29
  end
29
30
  end
30
31
  end
@@ -52,7 +52,6 @@ module Contrast
52
52
  return unless enabled?
53
53
 
54
54
  spec_lookup_path = adjust_path_for_spec_lookup(path)
55
-
56
55
  spec = Gem::Specification.find_by_path(spec_lookup_path)
57
56
  unless spec
58
57
  logger.debug('Unable to resolve gem spec for path', path: path)
@@ -70,15 +69,13 @@ module Contrast
70
69
  logger.error('Unable to inventory file path', e, path: path)
71
70
  end
72
71
 
73
- # Populate the library_usages field of the Activity message using the data stored in the @gemdigest_cache.
72
+ # Populate the library_usages field of the Activity message using the data stored in the @gemdigest_cache. If
73
+ # no libraries had files loaded, or inventory analysis is disabled, return nil instead.
74
74
  #
75
- # TODO: RUBY-1355
76
- # TODO: RUBY-1438 -- change to just use EventMessage
77
- # @param observed_library_usage [Contrast::Agent::Reporting::ObservedLibraryUsage] the message to
78
- # which to append the usage data
79
- def generate_library_usage observed_library_usage
75
+ # @return [Contrast::Agent::Reporting::ObservedLibraryUsage, nil]
76
+ def generate_library_usage
80
77
  return unless enabled?
81
- return unless observed_library_usage
78
+ return unless @gemdigest_cache.any?
82
79
 
83
80
  # Disconnect gemdigest_cache and replace it with an empty one; synch so new libs cannot be added between the
84
81
  # assignment and the replace
@@ -87,12 +84,15 @@ module Contrast
87
84
  @gemdigest_cache = Hash.new { |hash, key| hash[key] = Set.new }
88
85
  hold
89
86
  end
87
+
88
+ observed_library_usage = Contrast::Agent::Reporting::ObservedLibraryUsage.new
90
89
  gem_spec_digest_to_files.each_pair do |digest, files|
91
- usage = Contrast::Agent::Reporting::LibraryUsageObservation.new(digest, files)
92
- next if usage.names.empty?
90
+ next unless files.any?
93
91
 
92
+ usage = Contrast::Agent::Reporting::LibraryUsageObservation.new(digest, files)
94
93
  observed_library_usage.observations << usage
95
94
  end
95
+ observed_library_usage.observations.any? ? observed_library_usage : nil
96
96
  rescue StandardError => e
97
97
  logger.error('Unable to generate library usage.', e)
98
98
  end
@@ -142,6 +142,8 @@ module Contrast
142
142
  #
143
143
  # @param context [Contrast::Agent::RequestContext]
144
144
  # @param request_handler [Contrast::Agent::RequestHandler]
145
+ # @raise [StandardError] raises an error if the exception is security concern
146
+ # which is being triggered when there is a failure within the pre-call with the agent
145
147
  def pre_call_with_agent context, request_handler
146
148
  with_contrast_scope do
147
149
  context.service_extract_request
@@ -160,6 +162,8 @@ module Contrast
160
162
  # this Request
161
163
  # @param request_handler [Contrast::Agent::RequestHandler]
162
164
  # @param response [Array,Rack::Response]
165
+ # @raise [StandardError] raises an error if the exception is security concern
166
+ # which is being triggered when there is a failure within the post-call with the agent
163
167
  def post_call_with_agent context, env, request_handler, response
164
168
  with_contrast_scope do
165
169
  context.extract_after(response) # update context with final response information
@@ -5,6 +5,8 @@ require 'contrast/agent/patching/policy/after_load_patch'
5
5
  require 'contrast/components/logger'
6
6
  require 'contrast/framework/manager'
7
7
  require 'contrast/extension/extension'
8
+ require 'contrast/extension/assess/kernel'
9
+ require 'contrast/extension/assess/marshal'
8
10
 
9
11
  module Contrast
10
12
  module Agent
@@ -29,22 +31,31 @@ module Contrast
29
31
  # there are no require time side effects of loading our core
30
32
  # extensions.
31
33
  def apply_direct_patches!
32
- @_apply_direct_patches ||= begin # TODO: RUBY-1541 - put 'kernel' back
34
+ @_apply_direct_patches ||= begin
33
35
  paths = %w[
34
36
  array
35
37
  basic_object
36
38
  module
37
39
  fiber_track
38
40
  hash
41
+ kernel
39
42
  marshal_module
40
43
  regexp
41
44
  string
42
- string_interpolation26
45
+ string_interpolation
43
46
  ].cs__freeze
44
47
  paths.each do |p|
45
48
  path_part = "cs__assess_#{ p }"
46
49
  Contrast::Extension::Assess::InstrumentHelper.instrument("#{ path_part }/#{ path_part }")
47
50
  end
51
+ # apply Kernel#exec alias patch:
52
+ unless Contrast::Agent::Assess.cs__object_method_prepended?(Kernel, :exec, false)
53
+ apply_kernel_exec_alias_patch
54
+ end
55
+ # apply Marshal load alias patch:
56
+ unless Contrast::Agent::Assess.cs__object_method_prepended?(Marshal, :load, false)
57
+ apply_marshal_load_alias_patch
58
+ end
48
59
  true
49
60
  end
50
61
  end
@@ -96,6 +107,20 @@ module Contrast
96
107
  patch.instrument!
97
108
  after_load_patches.delete_if(&:applied?)
98
109
  end
110
+
111
+ # Applies the Kernel#exec alias patch
112
+ def apply_kernel_exec_alias_patch
113
+ Kernel.extend(Contrast::Extension::Assess::ContrastKernel)
114
+ Marshal.alias_method(:cs__kernel_exec, :exec)
115
+ Marshal.alias_method(:exec, :cs__kernel_exec)
116
+ end
117
+
118
+ # Applies the Marshal#load alias patch
119
+ def apply_marshal_load_alias_patch
120
+ Marshal.extend(Contrast::Extension::Assess::ContrastMarshal)
121
+ Marshal.alias_method(:cs__marshal_load, :load)
122
+ Marshal.alias_method(:load, :cs__marshal_load)
123
+ end
99
124
  end
100
125
  end
101
126
  end
@@ -35,16 +35,21 @@ module Contrast
35
35
  end
36
36
 
37
37
  # Indicates the folder in `resources` where this policy lives.
38
+ #
39
+ # @raise[NoMethodError] raises if any of the subclasses, extending this class is not implementing this method
38
40
  def self.policy_folder
39
41
  raise(NoMethodError, 'specify policy_folder for patching')
40
42
  end
41
43
 
42
44
  # Indicates is this feature has been disabled by the configuration, read at startup, and therefore can never
43
45
  # be enabled.
46
+ #
47
+ # @raise[NoMethodError] raises if any of the subclasses, extending this class is not implementing this method
44
48
  def disabled_globally?
45
49
  raise(NoMethodError, 'specify disabled_globally? conditions for patching')
46
50
  end
47
51
 
52
+ # @raise[NoMethodError] raises if any of the subclasses, extending this class is not implementing this method
48
53
  def node_type
49
54
  raise(NoMethodError, 'specify the concrete node type for this poilcy')
50
55
  end
@@ -47,10 +47,14 @@ module Contrast
47
47
  # Scope of the method parsed from our JSON policy.
48
48
  attr_reader :method_scope
49
49
 
50
+ # @raise [NoMethodError] This is being raised if any of the implementing subclasses does not have
51
+ # that method implemented, but is being called on.
50
52
  def node_class
51
53
  raise(NoMethodError, 'specify the type of the feature for which this node patches')
52
54
  end
53
55
 
56
+ # @raise [NoMethodError] This is being raised if any of the implementing subclasses does not have
57
+ # that method implemented, but is being called on.
54
58
  def feature
55
59
  raise(NoMethodError, 'specify the name of the feature for which this node patches')
56
60
  end
@@ -62,6 +66,8 @@ module Contrast
62
66
  # Don't let nodes be created that will be missing things we need
63
67
  # later on. Really, if they don't have these things, they couldn't have
64
68
  # done their jobs anyway.
69
+ #
70
+ # @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
65
71
  def validate
66
72
  unless class_name
67
73
  raise(ArgumentError, "#{ node_class } #{ id } did not have a proper class name. Unable to create.")
@@ -41,6 +41,7 @@ module Contrast
41
41
  NODE
42
42
  end
43
43
 
44
+ # @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
44
45
  def validate
45
46
  super
46
47
  unless applicator.public_methods(false).any?(applicator_method)
@@ -52,6 +53,7 @@ module Contrast
52
53
  validate_rule
53
54
  end
54
55
 
56
+ # @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
55
57
  def validate_properties
56
58
  if (required_properties & optional_properties).any?
57
59
  raise(ArgumentError,
@@ -66,6 +68,7 @@ module Contrast
66
68
  raise(ArgumentError, "#{ id } did not have a required property. Unable to create.")
67
69
  end
68
70
 
71
+ # @raise [ArgumentError] Validates if the created nodes have everything that we'll need now or later on.
69
72
  def validate_rule
70
73
  raise(ArgumentError, 'Unknown rule did not have a proper name. Unable to create.') unless rule_id
71
74
  raise(ArgumentError, "#{ id } did not have a proper applicator. Unable to create.") unless applicator
@@ -31,13 +31,12 @@ module Contrast
31
31
  # was invoked
32
32
  # @param args [Array<Object>] the arguments passed to the triggering
33
33
  # method at invocation
34
- # @raise [Contrast::SecurityException] on block, will pass the
35
- # exception from the rule
36
34
  def invoke _method, _exception, _properties, _object, args
37
35
  return unless valid_input?(args)
38
36
  return if skip_analysis?
39
37
 
40
38
  rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, args[0])
39
+ # add rescue here
41
40
  end
42
41
 
43
42
  # Calls the actual rule for this applicator, if required, when the
@@ -46,13 +45,12 @@ module Contrast
46
45
  #
47
46
  # @param arg [Object] the argument passed to the triggering method
48
47
  # at invocation
49
- # @raise [Contrast::SecurityException] on block, will pass the
50
- # exception from the rule
51
48
  def prepended_invoke arg
52
49
  return unless arg&.cs__is_a?(String)
53
50
  return if skip_analysis?
54
51
 
55
52
  rule.infilter(Contrast::Agent::REQUEST_TRACKER.current, arg)
53
+ # add rescue here
56
54
  end
57
55
 
58
56
  # Allow the rule to check if the given input is an attempt to
@@ -67,6 +65,7 @@ module Contrast
67
65
  return if skip_analysis?
68
66
 
69
67
  rule.check_command_scope(command)
68
+ # add rescue here
70
69
  end
71
70
 
72
71
  protected
@@ -64,6 +64,7 @@ module Contrast
64
64
  write_marker && possible_write?(write_marker)
65
65
  end
66
66
 
67
+ # @raise [Contrast::SecurityException] re-raises if some attack is blocked and raised from the infilter
67
68
  def path_traversal_rule path, possible_write, object, method
68
69
  return unless applies_to?(path, possible_write: possible_write)
69
70
 
@@ -61,8 +61,7 @@ module Contrast
61
61
  # was invoked
62
62
  # @param _args [Array<Object>] the arguments passed to the triggering
63
63
  # method at invocation
64
- # @raise [Contrast::SecurityException] on block, will pass the
65
- # exception from the rule
64
+ # @raise [NoMethodError] This is abstract method
66
65
  def invoke _method, _exception, _properties, _object, _args
67
66
  raise(NoMethodError, 'This is abstract, override it.')
68
67
  end
@@ -70,6 +69,7 @@ module Contrast
70
69
  # The name of the rule, as expected by the Contrast Service and Contrast UI.
71
70
  #
72
71
  # @return [String]
72
+ # @raise [NoMethodError] This is abstract method
73
73
  def rule_name
74
74
  raise(NoMethodError, 'This is abstract, override it.')
75
75
  end
@@ -230,6 +230,7 @@ module Contrast
230
230
  # the rule and matched the attack detection logic
231
231
  # @param _kwargs [Hash] key-value pairs used by the rule to build a
232
232
  # report.
233
+ # @raise[NoMethodError] raises if subclass did not implement this method on extend
233
234
  def find_attacker _context, _potential_attack_string, **_kwargs
234
235
  raise(NoMethodError, "Rule #{ rule_name } did not implement find_attack")
235
236
  end
@@ -40,6 +40,8 @@ module Contrast
40
40
  BLOCK_MESSAGE
41
41
  end
42
42
 
43
+ # @raise [Contrast::SecurityException] if the attack is blocked
44
+ # raised with BLOCK_MESSAGE
43
45
  def infilter context, database, query_string
44
46
  return unless infilter?(context)
45
47
 
@@ -38,14 +38,12 @@ module Contrast
38
38
  @_thread = Contrast::Agent::Thread.new do
39
39
  logger.debug('Starting background Reporter thread.')
40
40
  loop do
41
- # TODO: RUBY-99999
42
- # The client and connection are being used in multiple threads/ concurrently, and that's not okay. We need
43
- # to figure out why that is and lock it so that it isn't.
44
- next unless client && connection
41
+ next unless connected?
42
+ next unless app_create_complete?
45
43
 
46
44
  process_event(queue.pop)
47
45
  rescue StandardError => e
48
- logger.debug('Reporter thread could not process because of:', error: e)
46
+ logger.debug('Reporter thread could not process because of:', e)
49
47
  end
50
48
  end
51
49
  end
@@ -71,7 +69,6 @@ module Contrast
71
69
  end
72
70
  return unless event
73
71
 
74
- logger.debug('Enqueued event for sending', event_type: event.cs__class)
75
72
  queue << event
76
73
  end
77
74
 
@@ -84,7 +81,9 @@ module Contrast
84
81
  logger.warn('Reporter attempted to send event immediately with Agent disabled', caller: caller, event: event)
85
82
  return
86
83
  end
87
- client.send_event(event, connection, send_immediately: true)
84
+ return unless event
85
+
86
+ client.send_event(event, connection)
88
87
  rescue StandardError => e
89
88
  logger.error('Could not send message to TeamServer from Reporter queue.', e)
90
89
  end
@@ -108,6 +107,32 @@ module Contrast
108
107
  @_queue ||= Queue.new
109
108
  end
110
109
 
110
+ # TODO: RUBY-99999
111
+ # The client and connection are being used in multiple threads/ concurrently, and that's not okay. We need
112
+ # to figure out why that is and lock it so that it isn't.
113
+ #
114
+ # @return [Boolean]
115
+ def connected?
116
+ return true if client && connection
117
+
118
+ logger.debug('No client/connection; sleeping', client: client, connection: connection)
119
+ sleep(5)
120
+ false
121
+ end
122
+
123
+ # Unless we're in bypass mode, we need to make sure the service has started and built the application on
124
+ # TeamServer since we're doing a split style here.
125
+ #
126
+ # @return [Boolean]
127
+ def app_create_complete?
128
+ return true if Contrast::CONTRAST_SERVICE.use_agent_communication?
129
+ return true if Contrast::Agent.messaging_queue&.speedracer&.status&.startup_messages_sent?
130
+
131
+ logger.debug('Service startup incomplete; Application may not be created; sleeping')
132
+ sleep(5)
133
+ false
134
+ end
135
+
111
136
  # @param event [Contrast::Agent::Reporting::ReportingEvent]
112
137
  def process_event event
113
138
  client.send_event(event, connection)
@@ -4,12 +4,15 @@
4
4
  require 'contrast/agent/worker_thread'
5
5
  require 'contrast/agent/reporting/report'
6
6
  require 'contrast/components/logger'
7
- require 'contrast/agent/reporting/reporting_events/agent_startup'
7
+ require 'contrast/agent/inventory/dependency_usage_analysis'
8
+ require 'contrast/agent/reporting/reporting_events/poll'
9
+ require 'contrast/agent/reporting/reporting_events/server_activity'
8
10
 
9
11
  module Contrast
10
12
  module Agent
11
13
  # The ReporterHeartbeat will make sure that the process remains marked alive by TeamServer and that we periodically
12
- # reach out to get the latest settings for this application.
14
+ # reach out to get the latest settings for this application. It also sends out those messages which do not need to
15
+ # be associated directly with a request, such as Server Activity and Library Observation.
13
16
  class ReporterHeartbeat < WorkerThread
14
17
  include Contrast::Components::Logger::InstanceMethods
15
18
 
@@ -17,33 +20,36 @@ module Contrast
17
20
  # to satisfy our goals.
18
21
  REFRESH_INTERVAL_SEC = 60
19
22
 
20
- # check if we can report to TS
21
- #
22
- # @return[Boolean] true if bypass is enabled, or false if bypass disabled
23
- def enabled?
24
- @_enabled = Contrast::CONTRAST_SERVICE.use_agent_communication? if @_enabled.nil?
25
- @_enabled
26
- end
27
-
28
- def connection
29
- @_connection ||= client.initialize_connection
30
- end
31
-
32
23
  def start_thread!
33
24
  return if running?
34
25
 
35
26
  @_thread = Contrast::Agent::Thread.new do
36
27
  logger.info('Starting heartbeat thread.')
37
28
  loop do
38
- Contrast::Agent.reporter&.send_event(poll_message)
29
+ polling_events.each do |event|
30
+ Contrast::Agent.reporter&.send_event(event)
31
+ end
39
32
  sleep(REFRESH_INTERVAL_SEC)
40
33
  end
41
34
  end
42
35
  end
43
36
 
37
+ private
38
+
44
39
  def poll_message
45
40
  @_poll_message ||= Contrast::Agent::Reporting::Poll.new
46
41
  end
42
+
43
+ # Those events which should be sent periodically, rather than on event or request.
44
+ #
45
+ # @return [Array<Contrast::Agent::Reporting::ReportingEvent>]
46
+ def polling_events
47
+ [
48
+ Contrast::Agent::Inventory::DependencyUsageAnalysis.instance.generate_library_usage,
49
+ Contrast::Agent::Reporting::ServerActivity.new,
50
+ poll_message
51
+ ].compact
52
+ end
47
53
  end
48
54
  end
49
55
  end
@@ -17,23 +17,16 @@ module Contrast
17
17
  # system. Contains data used by TeamServer to render the Flow Map and SCA features.
18
18
  #
19
19
  # @attr_reader components [Array<Contrast::Agent::Reporting::ArchitectureComponent>]
20
- # @attr_reader libraries [Array<Contrast::Agent::Reporting::LibraryDiscovery>]
20
+ # @attr_accessor libraries [Array<Contrast::Agent::Reporting::LibraryDiscovery>]
21
21
  class ApplicationUpdate < Contrast::Agent::Reporting::ApplicationReportingEvent
22
- attr_reader :components, :libraries
23
-
24
- class << self
25
- # @param app_update_dtm [Contrast::Api::Dtm::ApplicationUpdate]
26
- # @return [Contrast::Agent::Reporting::ApplicationUpdate]
27
- def convert app_update_dtm
28
- report = new
29
- report.attach_data(app_update_dtm)
30
- report
31
- end
32
- end
22
+ attr_reader :components
23
+ attr_accessor :libraries
33
24
 
34
25
  def initialize
35
26
  @event_method = :PUT
36
27
  @event_endpoint = "#{ Contrast::API.api_url }/api/ng/update/application"
28
+ @components = []
29
+ @libraries = []
37
30
  super
38
31
  end
39
32
 
@@ -41,18 +34,6 @@ module Contrast
41
34
  'update-application'
42
35
  end
43
36
 
44
- # Attach the data from the protobuf models to this reporter so that it can be sent to TeamServer directly.
45
- #
46
- # @param app_update_dtm [Contrast::Api::Dtm::ApplicationUpdate]
47
- def attach_data app_update_dtm
48
- @components = app_update_dtm.components.map do |component|
49
- Contrast::Agent::Reporting::ArchitectureComponent.convert(component)
50
- end
51
- @libraries = app_update_dtm.libraries.values.map do |library|
52
- Contrast::Agent::Reporting::LibraryDiscovery.convert(library)
53
- end
54
- end
55
-
56
37
  # Convert the instance variables on the class, and other information, into the identifiers required for
57
38
  # TeamServer to process the JSON form of this message.
58
39
  #
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/api/dtm.pb'
5
+ require 'contrast/components/logger'
5
6
 
6
7
  module Contrast
7
8
  module Agent
@@ -17,6 +18,7 @@ module Contrast
17
18
  # @attr_reader url [String] the url used to connect to the component. Required for reporting.
18
19
  # @attr_reader vendor [String] the publisher of the component, like MySQL.
19
20
  class ArchitectureComponent
21
+ include Contrast::Components::Logger::InstanceMethods
20
22
  # required attributes
21
23
  attr_reader :type, :url
22
24
  # optional attributes
@@ -55,7 +57,12 @@ module Contrast
55
57
  # @return [Hash]
56
58
  # @raise [ArgumentError]
57
59
  def to_controlled_hash
58
- validate
60
+ begin
61
+ validate
62
+ rescue ArgumentError => e
63
+ logger.error('ArchitectureComponent validation failed with: ', e)
64
+ return
65
+ end
59
66
  {
60
67
  remoteHost: remote_host,
61
68
  remotePort: remote_port,