contrast-agent 6.6.5 → 6.7.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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.gitmodules +0 -3
  4. data/ext/cs__scope/cs__scope.c +1 -1
  5. data/lib/contrast/agent/assess/contrast_event.rb +2 -24
  6. data/lib/contrast/agent/assess/events/source_event.rb +7 -61
  7. data/lib/contrast/agent/assess/finalizers/hash.rb +11 -0
  8. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +0 -55
  9. data/lib/contrast/agent/assess/policy/policy_node.rb +3 -3
  10. data/lib/contrast/agent/assess/policy/policy_node_utils.rb +0 -1
  11. data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
  12. data/lib/contrast/agent/assess/policy/source_method.rb +24 -1
  13. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +7 -5
  14. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +6 -1
  15. data/lib/contrast/agent/assess/policy/trigger_method.rb +36 -132
  16. data/lib/contrast/agent/assess/policy/trigger_node.rb +3 -3
  17. data/lib/contrast/agent/assess/property/evented.rb +2 -12
  18. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +42 -84
  19. data/lib/contrast/agent/assess/rule/response/base_rule.rb +11 -27
  20. data/lib/contrast/agent/assess/rule/response/body_rule.rb +1 -3
  21. data/lib/contrast/agent/assess/rule/response/cache_control_header_rule.rb +77 -62
  22. data/lib/contrast/agent/assess/rule/response/csp_header_insecure_rule.rb +1 -1
  23. data/lib/contrast/agent/assess/rule/response/framework/rails_support.rb +6 -1
  24. data/lib/contrast/agent/assess/rule/response/header_rule.rb +5 -5
  25. data/lib/contrast/agent/assess/rule/response/hsts_header_rule.rb +1 -1
  26. data/lib/contrast/agent/assess/rule/response/x_xss_protection_header_rule.rb +1 -1
  27. data/lib/contrast/agent/assess/tracker.rb +1 -7
  28. data/lib/contrast/agent/excluder.rb +206 -0
  29. data/lib/contrast/agent/exclusion_matcher.rb +6 -0
  30. data/lib/contrast/agent/inventory/database_config.rb +6 -10
  31. data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +4 -0
  32. data/lib/contrast/agent/protect/policy/applies_sqli_rule.rb +1 -0
  33. data/lib/contrast/agent/protect/rule/base.rb +49 -5
  34. data/lib/contrast/agent/protect/rule/base_service.rb +1 -0
  35. data/lib/contrast/agent/protect/rule/cmd_injection.rb +18 -105
  36. data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +129 -0
  37. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +169 -0
  38. data/lib/contrast/agent/protect/rule/deserialization.rb +2 -1
  39. data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +51 -0
  40. data/lib/contrast/agent/protect/rule/sqli/sqli_semantic/sqli_dangerous_functions.rb +67 -0
  41. data/lib/contrast/agent/protect/rule/sqli.rb +6 -31
  42. data/lib/contrast/agent/protect/rule/xxe.rb +2 -0
  43. data/lib/contrast/agent/protect/rule.rb +3 -1
  44. data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +6 -0
  45. data/lib/contrast/agent/reporting/details/sqli_dangerous_functions.rb +22 -0
  46. data/lib/contrast/agent/reporting/reporter.rb +1 -2
  47. data/lib/contrast/agent/reporting/reporting_events/agent_startup.rb +2 -2
  48. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +1 -4
  49. data/lib/contrast/agent/reporting/reporting_events/application_startup.rb +1 -1
  50. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +0 -23
  51. data/lib/contrast/agent/reporting/reporting_events/finding.rb +19 -49
  52. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +12 -9
  53. data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +1 -1
  54. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +23 -21
  55. data/lib/contrast/agent/reporting/reporting_events/finding_event_stack.rb +5 -18
  56. data/lib/contrast/agent/reporting/reporting_events/finding_event_taint_range.rb +1 -0
  57. data/lib/contrast/{api/decorators/trace_taint_range_tags.rb → agent/reporting/reporting_events/finding_event_taint_range_tags.rb} +7 -6
  58. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +1 -1
  59. data/lib/contrast/agent/reporting/reporting_events/library_usage_observation.rb +1 -1
  60. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +2 -2
  61. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +10 -14
  62. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +11 -0
  63. data/lib/contrast/agent/reporting/reporting_events/route_coverage.rb +3 -1
  64. data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +11 -23
  65. data/lib/contrast/agent/reporting/reporting_events/route_discovery_observation.rb +8 -26
  66. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -1
  67. data/lib/contrast/agent/reporting/reporting_utilities/build_preflight.rb +4 -7
  68. data/lib/contrast/agent/reporting/reporting_utilities/headers.rb +1 -1
  69. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +3 -3
  70. data/lib/contrast/agent/request.rb +2 -2
  71. data/lib/contrast/agent/request_context.rb +8 -20
  72. data/lib/contrast/agent/request_context_extend.rb +15 -36
  73. data/lib/contrast/agent/request_handler.rb +0 -8
  74. data/lib/contrast/agent/response.rb +0 -18
  75. data/lib/contrast/agent/telemetry/events/event.rb +1 -1
  76. data/lib/contrast/agent/telemetry/events/metric_event.rb +1 -1
  77. data/lib/contrast/agent/telemetry/events/startup_metrics_event.rb +3 -3
  78. data/lib/contrast/agent/version.rb +1 -1
  79. data/lib/contrast/api/communication/messaging_queue.rb +2 -3
  80. data/lib/contrast/api/communication/socket_client.rb +4 -4
  81. data/lib/contrast/api/communication/speedracer.rb +4 -8
  82. data/lib/contrast/api/decorators/agent_startup.rb +5 -6
  83. data/lib/contrast/api/decorators/application_settings.rb +2 -1
  84. data/lib/contrast/api/decorators/application_startup.rb +6 -6
  85. data/lib/contrast/api/decorators/message.rb +0 -4
  86. data/lib/contrast/api/decorators/rasp_rule_sample.rb +0 -6
  87. data/lib/contrast/api/decorators.rb +0 -6
  88. data/lib/contrast/api/dtm.pb.rb +0 -489
  89. data/lib/contrast/components/agent.rb +16 -12
  90. data/lib/contrast/components/api.rb +10 -10
  91. data/lib/contrast/components/app_context.rb +3 -3
  92. data/lib/contrast/components/app_context_extend.rb +1 -1
  93. data/lib/contrast/components/assess.rb +92 -38
  94. data/lib/contrast/components/assess_rules.rb +36 -0
  95. data/lib/contrast/components/config.rb +54 -12
  96. data/lib/contrast/components/contrast_service.rb +8 -8
  97. data/lib/contrast/components/heap_dump.rb +1 -1
  98. data/lib/contrast/components/protect.rb +5 -5
  99. data/lib/contrast/components/ruby_component.rb +81 -0
  100. data/lib/contrast/components/sampling.rb +1 -1
  101. data/lib/contrast/components/security_logger.rb +23 -0
  102. data/lib/contrast/components/service.rb +55 -0
  103. data/lib/contrast/components/settings.rb +12 -4
  104. data/lib/contrast/config/base_configuration.rb +1 -1
  105. data/lib/contrast/config/protect_rules_configuration.rb +17 -3
  106. data/lib/contrast/config/server_configuration.rb +1 -1
  107. data/lib/contrast/config.rb +0 -6
  108. data/lib/contrast/configuration.rb +81 -17
  109. data/lib/contrast/extension/assess/exec_trigger.rb +3 -1
  110. data/lib/contrast/extension/assess/marshal.rb +3 -2
  111. data/lib/contrast/extension/assess/string.rb +0 -1
  112. data/lib/contrast/extension/extension.rb +1 -1
  113. data/lib/contrast/framework/base_support.rb +0 -5
  114. data/lib/contrast/framework/grape/support.rb +1 -23
  115. data/lib/contrast/framework/manager.rb +0 -10
  116. data/lib/contrast/framework/rails/support.rb +5 -58
  117. data/lib/contrast/framework/sinatra/support.rb +2 -21
  118. data/lib/contrast/logger/cef_log.rb +21 -3
  119. data/lib/contrast/logger/log.rb +1 -11
  120. data/lib/contrast/tasks/config.rb +4 -2
  121. data/lib/contrast/utils/assess/event_limit_utils.rb +5 -8
  122. data/lib/contrast/utils/assess/trigger_method_utils.rb +10 -18
  123. data/lib/contrast/utils/findings.rb +6 -5
  124. data/lib/contrast/utils/hash_digest.rb +9 -24
  125. data/lib/contrast/utils/hash_digest_extend.rb +6 -6
  126. data/lib/contrast/utils/invalid_configuration_util.rb +21 -58
  127. data/lib/contrast/utils/log_utils.rb +32 -8
  128. data/lib/contrast/utils/net_http_base.rb +2 -2
  129. data/lib/contrast/utils/patching/policy/patch_utils.rb +3 -2
  130. data/lib/contrast/utils/stack_trace_utils.rb +0 -25
  131. data/lib/contrast/utils/string_utils.rb +9 -0
  132. data/lib/contrast/utils/telemetry_client.rb +13 -7
  133. data/lib/contrast.rb +5 -10
  134. metadata +22 -28
  135. data/lib/contrast/agent/reporting/reporting_events/trace_event_source.rb +0 -30
  136. data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -36
  137. data/lib/contrast/api/decorators/activity.rb +0 -33
  138. data/lib/contrast/api/decorators/architecture_component.rb +0 -36
  139. data/lib/contrast/api/decorators/finding.rb +0 -29
  140. data/lib/contrast/api/decorators/route_coverage.rb +0 -91
  141. data/lib/contrast/api/decorators/trace_event.rb +0 -120
  142. data/lib/contrast/api/decorators/trace_event_object.rb +0 -63
  143. data/lib/contrast/api/decorators/trace_event_signature.rb +0 -69
  144. data/lib/contrast/api/decorators/trace_taint_range.rb +0 -52
  145. data/lib/contrast/config/assess_configuration.rb +0 -93
  146. data/lib/contrast/config/assess_rules_configuration.rb +0 -32
  147. data/lib/contrast/config/root_configuration.rb +0 -90
  148. data/lib/contrast/config/ruby_configuration.rb +0 -81
  149. data/lib/contrast/config/service_configuration.rb +0 -49
  150. data/lib/contrast/utils/preflight_util.rb +0 -13
@@ -49,36 +49,6 @@ module Contrast
49
49
  find_all_routes(::Rails.application, [])
50
50
  end
51
51
 
52
- # Find the current route, based on the provided Request wrapper
53
- #
54
- # @param request[Contrast::Agent::Request]
55
- # @return [Contrast::Api::Dtm::RouteCoverage, nil]
56
- def current_route request
57
- return unless ::Rails.cs__respond_to?(:application)
58
-
59
- # ActionDispatch::Journey::Path::Pattern::MatchData, Hash, ActionDispatch::Journey::Route, Array<String>
60
- match, _params, route, path = get_full_route(request.rack_request)
61
- unless route
62
- logger.warn("Unable to determine the current route of this request: #{ request.rack_request }")
63
- return
64
- end
65
-
66
- original_url = request.rack_request.path_info
67
- mounted_app = route&.app&.app
68
- # Route is either the final rails route, or a router that points to a Sinatra controller.
69
- if mounted_app && Contrast::Framework::Sinatra::Support.sinatra_controller?(mounted_app)
70
- return mounted_sinatra_route(request, match, path, route, original_url)
71
- end
72
- if mounted_app && Contrast::Framework::Grape::Support.grape_controller?(mounted_app)
73
- return mounted_grape_route(request, match, path, route, original_url)
74
- end
75
-
76
- Contrast::Api::Dtm::RouteCoverage.from_action_dispatch_journey(route, original_url)
77
- rescue StandardError => e
78
- logger.warn('Unable to determine the current route of this request', e)
79
- nil
80
- end
81
-
82
52
  # Find the current route, based on the provided Request wrapper
83
53
  #
84
54
  # @param request[Contrast::Agent::Request]
@@ -105,9 +75,10 @@ module Contrast
105
75
  end
106
76
 
107
77
  new_route_coverage = Contrast::Agent::Reporting::RouteCoverage.new
108
- new_route_coverage.attach_rails_data(route, original_url)
78
+ new_route_coverage&.attach_rails_data(route, original_url)
79
+ new_route_coverage
109
80
  rescue StandardError => e
110
- logger.warn('Unable to determine the current route of this request', e)
81
+ logger.warn('Unable to determine the current route of this request due to exception: ', e)
111
82
  nil
112
83
  end
113
84
 
@@ -132,7 +103,7 @@ module Contrast
132
103
  # Determine if route is a Rails engine route.
133
104
  #
134
105
  # @param route [Object] app or route that points to a ::Rails::Engine
135
- # @return [bool] whether the router is an engine or not.
106
+ # @return [Boolean, nil] whether the router is an engine or not.
136
107
  def engine_route? route
137
108
  return false unless route&.app&.app
138
109
  return false unless route.app.is_a?(::ActionDispatch::Routing::Mapper::Constraints) ||
@@ -155,7 +126,7 @@ module Contrast
155
126
 
156
127
  match, params, route = route_matches.first
157
128
 
158
- # If the current routing node points to a sub-app (::Rais::Engine), dive deeper.
129
+ # If the current routing node points to a sub-app (::Rails::Engine), dive deeper.
159
130
  # Have sub-app route the remainder of the url.
160
131
  if engine_route?(route)
161
132
  new_req = retrieve_request(request.env)
@@ -186,36 +157,12 @@ module Contrast
186
157
  route_list
187
158
  end
188
159
 
189
- # @param request[Contrast::Agent::Request]
190
- # @param match [ActionDispatch::Journey::Path::Pattern::MatchData]
191
- # @param path [Array<String>] the path of this request, built out from each nested
192
- # ActionDispatch::Journey::Path::Pattern::MatchData
193
- # @param route [::ActionDispatch::Journey::Route]
194
- # @param original_url [String] the full url of this request, including the mount
195
- # @return [Contrast::Api::Dtm::RouteCoverage, nil]
196
- def mounted_sinatra_route request, match, path, route, original_url
197
- new_req = unmounted_route(request, match, path)
198
- Contrast::Framework::Sinatra::Support.current_route(new_req, route.app.app, original_url)
199
- end
200
-
201
160
  # @return [Contrast::Agent::Reporting::RouteCoverage, nil]
202
161
  def mounted_new_sinatra_route request, match, path, route, original_url
203
162
  new_req = unmounted_route(request, match, path)
204
163
  Contrast::Framework::Sinatra::Support.current_route_coverage(new_req, route.app.app, original_url)
205
164
  end
206
165
 
207
- # @param request[Contrast::Agent::Request]
208
- # @param match [ActionDispatch::Journey::Path::Pattern::MatchData]
209
- # @param path [Array<String>] the path of this request, built out from each nested
210
- # ActionDispatch::Journey::Path::Pattern::MatchData
211
- # @param route [::ActionDispatch::Journey::Route]
212
- # @param original_url [String] the full url of this request, including the mount
213
- # @return [Contrast::Api::Dtm::RouteCoverage, nil]
214
- def mounted_grape_route request, match, path, route, original_url
215
- new_req = unmounted_route(request, match, path)
216
- Contrast::Framework::Grape::Support.current_route(new_req, route.app.app, original_url)
217
- end
218
-
219
166
  # @return [Contrast::Agent::Reporting::RouteCoverage, nil]
220
167
  def mounted_new_grape_route request, match, path, route, original_url
221
168
  new_req = unmounted_route(request, match, path)
@@ -64,26 +64,6 @@ module Contrast
64
64
  routes
65
65
  end
66
66
 
67
- # Given the current request return a RouteCoverage dtm.
68
- #
69
- # @param request [Contrast::Agent::Request] a contrast tracked request.
70
- # @param controller [::Sinatra::Base] optionally use this controller instead of global ::Sinatra::Base.
71
- # @return [Contrast::Api::Dtm::RouteCoverage, nil] a Dtm describing the route
72
- # matched to the request if a match was found.
73
- def current_route request, controller = ::Sinatra::Base, full_route = nil
74
- return unless sinatra_controller?(controller)
75
-
76
- method = request.env[::Rack::REQUEST_METHOD] # GET, PUT, POST, etc...
77
-
78
- # Find route match--checking superclasses if necessary.
79
- final_controller, route_pattern = _route_recurse(controller, method, _cleaned_route(request))
80
- return unless !final_controller.nil? && !route_pattern.nil?
81
-
82
- full_route ||= request.path_info
83
-
84
- Contrast::Api::Dtm::RouteCoverage.from_sinatra_route(final_controller, method, route_pattern, full_route)
85
- end
86
-
87
67
  # Given the current request - return a RouteCoverage object
88
68
 
89
69
  # @param request [Contrast::Agent::Request] a contrast tracked request.
@@ -97,12 +77,13 @@ module Contrast
97
77
 
98
78
  # Find route match--checking superclasses if necessary.
99
79
  final_controller, route_pattern = _route_recurse(controller, method, _cleaned_route(request))
100
- return unless final_controller
80
+ return unless final_controller && route_pattern
101
81
 
102
82
  full_route ||= request.env[::Rack::PATH_INFO]
103
83
 
104
84
  new_route_coverage = Contrast::Agent::Reporting::RouteCoverage.new
105
85
  new_route_coverage.attach_rack_based_data(final_controller, method, route_pattern, full_route)
86
+ new_route_coverage
106
87
  end
107
88
 
108
89
  # Search object space for sinatra controllers--any class that subclasses ::Sinatra::Base.
@@ -51,16 +51,20 @@ module Contrast
51
51
  # set by local configuration.
52
52
  #
53
53
  # @param log_level [String] the level at which to log, as provided by TeamServer settings
54
- def build_logger log_level = nil
54
+ # @param log_file [String] the file to which to log, as provided by TeamServer settings
55
+ def build_logger log_level = nil, log_file = nil
56
+ current_path_const = find_valid_path(log_file)
55
57
  current_level_const = find_valid_level(log_level)
56
58
  level_change = current_level_const != previous_level
59
+ path_change = current_path_const != previous_path
57
60
 
58
61
  # don't needlessly recreate logger
59
- return if @cef_logger && !level_change
62
+ return if @cef_logger && !(level_change || path_change)
60
63
 
61
64
  @previous_level = current_level_const
65
+ @previous_path = current_path_const
62
66
 
63
- @_cef_logger = build(path: DEFAULT_CEF_NAME, level_const: current_level_const)
67
+ @_cef_logger = build(path: current_path_const, level_const: current_level_const)
64
68
  # If we're logging to a new path, then let's start it w/ our helpful
65
69
  # data gathering messages
66
70
  # log_update if path_change
@@ -82,6 +86,20 @@ module Contrast
82
86
  @_cef_logger
83
87
  end
84
88
 
89
+ def find_valid_path log_file
90
+ config = ::Contrast::CONFIG.agent.security_logger
91
+ config_path = config&.path&.length.to_i.positive? ? config.path : nil
92
+ valid_path(config_path || log_file, default_name: DEFAULT_CEF_NAME)
93
+ end
94
+
95
+ # @return [::Ougai::Logging::Severity] the level at which to log
96
+ def find_valid_level log_level
97
+ config = ::Contrast::CONFIG.agent.security_logger
98
+ config_level = config&.level&.length&.positive? ? config.level : nil
99
+
100
+ valid_level(config_level || log_level)
101
+ end
102
+
85
103
  def log msg, level = @_cef_logger.level
86
104
  case level
87
105
  when ::Logger::Severity::INFO
@@ -65,7 +65,7 @@ module Contrast
65
65
  @previous_path = current_path
66
66
  @previous_level = current_level_const
67
67
 
68
- progname = Contrast::CONFIG.root.agent.logger.progname
68
+ progname = Contrast::CONFIG.agent.logger.progname
69
69
  @_logger = build(path: current_path, level_const: current_level_const, progname: progname)
70
70
  # If we're logging to a new path, then let's start it w/ our helpful
71
71
  # data gathering messages
@@ -85,16 +85,6 @@ module Contrast
85
85
  def logger
86
86
  @_logger
87
87
  end
88
-
89
- # StringIO is a valid path because it logs directly to a string buffer
90
- def write_permission? path
91
- return false if path.nil?
92
- return true if path.is_a?(StringIO)
93
- return File.writable?(path) if File.exist?(path)
94
-
95
- dir_name = File.dirname(File.absolute_path(path))
96
- File.writable?(dir_name)
97
- end
98
88
  end
99
89
  end
100
90
  end
@@ -73,7 +73,9 @@ module Contrast
73
73
  config = Contrast::Configuration.new
74
74
  abort('Unable to Build Config') unless config
75
75
  missing = []
76
- api_hash = config.root.api.to_hash
76
+
77
+ api_hash = config.api.to_contrast_hash
78
+
77
79
  api_hash.each_key do |key|
78
80
  value = mask_keys(api_hash, key)
79
81
  if value.is_a?(Contrast::Config::ApiProxyConfiguration)
@@ -123,7 +125,7 @@ module Contrast
123
125
  def self.validate_headers
124
126
  missing = []
125
127
  reporter = Contrast::Agent::Reporter.new
126
- reporter_headers = reporter.client.headers.to_hash
128
+ reporter_headers = reporter.client.headers.to_contrast_hash
127
129
  reporter_headers.each_key do |key|
128
130
  value = mask_keys(reporter_headers, key)
129
131
  missing << key if value.nil?
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/components/logger'
5
+ require 'contrast/components/assess'
5
6
 
6
7
  module Contrast
7
8
  module Utils
@@ -16,14 +17,12 @@ module Contrast
16
17
  return false unless (context = Contrast::Agent::REQUEST_TRACKER.current)
17
18
 
18
19
  if method_policy.source_node
19
- max = (::Contrast::ASSESS.max_source_events ||
20
- Contrast::Config::AssessConfiguration::DEFAULT_MAX_SOURCE_EVENTS)
20
+ max = ::Contrast::ASSESS.max_context_source_events
21
21
  return at_limit?(method_policy, context.source_event_count, max)
22
22
 
23
23
  end
24
24
  if method_policy.propagation_node
25
- max = (::Contrast::ASSESS.max_propagation_events ||
26
- Contrast::Config::AssessConfiguration::DEFAULT_MAX_PROPAGATION_EVENTS)
25
+ max = ::Contrast::ASSESS.max_propagation_events
27
26
  return at_limit?(method_policy, context.propagation_event_count, max)
28
27
  end
29
28
 
@@ -33,7 +32,7 @@ module Contrast
33
32
  def event_limit_for_rule? rule_id # rubocop:disable Metrics/AbcSize
34
33
  return false unless (context = Contrast::Agent::REQUEST_TRACKER.current)
35
34
 
36
- saved_request_ids = rule_counts.keys.map { |k| k.to_s.split('_')[1] }
35
+ saved_request_ids = rule_counts.keys.map { |k| k.to_s.split('_')[1].to_i }
37
36
 
38
37
  # if we passed the threshold and we actually have records for that request - wipe them
39
38
  if saved_request_ids.uniq.include?(context.request.__id__)
@@ -43,15 +42,13 @@ module Contrast
43
42
 
44
43
  # if we have recorded rule counts, but none of them are for the current request_id
45
44
  # eventually we can try and play with the time_limit_threshold -> DEFAULT_MAX_RULE_TIME_THRESHOLD
46
- unless !rule_counts.empty? && saved_request_ids.include?(context.request.__id__)
45
+ if !rule_counts.empty? && !saved_request_ids.include?(context.request.__id__)
47
46
  restore_defaults
48
47
  threshold_time_limit
49
48
  end
50
49
 
51
50
  rule_key = "#{ rule_id }_#{ context.request.__id__ }"
52
51
  rule_counts[rule_key] += 1
53
- # TODO: RUBY-1680 remove default
54
- # we don't need the default here because we either return from the config, or we return the default
55
52
  rule_counts[rule_key] >= ::Contrast::ASSESS.max_rule_reported
56
53
  end
57
54
 
@@ -72,7 +72,8 @@ module Contrast
72
72
  elsif trigger_node.dataflow?
73
73
  apply_dataflow_rule(trigger_node, source, object, ret, *args)
74
74
  else # trigger rule - just calling the method is dangerous
75
- build_finding(trigger_node, source, object, ret, *args)
75
+ finding = build_finding(trigger_node, source, object, ret, *args)
76
+ Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding) if finding
76
77
  end
77
78
  rescue StandardError => e
78
79
  logger.warn('Unable to apply trigger', e, node_id: trigger_node.id)
@@ -81,8 +82,8 @@ module Contrast
81
82
  # This is our method that actually checks the taint on the object our trigger_node targets for our Regexp
82
83
  # based rules.
83
84
  #
84
- # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
85
- # trigger event
85
+ # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this trigger
86
+ # event
86
87
  # @param source [Object] the source of the Trigger Event
87
88
  # @param object [Object] the Object on which the method was invoked
88
89
  # @param ret [Object] the Return of the invoked method
@@ -92,7 +93,8 @@ module Contrast
92
93
  return if trigger_node.good_value && source.match?(trigger_node.good_value)
93
94
  return if trigger_node.bad_value && source !~ trigger_node.bad_value
94
95
 
95
- build_finding(trigger_node, source, object, ret, *args)
96
+ finding = build_finding(trigger_node, source, object, ret, *args)
97
+ Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding) if finding
96
98
  end
97
99
 
98
100
  # This is our method that actually checks the taint on the object our trigger_node targets for our Dataflow
@@ -104,33 +106,23 @@ module Contrast
104
106
  # @param object [Object] the Object on which the method was invoked
105
107
  # @param ret [Object] the Return of the invoked method
106
108
  # @param args [Array<Object>] the Arguments with which the method was invoked
107
- def apply_dataflow_rule trigger_node, source, object, ret, *args # rubocop:disable Metrics/PerceivedComplexity
108
- return unless source
109
+ def apply_dataflow_rule trigger_node, source, object, ret, *args
110
+ return unless source && Contrast::Agent::Assess::Tracker.tracked?(source)
109
111
 
110
112
  if Contrast::Agent::Assess::Tracker.trackable?(source)
111
- return unless Contrast::Agent::Assess::Tracker.tracked?(source)
112
113
  return unless trigger_node.violated?(source)
113
114
 
114
- build_finding(trigger_node, source, object, ret, *args)
115
+ finding = build_finding(trigger_node, source, object, ret, *args)
116
+ report_finding(finding) if finding
115
117
  elsif Contrast::Utils::DuckUtils.iterable_hash?(source)
116
- return unless Contrast::Agent::Assess::Tracker.tracked?(source)
117
-
118
118
  source.each_pair do |key, value|
119
119
  apply_dataflow_rule(trigger_node, key, object, ret, *args)
120
120
  apply_dataflow_rule(trigger_node, value, object, ret, *args)
121
121
  end
122
122
  elsif Contrast::Utils::DuckUtils.iterable_enumerable?(source)
123
- return unless Contrast::Agent::Assess::Tracker.tracked?(source)
124
-
125
123
  source.each do |value|
126
124
  apply_dataflow_rule(trigger_node, value, object, ret, *args)
127
125
  end
128
- else
129
- logger.debug('Trigger source is untrackable. Unable to inspect.',
130
- node_id: trigger_node.id,
131
- source_id: source.__id__,
132
- source_type: source.cs__class.cs__name,
133
- frozen: source.cs__frozen?)
134
126
  end
135
127
  end
136
128
  end
@@ -52,11 +52,12 @@ module Contrast
52
52
 
53
53
  while @_collection.any?
54
54
  finding = @_collection.pop
55
- Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(finding[:trigger_node],
56
- finding[:source],
57
- finding[:object],
58
- finding[:ret],
59
- finding[:args])
55
+ collected = Contrast::Agent::Assess::Policy::TriggerMethod.build_finding(finding[:trigger_node],
56
+ finding[:source],
57
+ finding[:object],
58
+ finding[:ret],
59
+ finding[:args])
60
+ Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(collected) if collected
60
61
  end
61
62
  true
62
63
  end
@@ -29,12 +29,10 @@ module Contrast
29
29
  @crc32 = 0
30
30
  end
31
31
 
32
- # Update to CRC checksum the finding route and verb if finding route
33
- # [Contrast::Api::Dtm::RouteCoverage] is available else update the passed
34
- # request or Contrast::REQUEST_TRACKER.current.request uri and used request
35
- # method.
32
+ # Update to CRC checksum the finding route and verb if finding route is available, else update the passed
33
+ # request or Contrast::REQUEST_TRACKER.current.request uri and used request method.
36
34
  #
37
- # @param finding [Contrast::Api::Dtm::Finding, Contrast::Agent::Reporting::Finding] finding to be reported
35
+ # @param finding [Contrast::Agent::Reporting::Finding] finding to be reported
38
36
  # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
39
37
  # @return checksum [Integer, nil] returns nil if there is no request context or tracking
40
38
  # is disabled.
@@ -43,12 +41,9 @@ module Contrast
43
41
  return unless context || ::Contrast::ASSESS.non_request_tracking?
44
42
 
45
43
  if (route = finding.routes[0])
46
- if finding.cs__is_a?(Contrast::Agent::Reporting::Finding) && (observation = route.observations[0])
47
- update(observation.url)
44
+ update(route.signature)
45
+ if (observation = route.observations[0])
48
46
  update(observation.verb)
49
- else
50
- update(route.route) # the normalized URL used to access the method in the route.
51
- update(route.verb) # the HTTP Verb used to access the method in the route.
52
47
  end
53
48
  return
54
49
  end
@@ -60,24 +55,14 @@ module Contrast
60
55
  end
61
56
 
62
57
  # Update to CRC checksum the event source name and source type.
63
- # Expects Contrast::Api::Dtm::TraceEvent || Contrast::Agent::Assess::Events::SourceEvent
64
58
  #
65
- # @param events [Protobuf::Field::FieldArray<Contrast::Api::Dtm::TraceEvent>,
66
- # <Contrast::Agent::Assess::Events::SourceEvent>]
67
- # Array of TraceEvents
59
+ # @param events [Array<Contrast::Agent::Reporting::FindingEvent>]
68
60
  # @return checksum [Integer, nil] returns nil if there is no events
69
61
  def update_on_sources events
70
- return unless events&.any?
71
-
72
62
  events.each do |event|
73
- if event.cs__is_a?(Contrast::Api::Dtm::TraceEvent)
74
- event.event_sources&.each do |source|
75
- update(source.type)
76
- update(source.name) # rubocop:disable Security/Module/Name
77
- end
78
- elsif event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
79
- update(event.source_type)
80
- update(event.source_name)
63
+ event.event_sources.each do |source|
64
+ update(source.type)
65
+ update(source.name) # rubocop:disable Security/Module/Name
81
66
  end
82
67
  end
83
68
  end
@@ -34,7 +34,7 @@ module Contrast
34
34
  # crypto(crypto-bad-ciphers, crypto-bad-mac) rules or trigger event
35
35
  # and returns string representation.
36
36
  #
37
- # @param finding [Contrast::Api::Dtm::Finding] to be reported
37
+ # @param finding [Contrast::Agent::Reporting::Finding] to be reported
38
38
  # @param source [Object] the source of the Trigger Event
39
39
  # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
40
40
  # @return checksum [String] String representation of CRC32 checksum
@@ -50,7 +50,7 @@ module Contrast
50
50
  # Generates the hash checksum for configurations. Converts the finding rule_id, session_id and configPath and
51
51
  # to CRC32 checksum and returns string representation to be appended to Contrast::Api::Dtm::Finding
52
52
  #
53
- # @param finding [Contrast::Api::Dtm::Finding] to be reported
53
+ # @param finding [Contrast::Agent::Reporting::Finding] to be reported
54
54
  # @return checksum [String] String representation of CRC32 checksum.
55
55
  def generate_config_hash finding
56
56
  hash = new
@@ -65,7 +65,7 @@ module Contrast
65
65
  # Generates the hash checksum for class scanning. Converts the rule_id, finding.properties(source, name)
66
66
  # to CRC32 checksum and returns string representation.
67
67
  #
68
- # @param finding [Contrast::Api::Dtm::Finding] to be reported
68
+ # @param finding [Contrast::Agent::Reporting::Finding] to be reported
69
69
  # @return checksum [String] String representation of CRC32 checksum.
70
70
  def generate_class_scanning_hash finding
71
71
  hash = new
@@ -86,7 +86,7 @@ module Contrast
86
86
  # used in #generate_event_hash.
87
87
  #
88
88
  #
89
- # @param finding [Contrast::Api::Dtm::Finding] to be reported
89
+ # @param finding [Contrast::Agent::Reporting::Finding] to be reported
90
90
  # @param algorithm [Object] the source of the Trigger Event
91
91
  # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
92
92
  # @return checksum [String] String representation of CRC32 checksum
@@ -101,7 +101,7 @@ module Contrast
101
101
  # Generates the hash checksum for dataflow when the finding events are more than one
102
102
  # used in #generate_event_hash.
103
103
  #
104
- # @param finding [Contrast::Api::Dtm::Finding] to be reported
104
+ # @param finding [Contrast::Agent::Reporting::Finding] to be reported
105
105
  # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
106
106
  # @return checksum [String] String representation of CRC32 checksum
107
107
  def generate_dataflow_hash finding, request
@@ -115,7 +115,7 @@ module Contrast
115
115
  # Generates the hash checksum for trigger
116
116
  # used in #generate_event_hash.
117
117
  #
118
- # @param finding [Contrast::Api::Dtm::Finding] to be reported
118
+ # @param finding [Contrast::Agent::Reporting::Finding] to be reported
119
119
  # @param request [Contrast::Agent::Request] our wrapper around the Rack::Request.
120
120
  # @return checksum [String] String representation of CRC32 checksum
121
121
  def generate_trigger_hash finding, request
@@ -7,8 +7,8 @@ require 'contrast/components/scope'
7
7
 
8
8
  module Contrast
9
9
  module Utils
10
- # This utility allows us to report invalid configurations detected in
11
- # customer applications, as determined by Configuration Rules at runtime.
10
+ # This utility allows us to report invalid configurations detected in customer applications, as determined by
11
+ # Configuration Rules at runtime.
12
12
  module InvalidConfigurationUtil
13
13
  include Contrast::Components::Logger::InstanceMethods
14
14
  include Contrast::Components::Scope::InstanceMethods
@@ -20,64 +20,39 @@ module Contrast
20
20
  # Build and report a finding for the given rule
21
21
  #
22
22
  # @param rule_id [String] the rule that was violated by the configuration
23
- # @param user_provided_options [Hash] the configuration value(s) which
24
- # violated the rule
25
- # @param call_location [Thread::Backtrace::Location] the location where
26
- # the bad configuration was set
23
+ # @param user_provided_options [Hash] the configuration value(s) which violated the rule
24
+ # @param call_location [Thread::Backtrace::Location] the location where the bad configuration was set
27
25
  def cs__report_finding rule_id, user_provided_options, call_location
28
26
  with_contrast_scope do
29
- finding = Contrast::Api::Dtm::Finding.new
30
- finding.version = Contrast::Agent::Assess::Policy::TriggerMethod::CURRENT_FINDING_VERSION
31
- finding.rule_id = rule_id
32
- set_properties(finding, user_provided_options, call_location)
33
- hash = Contrast::Utils::HashDigest.generate_config_hash(finding)
34
- finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash)
35
- finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
36
- if Contrast::Agent::Reporter.enabled? # TODO: RUBY-1438 -- remove
37
- cs__report_new_finding(hash, rule_id, user_provided_options, call_location)
38
- else
39
- Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
40
- end
27
+ finding = build_finding(rule_id, user_provided_options, call_location)
28
+ return unless finding
29
+
30
+ Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
41
31
  end
42
32
  rescue StandardError => e
43
33
  logger.error('Unable to build a finding', e, rule: rule_id)
44
34
  end
45
35
 
46
- def cs__report_new_finding hash_code, rule_id, user_provided_options, call_location
47
- new_preflight = Contrast::Agent::Reporting::Preflight.new
48
- new_preflight_message = Contrast::Agent::Reporting::PreflightMessage.new
49
- new_preflight_message.hash_code = hash_code
50
- new_preflight_message.data = "#{ rule_id },#{ hash_code }"
51
- new_preflight.messages << new_preflight_message
52
-
53
- ruby_finding = Contrast::Agent::Reporting::Finding.new(rule_id)
54
- ruby_finding.hash_code = hash_code
55
- set_new_finding_properties(ruby_finding, user_provided_options, call_location)
56
- Contrast::Agent.reporter&.send_event(new_preflight)
57
- Contrast::Agent::Reporting::ReportingStorage[hash_code] = ruby_finding
58
- end
59
-
60
36
  private
61
37
 
62
38
  # Set the properties needed to report and subsequently render this finding on the finding given.
63
39
  #
64
- # @param finding [Contrast::Api::Dtm::Finding] the configuration finding to populate
65
- # @param user_provided_options [Hash] the configuration value(s) which
66
- # violated the rule
67
- # @param call_location [Thread::Backtrace::Location] the location where
68
- # the bad configuration was set
69
- def set_properties finding, user_provided_options, call_location
70
- path = call_location.path
40
+ # @param rule_id [String] the rule that was violated by the configuration
41
+ # @param user_provided_options [Hash] the configuration value(s) which violated the rule
42
+ # @param call_location [Thread::Backtrace::Location] the location where the bad configuration was set
43
+ # @return [Contrast::Agent::Reporting::Finding]
44
+ def build_finding rule_id, user_provided_options, call_location
45
+ finding = Contrast::Agent::Reporting::Finding.new(rule_id)
46
+ finding.properties[CS__SESSION_ID] = user_provided_options[:key].to_s if user_provided_options
71
47
  # just get the file name, not the full path
72
- path = path.split(Contrast::Utils::ObjectShare::SLASH).last
73
- session_id = user_provided_options[:key].to_s if user_provided_options
74
- finding.properties[CS__SESSION_ID] = Contrast::Utils::StringUtils.force_utf8(session_id)
75
- finding.properties[CS__PATH] = Contrast::Utils::StringUtils.force_utf8(path)
76
- file_path = call_location.absolute_path
77
- snippet = file_snippet(file_path, call_location)
78
- finding.properties[CS__SNIPPET] = Contrast::Utils::StringUtils.force_utf8(snippet)
48
+ finding.properties[CS__PATH] = call_location.path.split(Contrast::Utils::ObjectShare::SLASH).last
49
+ finding.properties[CS__SNIPPET] = file_snippet(call_location.absolute_path, call_location)
50
+ finding.hash_code = Contrast::Utils::HashDigest.generate_config_hash(finding)
51
+ finding
79
52
  end
80
53
 
54
+ # @param file_path [String] full path to the file with the property
55
+ # @param call_location [Thread::Backtrace::Location] the location where the bad configuration was set
81
56
  def file_snippet file_path, call_location
82
57
  idx = call_location&.lineno
83
58
  if file_path && idx && File.exist?(file_path)
@@ -94,18 +69,6 @@ module Contrast
94
69
  end
95
70
  call_location&.label&.dup
96
71
  end
97
-
98
- def set_new_finding_properties finding, user_provided_options, call_location
99
- path = call_location.path
100
- # just get the file name, not the full path
101
- path = path.split(Contrast::Utils::ObjectShare::SLASH).last
102
- session_id = user_provided_options[:key].to_s if user_provided_options
103
- finding.properties[CS__SESSION_ID] = session_id
104
- finding.properties[CS__PATH] = path
105
- file_path = call_location.absolute_path
106
- snippet = file_snippet(file_path, call_location)
107
- finding.properties[CS__SNIPPET] = snippet
108
- end
109
72
  end
110
73
  end
111
74
  end