contrast-agent 6.8.0 → 6.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/assess/policy/trigger_method.rb +1 -1
  3. data/lib/contrast/agent/assess/property/evented.rb +11 -11
  4. data/lib/contrast/agent/assess.rb +0 -1
  5. data/lib/contrast/agent/excluder.rb +1 -1
  6. data/lib/contrast/agent/middleware.rb +8 -2
  7. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +116 -0
  8. data/lib/contrast/agent/protect/rule/base.rb +2 -2
  9. data/lib/contrast/agent/protect/rule/bot_blocker.rb +1 -1
  10. data/lib/contrast/agent/protect/rule/cmd_injection.rb +8 -7
  11. data/lib/contrast/agent/protect/rule/cmdi/cmdi_chained_command.rb +0 -5
  12. data/lib/contrast/agent/protect/rule/cmdi/cmdi_dangerous_path.rb +0 -5
  13. data/lib/contrast/agent/protect/rule/path_traversal.rb +4 -3
  14. data/lib/contrast/agent/protect/rule/sqli.rb +4 -3
  15. data/lib/contrast/agent/protect/rule/xss.rb +1 -0
  16. data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +1 -1
  17. data/lib/contrast/agent/reporting/report.rb +1 -0
  18. data/lib/contrast/agent/reporting/reporter.rb +34 -0
  19. data/lib/contrast/agent/reporting/reporter_heartbeat.rb +3 -9
  20. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +1 -1
  21. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +12 -7
  22. data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +6 -1
  23. data/lib/contrast/agent/reporting/reporting_events/finding.rb +2 -2
  24. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +239 -93
  25. data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +10 -23
  26. data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +10 -9
  27. data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +12 -0
  28. data/lib/contrast/agent/reporting/reporting_events/server_reporting_event.rb +8 -0
  29. data/lib/contrast/agent/reporting/reporting_events/server_settings.rb +40 -0
  30. data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +6 -0
  31. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +43 -1
  32. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +8 -4
  33. data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +58 -4
  34. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +4 -3
  35. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +76 -16
  36. data/lib/contrast/agent/reporting/server_settings_worker.rb +44 -0
  37. data/lib/contrast/agent/reporting/settings/assess_server_feature.rb +14 -2
  38. data/lib/contrast/agent/reporting/settings/helpers.rb +7 -0
  39. data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +39 -2
  40. data/lib/contrast/agent/reporting/settings/rule_definition.rb +3 -0
  41. data/lib/contrast/agent/reporting/settings/security_logger.rb +77 -0
  42. data/lib/contrast/agent/reporting/settings/server_features.rb +9 -0
  43. data/lib/contrast/agent/reporting/settings/syslog.rb +34 -5
  44. data/lib/contrast/agent/request.rb +1 -0
  45. data/lib/contrast/agent/request_handler.rb +5 -10
  46. data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +1 -1
  47. data/lib/contrast/agent/thread_watcher.rb +35 -1
  48. data/lib/contrast/agent/version.rb +1 -1
  49. data/lib/contrast/agent.rb +6 -0
  50. data/lib/contrast/api/communication/connection_status.rb +15 -0
  51. data/lib/contrast/components/agent.rb +34 -0
  52. data/lib/contrast/components/api.rb +23 -0
  53. data/lib/contrast/components/app_context.rb +23 -3
  54. data/lib/contrast/components/assess.rb +34 -4
  55. data/lib/contrast/components/assess_rules.rb +18 -0
  56. data/lib/contrast/components/base.rb +40 -0
  57. data/lib/contrast/components/config/sources.rb +95 -0
  58. data/lib/contrast/components/config.rb +18 -1
  59. data/lib/contrast/components/heap_dump.rb +10 -0
  60. data/lib/contrast/components/inventory.rb +15 -2
  61. data/lib/contrast/components/logger.rb +18 -0
  62. data/lib/contrast/components/polling.rb +36 -0
  63. data/lib/contrast/components/protect.rb +48 -1
  64. data/lib/contrast/components/ruby_component.rb +15 -0
  65. data/lib/contrast/components/sampling.rb +70 -13
  66. data/lib/contrast/components/security_logger.rb +13 -0
  67. data/lib/contrast/components/settings.rb +74 -7
  68. data/lib/contrast/config/certification_configuration.rb +14 -0
  69. data/lib/contrast/config/config.rb +46 -0
  70. data/lib/contrast/config/diagnostics.rb +114 -0
  71. data/lib/contrast/config/diagnostics_tools.rb +98 -0
  72. data/lib/contrast/config/effective_config.rb +65 -0
  73. data/lib/contrast/config/effective_config_value.rb +32 -0
  74. data/lib/contrast/config/exception_configuration.rb +12 -0
  75. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  76. data/lib/contrast/config/protect_rules_configuration.rb +8 -7
  77. data/lib/contrast/config/request_audit_configuration.rb +13 -0
  78. data/lib/contrast/config/server_configuration.rb +41 -2
  79. data/lib/contrast/configuration.rb +28 -2
  80. data/lib/contrast/extension/assess/erb.rb +1 -1
  81. data/lib/contrast/utils/assess/event_limit_utils.rb +31 -9
  82. data/lib/contrast/utils/assess/trigger_method_utils.rb +5 -4
  83. data/lib/contrast/utils/hash_digest.rb +2 -2
  84. data/lib/contrast/utils/input_classification_base.rb +1 -2
  85. data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +81 -0
  86. data/lib/contrast/utils/routes_sent.rb +60 -0
  87. data/lib/contrast/utils/telemetry_client.rb +1 -2
  88. data/lib/contrast/utils/timer.rb +16 -0
  89. data/lib/contrast.rb +3 -1
  90. data/ruby-agent.gemspec +5 -1
  91. metadata +29 -20
  92. data/lib/contrast/agent/assess/contrast_event.rb +0 -157
  93. data/lib/contrast/agent/assess/events/event_factory.rb +0 -34
  94. data/lib/contrast/agent/assess/events/source_event.rb +0 -46
  95. data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +0 -36
@@ -5,9 +5,17 @@ require 'contrast/agent/reporting/reporting_events/finding_event_object'
5
5
  require 'contrast/agent/reporting/reporting_events/finding_event_parent_object'
6
6
  require 'contrast/agent/reporting/reporting_events/finding_event_property'
7
7
  require 'contrast/agent/reporting/reporting_events/finding_event_signature'
8
- require 'contrast/agent/reporting/reporting_events/finding_event_source'
9
8
  require 'contrast/agent/reporting/reporting_events/finding_event_stack'
10
9
  require 'contrast/agent/reporting/reporting_events/finding_event_taint_range'
10
+ require 'contrast/agent/reporting/reporting_events/finding_event_source'
11
+ require 'contrast/agent/assess/contrast_object'
12
+ require 'contrast/utils/assess/tracking_util'
13
+ require 'contrast/utils/class_util'
14
+ require 'contrast/utils/duck_utils'
15
+ require 'contrast/utils/object_share'
16
+ require 'contrast/utils/stack_trace_utils'
17
+ require 'contrast/utils/string_utils'
18
+ require 'contrast/utils/timer'
11
19
  require 'contrast/components/logger'
12
20
 
13
21
  module Contrast
@@ -24,23 +32,39 @@ module Contrast
24
32
  # TAG, TRIGGER.
25
33
  attr_reader :action
26
34
  # @return [Array<Contrast::Agent::Reporting::FindingEventObject>] the arguments passed to the method.
35
+ attr_reader :reportable_args
36
+ # @return [Array<Contrast::Agent::Assess::ContrastObject, nil>] the safe representation of the Arguments with
37
+ # which the method was invoked
27
38
  attr_reader :args
28
39
  # @return [nil] unused.
29
40
  attr_reader :code
30
41
  # @return [Integer] the id of this event.
31
42
  attr_reader :event_id
32
- # @return [Array<Contrast::Agent::Reporting::FindingEventSource>] the source of taint
43
+ # @return [Array<Contrast::Agent::Reporting::EventSource>] the source of taint
33
44
  attr_reader :event_sources
45
+ # @return [String, nil]
46
+ attr_reader :source_type
47
+ # @return [String, nil]
48
+ attr_reader :source_name
34
49
  # @return [nil] unused.
35
50
  attr_reader :field_name
36
51
  # @return [Contrast::Agent::Reporting::FindingEventObject] the object this method was invoked on.
52
+ attr_reader :reportable_object
53
+ # @return [Contrast::Agent::Request, nil] our wrapper around the Rack::Request for this context
54
+ attr_reader :request
55
+ # @return [Contrast::Agent::Assess::ContrastObject] the safe representation of the Object on which the method
56
+ # was invoked
37
57
  attr_reader :object
38
- # @@return [Array<Contrast::Agent::Reporting::FindingEventParentObject>] the ids of all the events directly
58
+ # @return [Array<Contrast::Agent::Reporting::FindingEventParentObject>] the ids of all the events directly
39
59
  # preceding this
40
60
  attr_reader :parent_object_ids
61
+ # @return [Contrast::Agent::Assess::Policy::PolicyNode] the node that governs this event.
62
+ attr_reader :policy_node
41
63
  # @return [Array<Contrast::Agent::Reporting::FindingEventProperty>]
42
64
  attr_reader :properties
43
65
  # @return [Contrast::Agent::Reporting::FindingEventObject] the return of the method.
66
+ attr_reader :reportable_ret
67
+ # @return [Contrast::Agent::Assess::ContrastObject] the safe representation of the Return of the invoked method
44
68
  attr_reader :ret
45
69
  # @return [Contrast::Agent::Reporting::FindingEventSignature] the signature of the method.
46
70
  attr_reader :signature
@@ -49,6 +73,8 @@ module Contrast
49
73
  # @return [Array<Contrast::Agent::Reporting::FindingEventStack>]
50
74
  attr_reader :stack
51
75
  # @return [String] comma separated list of descriptions of what's happened to the data
76
+ attr_reader :reportable_tags
77
+ # @return [Hash<Contrast::Agent::Assess::Tag>]
52
78
  attr_reader :tags
53
79
  # @return [Array<Contrast::Agent::Reporting::FindingEventTaintRange>] the tags and spans of the source that are
54
80
  # tracked
@@ -61,6 +87,70 @@ module Contrast
61
87
  attr_reader :time
62
88
  # @return [String] the type of event; METHOD, PROPAGATION, TAG
63
89
  attr_reader :type
90
+ # @return [Array<String>] the execution stack at the time the method for this event was invoked
91
+ attr_reader :stack_trace
92
+
93
+ # Creates new FindingEvent.
94
+ #
95
+ # @param event_data [Contrast::Agent::Assess::Events::EventData]
96
+ # @param source_type [String, nil] the type of this source, from the
97
+ # source_node, or a KEY_TYPE if invoked for a map,
98
+ # @param source_name [String, nil] the name of this source, i.e.
99
+ # the key used to accessed if from a map or nil if a type like,
100
+ # @return [Contrast::Agent::Reporting::FindingEvent]
101
+ def initialize event_data = nil, source_type = nil, source_name = nil
102
+ @event_sources = []
103
+ @stack = []
104
+ @time = Contrast::Utils::Timer.now_ms
105
+ @thread = Thread.current.object_id.to_s
106
+ @event_id = Contrast::Agent::Reporting::FindingEvent.next_atomic_id
107
+ initialize_routine(event_data, source_type, source_name)
108
+ end
109
+
110
+ # Init routine to find parents events, capture stack trace and retrieve object, args, ret and properties.
111
+ #
112
+ # @param event_data [Contrast::Agent::Assess::Events::EventData]
113
+ # @param source_type [String, nil] the type of this source, from the
114
+ # source_node, or a KEY_TYPE if invoked for a map,
115
+ # @param source_name [String, nil] the name of this source, i.e.
116
+ # the key used to accessed if from a map or nil if a type like,
117
+ def initialize_routine event_data, source_type = nil, source_name = nil
118
+ return unless event_data&.cs__is_a?(Contrast::Agent::Assess::Events::EventData)
119
+
120
+ # Initialize source event:
121
+ if event_data.policy_node.cs__class == Contrast::Agent::Assess::Policy::SourceNode
122
+ build_source_event(source_type, source_name)
123
+ end
124
+
125
+ @policy_node = event_data.policy_node
126
+ @tags = Contrast::Agent::Assess::Tracker.properties(event_data.tagged)&.get_tags
127
+ find_parent_events!(event_data.policy_node, event_data.object, event_data.ret, event_data.args)
128
+ snapshot!(event_data.object, event_data.ret, event_data.args)
129
+ display_params!
130
+ capture_stacktrace!
131
+ stack!
132
+ properties!
133
+ # following methods must be called after snapshot!
134
+ dataflow!
135
+ @signature = Contrast::Agent::Reporting::FindingEventSignature.new(policy_node, args, ret)
136
+ end
137
+
138
+ # We need this to track the parent id's of events to build up a flow chart of the finding
139
+ @atomic_id = 0
140
+ @atomic_mutex = Mutex.new
141
+ def self.next_atomic_id
142
+ @atomic_mutex.synchronize do
143
+ @atomic_id += 1
144
+ # Rollover things
145
+ rescue StandardError
146
+ @atomic_id = 1
147
+ end
148
+ end
149
+
150
+ # @return [Array<Contrast::Agent::Reporting::FindingEvent>]
151
+ def parent_events
152
+ @_parent_events ||= []
153
+ end
64
154
 
65
155
  class << self
66
156
  # Find all the events leading up to the given source and return an array of FindingEvents
@@ -73,21 +163,13 @@ module Contrast
73
163
  build_events([], props.event) if props.event
74
164
  end
75
165
 
76
- # @param event [Contrast::Agent::Assess::ContrastEvent]
77
- # @return [Contrast::Agent::Reporting::FindingEvent]
78
- def convert event
79
- report = new
80
- report.attach_data(event)
81
- report
82
- end
83
-
84
166
  private
85
167
 
86
168
  # Pull the parent events from the given event, generating an array of FindingEvents, passing back the given
87
169
  # array of events after populating it.
88
170
  #
89
171
  # @param events [Array<Contrast::Agent::Reporting::FindingEvent>]
90
- # @param event [Contrast::Agent::Assess::ContrastEvent]
172
+ # @param event [Contrast::Agent::Reporting::FindingEvent]
91
173
  # @return [Array<Contrast::Agent::Reporting::FindingEvent>]
92
174
  def build_events events, event
93
175
  return unless event
@@ -95,32 +177,11 @@ module Contrast
95
177
  event.parent_events&.each do |parent_event|
96
178
  build_events(events, parent_event)
97
179
  end
98
- events << convert(event)
180
+ events << event
99
181
  events
100
182
  end
101
183
  end
102
184
 
103
- def initialize
104
- @event_sources = []
105
- end
106
-
107
- # Parse the data from a Contrast::Agent::Assess::ContrastEvent to attach what is required for reporting to
108
- # TeamServer to this Contrast::Agent::Reporting::FindingEvent
109
- #
110
- # @param event [Contrast::Agent::Assess::ContrastEvent]
111
- def attach_data event
112
- @event_id = event.event_id
113
- @time = event.time.to_i
114
- @thread = event.thread.to_s
115
- display_params!(event)
116
- dataflow!(event)
117
- event_sources!(event)
118
- @signature = Contrast::Agent::Reporting::FindingEventSignature.convert(event)
119
- stack!(event)
120
- parent_ids!(event)
121
- properties!(event)
122
- end
123
-
124
185
  # Convert the instance variables on the class, and other information, into the identifiers required for
125
186
  # TeamServer to process the JSON form of this message.
126
187
  #
@@ -136,19 +197,19 @@ module Contrast
136
197
 
137
198
  {
138
199
  action: action,
139
- args: args.map(&:to_controlled_hash),
200
+ args: reportable_args.map(&:to_controlled_hash),
140
201
  # code: code, # Unused by our agent
141
202
  objectId: event_id,
142
203
  eventSources: event_sources.map(&:to_controlled_hash),
143
204
  # fieldName: field_name, # Unused by our agent
144
- object: object.to_controlled_hash,
205
+ object: reportable_object.to_controlled_hash,
145
206
  parentObjectIds: parent_object_ids.map(&:to_controlled_hash),
146
207
  properties: properties.map(&:to_controlled_hash),
147
- ret: ret&.to_controlled_hash,
208
+ ret: reportable_ret&.to_controlled_hash,
148
209
  signature: signature.to_controlled_hash,
149
210
  source: source || '',
150
211
  stack: stack.map(&:to_controlled_hash),
151
- tags: tags.join(','),
212
+ tags: reportable_tags.join(','),
152
213
  taintRanges: taint_ranges.map(&:to_controlled_hash),
153
214
  target: target || '',
154
215
  thread: thread,
@@ -165,13 +226,22 @@ module Contrast
165
226
 
166
227
  private
167
228
 
229
+ # @param source_type [String, nil] the type of this source, from the
230
+ # source_node, or a KEY_TYPE if invoked for a map,
231
+ # @param source_name [String, nil] the name of this source, i.e.
232
+ # the key used to accessed if from a map or nil if a type like,
233
+ def build_source_event source_type = nil, source_name = nil
234
+ @source_type = Contrast::Utils::StringUtils.force_utf8(source_type)
235
+ @source_name = Contrast::Utils::StringUtils.force_utf8(source_name)
236
+ @event_sources << Contrast::Agent::Reporting::FindingEventSource.new(@source_type, @source_name)
237
+ @request = Contrast::Agent::REQUEST_TRACKER.current&.request
238
+ end
239
+
168
240
  # Find the action and type of this event, used by TeamServer to display type of the event and the
169
241
  # transformation if made.
170
- #
171
- # @param event [Contrast::Agent::Assess::ContrastEvent]
172
- def display_params! event
173
- @action = event.policy_node.build_action
174
- @type = case event.policy_node.node_type
242
+ def display_params!
243
+ @action = policy_node.build_action
244
+ @type = case policy_node.node_type
175
245
  when :TYPE_TAG
176
246
  'TAG'
177
247
  when :TYPE_PROPAGATION
@@ -182,65 +252,59 @@ module Contrast
182
252
  end
183
253
 
184
254
  # Build the dataflow components of this FindingEvent.
185
- #
186
- # @param event [Contrast::Agent::Assess::ContrastEvent]
187
- def dataflow! event
188
- taint_target = taint_target!(event)
255
+ def dataflow!
256
+ taint_target = taint_target!
189
257
  truncate_obj = Contrast::Utils::ObjectShare::OBJECT_KEY != taint_target
190
- @object = Contrast::Agent::Reporting::FindingEventObject.convert(event.object, truncate_obj)
258
+ @reportable_object = Contrast::Agent::Reporting::FindingEventObject.convert(@object, truncate_obj)
191
259
  truncate_ret = Contrast::Utils::ObjectShare::RETURN_KEY != taint_target
192
- @ret = Contrast::Agent::Reporting::FindingEventObject.convert(event.ret, truncate_ret)
193
- event_args!(event, taint_target)
194
- taint_ranges!(event)
260
+ @reportable_ret = Contrast::Agent::Reporting::FindingEventObject.convert(@ret, truncate_ret)
261
+ event_args!(taint_target)
262
+ taint_ranges!
195
263
  end
196
264
 
197
265
  # Convert the arguments of the given ContrastEvent to the reportable form for this FindingEvent. We'll
198
266
  # truncate any argument that isn't the taint target of this event.
199
267
  #
200
- # @param event [Contrast::Agent::Assess::ContrastEvent]
201
268
  # @param taint_target [String,nil] the target of the taint in this event.
202
- def event_args! event, taint_target
203
- @args = []
269
+ def event_args!taint_target
270
+ @reportable_args = []
271
+ return unless args
272
+
204
273
  idx = 0
205
- while idx < event.args.length
206
- @args << Contrast::Agent::Reporting::FindingEventObject.convert(event.args[idx], taint_target != idx)
274
+ while idx < args.length
275
+ @reportable_args << Contrast::Agent::Reporting::FindingEventObject.convert(args[idx], taint_target != idx)
207
276
  idx += 1
208
277
  end
209
278
  end
210
279
 
211
- # Convert the sources of the event to sources here
212
- #
213
- # @param event [Contrast::Agent::Assess::ContrastEvent]
214
- def event_sources! event
215
- return unless event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
216
-
217
- event_sources << event.event_source if event.event_source
218
- end
219
-
220
280
  # Convert the parent id's of the given ContrastEvent to the reportable form for this FindingEvent.
221
- #
222
- # @param event [Contrast::Agent::Assess::ContrastEvent]
223
- def parent_ids! event
281
+ def parent_ids!
224
282
  @parent_object_ids = []
225
- event.parent_events&.each do |parent_event|
283
+ parent_events&.each do |parent_event|
226
284
  parent_object_ids << Contrast::Agent::Reporting::FindingEventParentObject.new(parent_event.event_id.to_i)
227
285
  end
228
286
  end
229
287
 
230
- # Convert the properties of the given ContrastEvent to the reportable form for this FindingEvent.
288
+ # A set of properties that pertain to this event. This is not required for all rules.
289
+ # Dataflow rules can safely omit this property from their report. The properties are set
290
+ # by properties based rules on build_evidence method ( evidence - legacy, now new rules use properties).
291
+ # It returns a key-value pair of properties on each violated rule. The build finding does not create event,
292
+ # since it checks for violation and if there is a violation in the response it is reported, and properties
293
+ # are populated only for Contrast::Agent::Reporting::Finding.
231
294
  # TODO: RUBY-99999 How do properties get to events? Do they? I thought Stored-XSS needed it.
232
295
  #
233
- # @param _event [Contrast::Agent::Assess::ContrastEvent]
234
- def properties! _event
296
+ # @return [Array<Contrast::Agent::Reporting::FindingEventProperty>]
297
+ def properties!
235
298
  @properties = []
236
299
  end
237
300
 
238
- # Convert the stack of the given ContrastEvent to the reportable form for this FindingEvent.
301
+ # Capture stack traces only as configured. We'll use this to grab the start of the call stack as if the
302
+ # instrumented method were the caller. This means we'll start at the entry just after the first block of
303
+ # Contrast code.
239
304
  #
240
- # @param event [Contrast::Agent::Assess::ContrastEvent]
241
- def stack! event
242
- @stack = if event.stack_trace
243
- event.stack_trace.compact.map! do |stack_event|
305
+ def stack!
306
+ @stack = if stack_trace
307
+ stack_trace.compact.map! do |stack_event|
244
308
  Contrast::Agent::Reporting::FindingEventStack.new(stack_event)
245
309
  end
246
310
  else
@@ -249,13 +313,13 @@ module Contrast
249
313
  end
250
314
 
251
315
  # Convert the taint ranges of the given ContrastEvent to the reportable form for this FindingEvent.
252
- #
253
- # @param event [Contrast::Agent::Assess::ContrastEvent]
254
- def taint_ranges! event
255
- @tags = []
316
+ def taint_ranges!
317
+ @reportable_tags = []
256
318
  @taint_ranges = []
257
- event&.tags&.each_pair do |tag_key, tag_ranges|
258
- tags << tag_key
319
+ return unless tags
320
+
321
+ tags.each_pair do |tag_key, tag_ranges|
322
+ reportable_tags << tag_key
259
323
  tag_ranges.each do |range|
260
324
  taint_ranges << Contrast::Agent::Reporting::FindingEventTaintRange.convert(range)
261
325
  end
@@ -264,16 +328,98 @@ module Contrast
264
328
 
265
329
  # Find the source and target for this FindingEvent based on the provided event.
266
330
  #
267
- # @param event [Contrast::Agent::Assess::ContrastEvent]
268
331
  # @return [String,nil] the target of the taint in this event.
269
- def taint_target! event
270
- return unless (node = event.policy_node)
332
+ def taint_target!
333
+ return unless policy_node
334
+
335
+ @source = policy_node.source_string if policy_node.source_string
336
+ @target = policy_node.target_string if policy_node.target_string
337
+ return policy_node.targets[0] if policy_node.targets&.any?
338
+
339
+ policy_node.sources[0] if policy_node.sources&.any?
340
+ end
341
+
342
+ # Parent events are the events of all the sources of this event which were tracked prior to this event
343
+ # occurring. Depending on which, if any of the sources were tracked, there may be more than one parent.
344
+ #
345
+ # All events except for [Contrast::Agent::Assess::Events::SourceEvent] will have at least one parent.
346
+ #
347
+ # We set those events to this event's instance variables.
348
+ #
349
+ # @param policy_node [Contrast::Agent::Assess::Policy::PolicyNode] the node that governs this event.
350
+ # @param object [Object] the Object on which the method was invoked
351
+ # @param ret [Object] the Return of the invoked method
352
+ # @param args [Array<Object>] the Arguments with which the method was invoked
353
+ # @return event [Contrast::Agent::Reporting::FindingEvent]
354
+ def find_parent_events! policy_node, object, ret, args
355
+ policy_node.sources.each do |source_marker|
356
+ source = value_of_source(source_marker, object, ret, args)
357
+ next unless source
271
358
 
272
- @source = node.source_string if node.source_string
273
- @target = node.target_string if node.target_string
274
- return node.targets[0] if node.targets&.any?
359
+ event = Contrast::Agent::Assess::Tracker.properties(source)&.event
360
+ parent_events << event if event
361
+ end
362
+ parent_ids!
363
+ end
364
+
365
+ # @param source [String] the marker for the source type
366
+ # @param object [Object] the Object on which the method was invoked
367
+ # @param ret [Object] the Return of the invoked method
368
+ # @param args [Array<Object>] the Arguments with which the method was invoked
369
+ # @return [Object,nil] the literal value of the source indicated by the given marker
370
+ def value_of_source source, object, ret, args
371
+ case source
372
+ when Contrast::Utils::ObjectShare::OBJECT_KEY
373
+ object
374
+ when Contrast::Utils::ObjectShare::RETURN_KEY
375
+ ret
376
+ else
377
+ args[source]
378
+ end
379
+ end
380
+
381
+ # Everything* is mutable in Ruby. As such, to ensure we can accurately report the application state at the time
382
+ # of this method's invocation, we have to snapshot the given values, making safe representations of them for
383
+ # our later use. We set those safe values to this event's instance variables.
384
+ #
385
+ # @param object [Object] the Object on which the method was invoked
386
+ # @param ret [Object] the Return of the invoked method
387
+ # @param args [Array<Object>] the Arguments with which the method was invoked
388
+ def snapshot! object, ret, args
389
+ @object = Contrast::Agent::Assess::ContrastObject.new(object) if object
390
+ @ret = Contrast::Agent::Assess::ContrastObject.new(ret) if ret
391
+ @args = safe_args_representation(args)
392
+ self
393
+ end
394
+
395
+ # Given an array of arguments, copy them into a safe, meaning String, format that we can use to send to SR and
396
+ # TS for rendering.
397
+ #
398
+ # @param args [Array<Object>] the arguments to translate
399
+ # @return [Array<Contrast::Agent::Assess::ContrastObject>] the String forms of those Objects, as determined by
400
+ # Contrast::Utils::ClassUtil.to_contrast_string
401
+ def safe_args_representation args
402
+ return unless args
403
+ return Contrast::Utils::ObjectShare::EMPTY_ARRAY if args.empty?
404
+
405
+ args.map { |arg| arg ? Contrast::Agent::Assess::ContrastObject.new(arg) : nil }
406
+ end
407
+
408
+ # Capture stack traces only as configured. We'll use this to grab the start of the call stack as if the
409
+ # instrumented method were the caller. This means we'll start at the entry just after the first block of
410
+ # Contrast code.
411
+ def capture_stacktrace!
412
+ # If we're configured to not capture the stacktrace, usually for performance reasons, then don't and return an
413
+ # empty array instead
414
+ unless ::Contrast::ASSESS.capture_stacktrace?(policy_node)
415
+ @stack_trace = Contrast::Utils::ObjectShare::EMPTY_ARRAY
416
+ return
417
+ end
275
418
 
276
- node.sources[0] if node.sources&.any?
419
+ # Otherwise, find where in the stack the application / Ruby code starts
420
+ start = caller(0, 20)&.find_index { |stack| !stack.include?('/lib/contrast') }
421
+ # And then use that to build out the reported stacktrace, or a fallback if we couldn't find it.
422
+ @stack_trace = start ? caller(start + 1, 20) : caller(20, 20)
277
423
  end
278
424
 
279
425
  # @raise [ArgumentError]
@@ -291,11 +437,11 @@ module Contrast
291
437
  raise(ArgumentError, "#{ self } did not have a proper parentObjectIds. Unable to continue.")
292
438
  end
293
439
  raise(ArgumentError, "#{ self } did not have a proper taintRanges. Unable to continue.") unless taint_ranges
294
- raise(ArgumentError, "#{ self } did not have a proper args. Unable to continue.") unless args
295
- raise(ArgumentError, "#{ self } did not have a proper object. Unable to continue.") unless object
440
+ raise(ArgumentError, "#{ self } did not have a proper args. Unable to continue.") unless reportable_args
441
+ raise(ArgumentError, "#{ self } did not have a proper object. Unable to continue.") unless reportable_object
296
442
  raise(ArgumentError, "#{ self } did not have a proper signature. Unable to continue.") unless signature
297
443
  raise(ArgumentError, "#{ self } did not have a proper stack. Unable to continue.") unless stack
298
- raise(ArgumentError, "#{ self } did not have a proper tags. Unable to continue.") unless tags
444
+ raise(ArgumentError, "#{ self } did not have a proper tags. Unable to continue.") unless reportable_tags
299
445
  end
300
446
  end
301
447
  end
@@ -36,38 +36,25 @@ module Contrast
36
36
  # @return [Boolean] if the method is void or not; may be different for each invocation of the method.
37
37
  attr_reader :void_method
38
38
 
39
- class << self
40
- # @param event [Contrast::Agent::Assess::ContrastEvent] the event to build a signature for
41
- # @return [Contrast::Agent::Reporting::FindingEventSignature]
42
- def convert event
43
- report = new
44
- report.attach_data(event)
45
- report
46
- end
47
- end
48
-
49
- # Parse the data from a Contrast::Agent::Assess::ContrastEvent to attach what is required for reporting to
50
- # TeamServer to this Contrast::Agent::Reporting::FindingEventSignature
51
- #
52
- # @param event [Contrast::Agent::Assess::ContrastEvent]
53
- def attach_data event
54
- node = event.policy_node
39
+ # @param policy_node [Contrast::Agent::Assess::Policy::PolicyNode] the node that governs this event.
40
+ # @param args[Array<Contrast::Agent::Assess::ContrastObject, nil>] the safe representation of the Arguments with
41
+ # which the method was invoked
42
+ # @param ret [Contrast::Agent::Assess::ContrastObject] the safe representation of the Return of the
43
+ # invoked method
44
+ def initialize policy_node, args, ret
45
+ node = policy_node
55
46
  @arg_types = []
56
- event.args&.each do |arg|
57
- arg_types << type_name(arg)
58
- end
47
+ args&.each { |arg| arg_types << type_name(arg) }
59
48
  @class_name = node.class_name
60
49
  @constructor = node.method_name == :new || node.method_name == :initialize
61
50
  # 8 is STATIC in Java... we have to placate them for now it has been requested that flags be removed since it
62
51
  # isn't used
63
52
  @flags = 8 unless node.instance_method?
64
53
  @method_name = node.method_name.to_s
65
- @return_type = type_name(event.ret)
54
+ @return_type = type_name(ret)
66
55
  # if there's a ret, then this method isn't nil. not 100% full proof since you can return nil, but this is the
67
56
  # best we've got currently.
68
- @void_method = event.ret.nil? ||
69
- event.ret.object.nil? ||
70
- event.ret.object == Contrast::Utils::ObjectShare::NIL_STRING
57
+ @void_method = ret.nil? || ret.object.nil? || ret.object == Contrast::Utils::ObjectShare::NIL_STRING
71
58
  end
72
59
 
73
60
  # Convert the instance variables on the class, and other information, into the identifiers required for
@@ -15,15 +15,15 @@ module Contrast
15
15
  include Contrast::Components::Logger::InstanceMethods
16
16
 
17
17
  # @return [String] the name of the source
18
- attr_reader :name
18
+ attr_reader :source_name
19
19
  # @return [String] the type of the source
20
- attr_reader :type
20
+ attr_reader :source_type
21
21
 
22
22
  # @param type [String]
23
23
  # @param name [String]
24
24
  def initialize type, name
25
- @type = type
26
- @name = name
25
+ @source_type = type
26
+ @source_name = name
27
27
  end
28
28
 
29
29
  # Convert the instance variables on the class, and other information, into the identifiers required for
@@ -40,8 +40,8 @@ module Contrast
40
40
  end
41
41
 
42
42
  {
43
- sourceName: name, # rubocop:disable Security/Module/Name
44
- sourceType: type
43
+ sourceName: source_name,
44
+ sourceType: source_type
45
45
  }
46
46
  end
47
47
 
@@ -58,14 +58,15 @@ module Contrast
58
58
  end
59
59
 
60
60
  {
61
- name: name, # rubocop:disable Security/Module/Name
62
- type: type
61
+ name: source_name,
62
+ type: source_type
63
63
  }
64
64
  end
65
65
 
66
66
  # @raise [ArgumentError]
67
67
  def validate
68
- raise(ArgumentError, "#{ self } did not have a proper type. Unable to continue.") unless type && !type.empty?
68
+ raise(ArgumentError, "#{ self } did not have a proper type. Unable to continue.") unless source_type &&
69
+ !source_type.empty?
69
70
  end
70
71
  end
71
72
  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 'digest'
4
5
  require 'json'
5
6
  require 'contrast/components/logger'
6
7
  require 'contrast/agent/reporting/reporting_events/application_reporting_event'
@@ -63,6 +64,17 @@ module Contrast
63
64
  }.compact
64
65
  end
65
66
 
67
+ # Generates a hash id for the observed route, based on the sources, signature, verb, and url.
68
+ #
69
+ # @return [String]
70
+ #
71
+ def hash_id
72
+ hashable_data = to_controlled_hash.except(:session_id)
73
+ hashable_data[:sources] = hashable_data[:sources].sort_by { |s| s[:name] }
74
+
75
+ Digest::SHA2.new(256).hexdigest(hashable_data.to_s)
76
+ end
77
+
66
78
  # @raise [ArgumentError]
67
79
  def validate
68
80
  raise(ArgumentError, "#{ self } did not have a proper sources. Unable to continue.") if @sources.nil?
@@ -21,6 +21,14 @@ module Contrast
21
21
  def since_last_update
22
22
  (update_time = Contrast::SETTINGS.last_server_update_ms) ? Contrast::Utils::Timer.now_ms - update_time : 0
23
23
  end
24
+
25
+ # Human readable last time update for header set. Set to 0 if the agent is just starting and have not received
26
+ # the latest header from TS.
27
+ #
28
+ # @return [String]
29
+ def since_last_update_httpdate
30
+ Contrast::SETTINGS.server_settings_last_httpdate || Contrast::Utils::Timer.ms_to_httpdate(0)
31
+ end
24
32
  end
25
33
  end
26
34
  end
@@ -0,0 +1,40 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/agent/reporting/reporting_events/server_reporting_event'
5
+ require 'contrast/agent/reporting/reporting_utilities/endpoints'
6
+ require 'contrast/utils/timer'
7
+
8
+ module Contrast
9
+ module Agent
10
+ module Reporting
11
+ # This class will initialize a GET request to be send to TS. The server settings endpoint is the way the Agent
12
+ # receives server sittings - Protect rules settings, patterns, keywords and deny/allow lists, log setting.
13
+ class ServerSettings < Contrast::Agent::Reporting::ServerReportingEvent
14
+ def initialize
15
+ @event_method = :GET
16
+ @event_endpoint = Contrast::Agent::Reporting::Endpoints.server_settings
17
+ super
18
+ end
19
+
20
+ def file_name
21
+ 'server-settings'
22
+ end
23
+
24
+ # Attach the last server settings received timestamp to the request as it is required.
25
+ #
26
+ # If-Modified-Since: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
27
+ # @param request [Net::HTTPRequest]
28
+ def attach_headers request
29
+ request['If-Modified-Since'] = since_last_update_httpdate
30
+ end
31
+
32
+ # @return [Hash]
33
+ # @raise [ArgumentError]
34
+ def to_controlled_hash
35
+ {}
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end