contrast-agent 6.6.4 → 6.6.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/assess/policy/trigger_method.rb +21 -6
  3. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +2 -0
  4. data/lib/contrast/agent/at_exit_hook.rb +1 -7
  5. data/lib/contrast/agent/inventory/database_config.rb +12 -13
  6. data/lib/contrast/agent/middleware.rb +0 -1
  7. data/lib/contrast/agent/protect/rule/base.rb +16 -20
  8. data/lib/contrast/agent/protect/rule/cmd_injection.rb +5 -4
  9. data/lib/contrast/agent/protect/rule/deserialization.rb +5 -4
  10. data/lib/contrast/agent/protect/rule/path_traversal.rb +9 -7
  11. data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +16 -14
  12. data/lib/contrast/agent/protect/rule/sqli.rb +1 -1
  13. data/lib/contrast/agent/protect/rule/xxe.rb +9 -6
  14. data/lib/contrast/agent/reporting/attack_result/attack_result.rb +8 -0
  15. data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +85 -36
  16. data/lib/contrast/agent/reporting/attack_result/user_input.rb +11 -0
  17. data/lib/contrast/agent/reporting/details/bot_blocker_details.rb +29 -0
  18. data/lib/contrast/agent/reporting/details/cmd_injection_details.rb +30 -0
  19. data/lib/contrast/agent/reporting/details/details.rb +18 -0
  20. data/lib/contrast/agent/reporting/details/http_method_tempering_details.rb +27 -0
  21. data/lib/contrast/agent/reporting/details/ip_denylist_details.rb +27 -0
  22. data/lib/contrast/agent/reporting/details/no_sqli_details.rb +36 -0
  23. data/lib/contrast/agent/reporting/details/path_traversal_details.rb +24 -0
  24. data/lib/contrast/agent/reporting/details/path_traversal_semantic_analysis_details.rb +32 -0
  25. data/lib/contrast/agent/reporting/details/protect_rule_details.rb +17 -0
  26. data/lib/contrast/agent/reporting/details/sqli_details.rb +36 -0
  27. data/lib/contrast/agent/reporting/details/untrusted_deserialization_details.rb +27 -0
  28. data/lib/contrast/agent/reporting/details/virtual_patch_details.rb +24 -0
  29. data/lib/contrast/agent/reporting/details/xss_details.rb +33 -0
  30. data/lib/contrast/agent/reporting/details/xss_match.rb +30 -0
  31. data/lib/contrast/agent/reporting/details/xxe_details.rb +36 -0
  32. data/lib/contrast/agent/reporting/details/xxe_match.rb +25 -0
  33. data/lib/contrast/agent/reporting/details/xxe_wrapper.rb +25 -0
  34. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +1 -1
  35. data/lib/contrast/agent/reporting/masker/masker.rb +78 -65
  36. data/lib/contrast/agent/reporting/masker/masker_utils.rb +1 -30
  37. data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +84 -15
  38. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +13 -25
  39. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_activity.rb +17 -22
  40. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +46 -125
  41. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +5 -16
  42. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +10 -18
  43. data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +6 -14
  44. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +29 -20
  45. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +45 -10
  46. data/lib/contrast/agent/reporting/reporting_utilities/dtm_message.rb +0 -7
  47. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +2 -1
  48. data/lib/contrast/agent/request.rb +2 -0
  49. data/lib/contrast/agent/request_context.rb +13 -4
  50. data/lib/contrast/agent/request_context_extend.rb +59 -40
  51. data/lib/contrast/agent/request_handler.rb +7 -9
  52. data/lib/contrast/agent/service_heartbeat.rb +1 -1
  53. data/lib/contrast/agent/version.rb +1 -1
  54. data/lib/contrast/components/app_context.rb +6 -6
  55. data/lib/contrast/config/assess_configuration.rb +1 -1
  56. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +1 -6
  57. data/lib/contrast/utils/assess/event_limit_utils.rb +26 -7
  58. data/lib/contrast/utils/log_utils.rb +15 -9
  59. metadata +19 -2
@@ -23,6 +23,7 @@ module Contrast
23
23
  include Contrast::Utils::CEFLogUtils
24
24
  include Contrast::Components::Logger::InstanceMethods
25
25
  BUILD_ATTACK_LOGGER_MESSAGE = 'Building attack result from Contrast Service input analysis result'
26
+ CEF_LOGGING_RULES = %w[bot-blocker virtual-patch ip-denylist].cs__freeze
26
27
  # Convert the discovered route for this request to appropriate forms and disseminate it to those locations
27
28
  # where it is necessary for our route coverage and finding vulnerability discovery features to function.
28
29
  #
@@ -34,8 +35,10 @@ module Contrast
34
35
  # For our findings
35
36
  @route = route
36
37
 
38
+ # REMOVE_DTM_ACTIVITY
39
+ #
37
40
  # For SR findings
38
- @activity.routes << route
41
+ @dtm_activity.routes << route
39
42
 
40
43
  # For TS routes
41
44
  @request.route = route
@@ -54,27 +57,14 @@ module Contrast
54
57
  @request.observed_route = @observed_route
55
58
  end
56
59
 
57
- # Collect the results for the given rule with the given action
58
- #
59
- # @param rule [String] the id of the rule to which the results apply
60
- # @param response_type [Symbol] the result of the response, matching a value of
61
- # Contrast::Api::Dtm::AttackResult::ResponseType
62
- # @return [Array<Contrast::Api::Dtm::AttackResult>]
63
- def results_for rule, response_type = nil
64
- if response_type.nil?
65
- activity.results.select { |r| r.rule_id == rule }
66
- else
67
- activity.results.select { |r| r.rule_id == rule && r.response == response_type }
68
- end
69
- end
70
-
71
60
  # @raise [Contrast::SecurityException]
72
61
  def service_extract_request
73
62
  return false unless ::Contrast::AGENT.enabled?
74
63
  return false unless ::Contrast::PROTECT.enabled?
75
64
  return false if @do_not_track
76
65
 
77
- service_response = Contrast::Agent&.messaging_queue&.send_event_immediately(@activity.http_request)
66
+ # REMOVE_DTM_ACTIVITY
67
+ service_response = Contrast::Agent&.messaging_queue&.send_event_immediately(@activity.request.dtm)
78
68
  return false unless service_response
79
69
 
80
70
  handle_protect_state(service_response)
@@ -112,6 +102,7 @@ module Contrast
112
102
  @do_not_track = true unless state.track_request
113
103
  return unless state.security_exception
114
104
 
105
+ # make sure the activity get send before the error
115
106
  # If Contrast Service has NOT handled the input analysis, handle them here
116
107
  build_attack_results(agent_settings)
117
108
  logger.debug('Contrast Service said to block this request')
@@ -126,7 +117,6 @@ module Contrast
126
117
  @response = Contrast::Agent::Response.new(rack_response)
127
118
  return unless @sample_res
128
119
 
129
- #
130
120
  # TODO: RUBY-1376 once all rules translated, move this to if/else w/ the enabled
131
121
  if Contrast::Agent::Reporter.enabled?
132
122
  Contrast::Agent::Assess::Rule::Response::AutoComplete.new.analyze(@response)
@@ -139,7 +129,8 @@ module Contrast
139
129
  Contrast::Agent::Assess::Rule::Response::XContentType.new.analyze(@response)
140
130
  Contrast::Agent::Assess::Rule::Response::XXssProtection.new.analyze(@response)
141
131
  else
142
- activity.http_response = @response.dtm
132
+ # REMOVE_DTM_ACTIVITY
133
+ dtm_activity.http_response = @response.dtm
143
134
  end
144
135
  rescue StandardError => e
145
136
  logger.error('Unable to extract information after request', e)
@@ -147,7 +138,7 @@ module Contrast
147
138
 
148
139
  # This here is for things we don't have implemented
149
140
  def log_to_cef
150
- activity.results.each { |attack_result| logging_logic(attack_result, attack_result.rule_id.downcase) }
141
+ activity.defend.attackers.each { |attacker| logging_logic(attacker.protection_rules) }
151
142
  end
152
143
 
153
144
  # @param input_analysis [Contrast::Api::Settings::InputAnalysis]
@@ -177,7 +168,7 @@ module Contrast
177
168
 
178
169
  results_by_rule.each_pair do |_, attack_result|
179
170
  logger.info('Blocking attack result', rule: attack_result.rule_id)
180
- activity.results << attack_result
171
+ activity.attach_defend(attack_result)
181
172
  end
182
173
  end
183
174
 
@@ -199,28 +190,56 @@ module Contrast
199
190
  end
200
191
  end
201
192
 
202
- def logging_logic result, rule_id
203
- rules = %w[bot_blocker virtual_patch ip_denylist]
204
- return unless rules.include?(rule_id)
205
-
206
- rule_details = Contrast::Api::Dtm::RaspRuleSample.to_controlled_hash(result.samples[0]).fetch(rule_id.to_sym)
207
- outcome = Contrast::Api::Dtm::AttackResult::ResponseType.get_name_by_tag(result.response)
208
- case rule_id
209
- when /bot_blocker/i
210
- blocker_to_json = Contrast::Api::Dtm::BotBlockerDetails.to_controlled_hash(rule_details)
211
- cef_logger.bot_blocking_message(blocker_to_json, outcome)
212
- when /virtual_patch/i
213
- virtual_patch_to_json = Contrast::Api::Dtm::VirtualPatchDetails.to_controlled_hash(rule_details)
214
- cef_logger.virtual_patch_message(virtual_patch_to_json, outcome)
215
- when /ip_denylist/i
216
- sender_ip = extract_sender_ip
217
- ip_denylist_to_json = Contrast::Api::Dtm::IpDenylistDetails.to_controlled_hash(rule_details)
218
- return unless sender_ip
219
- return unless sender_ip.include?(ip_denylist_to_json[:ip])
220
-
221
- cef_logger.ip_denylisted_message(sender_ip, ip_denylist_to_json, outcome)
193
+ # @param protection_rules [Array<rule_id => Contrast::Agent::Reporting::ApplicationDefendAttackActivity>] Array
194
+ # of all protection rules per active attackers of this request life cycle.
195
+ def logging_logic protection_rules
196
+ protection_rules.any? do |rule_id, activity|
197
+ next unless CEF_LOGGING_RULES.include?(rule_id)
198
+
199
+ outcome = activity.response_type
200
+ rule_details = details_builder(outcome, activity)
201
+ case rule_id
202
+ when /bot-blocker/i
203
+ cef_logger.bot_blocking_message(rule_details.to_controlled_hash, outcome) if rule_details
204
+ when /virtual-patch/i
205
+ cef_logger.virtual_patch_message(rule_details.to_controlled_hash, outcome) if rule_details
206
+ when /ip-denylist/i
207
+ sender_ip = extract_sender_ip
208
+ next unless sender_ip
209
+ next unless rule_details && rule_details.ip == sender_ip
210
+
211
+ cef_logger.ip_denylisted_message(sender_ip, rule_details.to_controlled_hash, outcome)
212
+ end
213
+ end
214
+ end
215
+
216
+ # @param outcome [Symbol<Contrast::Agent::Reporting::ResponseType>]
217
+ # @param activity [Contrast::Agent::Reporting::ApplicationDefendAttackActivity]
218
+ def details_builder outcome, activity
219
+ case outcome
220
+ when ::Contrast::Agent::Reporting::ResponseType::BLOCKED
221
+ blocked = activity.blocked
222
+ get_details(blocked)
223
+ when ::Contrast::Agent::Reporting::ResponseType::MONITORED
224
+ exploited = activity.exploited
225
+ get_details(exploited)
226
+ when ::Contrast::Agent::Reporting::ResponseType::PROBED
227
+ ineffective = activity.ineffective
228
+ get_details(ineffective)
229
+ when ::Contrast::Agent::Reporting::ResponseType::SUSPICIOUS
230
+ activity.suspicious.samples[0].details
231
+ suspicious = activity.suspicious
232
+ get_details(suspicious)
222
233
  end
223
234
  end
235
+
236
+ # @param type [Contrast::Agent::Reporting::ApplicationDefendAttackSampleActivity]
237
+ # @return details [Contrast::Agent::Reporting::ProtectRuleDetails] depends on rule
238
+ def get_details type
239
+ sample = nil
240
+ sample = type.samples[0] unless type.samples.empty?
241
+ sample&.details
242
+ end
224
243
  end
225
244
  end
226
245
  end
@@ -22,13 +22,6 @@ module Contrast
22
22
  @ruleset = ::Contrast::AGENT.ruleset
23
23
  end
24
24
 
25
- # Send Activities messages to TS [Contrast::Api::Dtm::Activity]
26
- # TODO: RUBY-1704
27
- # TODO: RUBY-1438
28
- def send_activity_messages
29
- Contrast::Agent.messaging_queue&.send_event_eventually(context.activity)
30
- end
31
-
32
25
  # reports events[Contrast::Agent::Reporting::ReporterEvent] to TS
33
26
  # This method is used to send our JSON messages directly to TeamServer at the end of each request. As we move
34
27
  # more endpoints over, this method will take the messages originally sent by #send_actiivty_messages. At the end,
@@ -37,11 +30,16 @@ module Contrast
37
30
  return unless (reporter = Contrast::Agent.reporter)
38
31
 
39
32
  reporter.send_event(context.observed_route)
40
- return unless Contrast::Agent::Reporter.enabled?
33
+ # return unless Contrast::Agent::Reporter.enabled?
34
+
35
+ # REMOVE_DTM_ACTIVITY after reporter routes, responses, findings are implemented
36
+ #
37
+ # This reports routes, findings and traces with response dependent rules.
38
+ Contrast::Agent.messaging_queue&.send_event_eventually(context.dtm_activity)
41
39
 
42
40
  # Mask Sensitive Data
43
41
  Contrast::Agent::Reporting::Masker.mask(context.activity)
44
- event = Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.activity)
42
+ event = context.activity
45
43
  reporter.send_event(event)
46
44
  end
47
45
 
@@ -19,7 +19,7 @@ module Contrast
19
19
  @_thread = Contrast::Agent::Thread.new do
20
20
  logger.info('Starting heartbeat thread.')
21
21
  loop do
22
- logger.info("Queue Size: #{ Contrast::Agent.messaging_queue.queue&.length }")
22
+ logger.info("Queue Size: #{ Contrast::Agent.messaging_queue&.queue&.length }")
23
23
  Contrast::Agent.messaging_queue&.send_event_eventually(poll_message)
24
24
  clean_properties
25
25
  sleep(REFRESH_INTERVAL_SEC)
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '6.6.4'
6
+ VERSION = '6.6.5'
7
7
  end
8
8
  end
@@ -27,17 +27,17 @@ module Contrast
27
27
  DEFAULT_SERVER_PATH = '/'
28
28
 
29
29
  # @return [String]
30
- attr_reader :version
30
+ attr_accessor :version
31
31
  # @return [String]
32
- attr_reader :language
32
+ attr_accessor :language
33
33
  # @return [String]
34
- attr_reader :group
34
+ attr_accessor :group
35
35
  # @return [String]
36
- attr_reader :tags
36
+ attr_accessor :tags
37
37
  # @return [String]
38
- attr_reader :code
38
+ attr_accessor :code
39
39
  # @return [String]
40
- attr_reader :metadata
40
+ attr_accessor :metadata
41
41
 
42
42
  def initialize hsh = {}
43
43
  original_pid
@@ -19,7 +19,7 @@ module Contrast
19
19
  DEFAULT_STACKTRACES = 'ALL'
20
20
  DEFAULT_MAX_SOURCE_EVENTS = 50_000
21
21
  DEFAULT_MAX_PROPAGATION_EVENTS = 50_000
22
- DEFAULT_MAX_RULE_REPORTED = 50_000
22
+ DEFAULT_MAX_RULE_REPORTED = 100
23
23
  DEFAULT_MAX_RULE_TIME_THRESHOLD = 300_000
24
24
 
25
25
  def initialize hsh = {}
@@ -20,12 +20,7 @@ module Contrast
20
20
  Contrast::Agent.reporter&.send_event_immediately(event)
21
21
  end
22
22
 
23
- if Contrast::Agent::Reporter.enabled?
24
- event = Contrast::Agent::Reporting::DtmMessage.dtm_to_event(context.activity)
25
- Contrast::Agent.reporter&.send_event_immediately(event)
26
- else
27
- Contrast::Agent.messaging_queue&.send_event_immediately(context.activity)
28
- end
23
+ Contrast::Agent.reporter&.send_event_immediately(context.activity)
29
24
  end
30
25
 
31
26
  def instrument
@@ -30,16 +30,29 @@ module Contrast
30
30
  false # policy does not have limit
31
31
  end
32
32
 
33
- def event_limit_for_rule? rule_id
34
- if Contrast::Utils::Timer.now_ms > threshold_time_limit
35
- @_rule_counts = nil
36
- @_threshold_time_limit = nil
33
+ def event_limit_for_rule? rule_id # rubocop:disable Metrics/AbcSize
34
+ return false unless (context = Contrast::Agent::REQUEST_TRACKER.current)
35
+
36
+ saved_request_ids = rule_counts.keys.map { |k| k.to_s.split('_')[1] }
37
+
38
+ # if we passed the threshold and we actually have records for that request - wipe them
39
+ if saved_request_ids.uniq.include?(context.request.__id__)
40
+ restore_defaults
37
41
  threshold_time_limit
38
42
  end
39
- rule_counts[rule_id] += 1
43
+
44
+ # if we have recorded rule counts, but none of them are for the current request_id
45
+ # 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__)
47
+ restore_defaults
48
+ threshold_time_limit
49
+ end
50
+
51
+ rule_key = "#{ rule_id }_#{ context.request.__id__ }"
52
+ rule_counts[rule_key] += 1
40
53
  # TODO: RUBY-1680 remove default
41
- rule_counts[rule_id] >=
42
- (::Contrast::ASSESS.max_rule_reported || Contrast::Config::AssessConfiguration::DEFAULT_MAX_RULE_REPORTED)
54
+ # we don't need the default here because we either return from the config, or we return the default
55
+ rule_counts[rule_key] >= ::Contrast::ASSESS.max_rule_reported
43
56
  end
44
57
 
45
58
  # Increments the event count for the type of event that is being tracked
@@ -90,6 +103,12 @@ module Contrast
90
103
  def threshold_time_limit
91
104
  @_threshold_time_limit ||= Contrast::Utils::Timer.now_ms + (::Contrast::ASSESS.time_limit_threshold || 0)
92
105
  end
106
+
107
+ # @return nil
108
+ def restore_defaults
109
+ @_rule_counts = nil
110
+ @_threshold_time_limit = nil
111
+ end
93
112
  end
94
113
  end
95
114
  end
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'socket'
5
5
  require 'contrast/agent/version'
6
+ require 'contrast/utils/object_share'
6
7
  require 'contrast/logger/aliased_logging'
7
8
 
8
9
  module Contrast
@@ -175,13 +176,10 @@ module Contrast
175
176
  # initially here we will use case to add it
176
177
  def extract_metadata rule_id = nil, outcome = nil
177
178
  message = []
178
- sender_info = context&.activity&.http_request&.sender
179
+ request = context&.activity&.request
180
+ sender_info = { ip: request&.ip || Contrast::Utils::ObjectShare::EMPTY_STRING, port: request&.port || 0 }
179
181
  rule_id ? message << "pri=#{ rule_id } " : 'asd'
180
- request_method = if context.request.rack_request.env['REQUEST_METHOD'].length.positive?
181
- context.request.rack_request.env['REQUEST_METHOD']
182
- else
183
- DEFAULT_METADATA
184
- end
182
+ request_method = assign_request_method(context)
185
183
  app_name = ::Contrast::APP_CONTEXT.name # rubocop:disable Security/Module/Name
186
184
  attach_request_and_sender_info(message, sender_info)
187
185
  message << "request=#{ context.request.url } "
@@ -198,10 +196,10 @@ module Contrast
198
196
  src = if needed_header
199
197
  needed_header
200
198
  else
201
- sender_info.ip.length > 1 ? sender_info.ip : DEFAULT_METADATA
199
+ sender_info[:ip].length > 1 ? sender_info[:ip] : DEFAULT_METADATA
202
200
  end
203
201
  message << "src=#{ src }"
204
- message << "port=#{ sender_info.port }"
202
+ message << "port=#{ sender_info[:port] }"
205
203
  end
206
204
 
207
205
  def extract_ip_address
@@ -216,9 +214,17 @@ module Contrast
216
214
  end
217
215
 
218
216
  def extract_sender_ip
219
- request_headers = context.activity.http_request.request_headers&.transform_keys(&:to_s)
217
+ request_headers = context.activity.request.headers&.transform_keys(&:to_s)
220
218
  request_headers['X-Forwarded-For']
221
219
  end
220
+
221
+ def assign_request_method context
222
+ if context.request.rack_request.env['REQUEST_METHOD'].length.positive?
223
+ context.request.rack_request.env['REQUEST_METHOD']
224
+ else
225
+ DEFAULT_METADATA
226
+ end
227
+ end
222
228
  end
223
229
  end
224
230
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contrast-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.6.4
4
+ version: 6.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - galen.palmer@contrastsecurity.com
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: exe
15
15
  cert_chain: []
16
- date: 2022-07-20 00:00:00.000000000 Z
16
+ date: 2022-08-04 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: bundler
@@ -1026,6 +1026,23 @@ files:
1026
1026
  - lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb
1027
1027
  - lib/contrast/agent/reporting/attack_result/response_type.rb
1028
1028
  - lib/contrast/agent/reporting/attack_result/user_input.rb
1029
+ - lib/contrast/agent/reporting/details/bot_blocker_details.rb
1030
+ - lib/contrast/agent/reporting/details/cmd_injection_details.rb
1031
+ - lib/contrast/agent/reporting/details/details.rb
1032
+ - lib/contrast/agent/reporting/details/http_method_tempering_details.rb
1033
+ - lib/contrast/agent/reporting/details/ip_denylist_details.rb
1034
+ - lib/contrast/agent/reporting/details/no_sqli_details.rb
1035
+ - lib/contrast/agent/reporting/details/path_traversal_details.rb
1036
+ - lib/contrast/agent/reporting/details/path_traversal_semantic_analysis_details.rb
1037
+ - lib/contrast/agent/reporting/details/protect_rule_details.rb
1038
+ - lib/contrast/agent/reporting/details/sqli_details.rb
1039
+ - lib/contrast/agent/reporting/details/untrusted_deserialization_details.rb
1040
+ - lib/contrast/agent/reporting/details/virtual_patch_details.rb
1041
+ - lib/contrast/agent/reporting/details/xss_details.rb
1042
+ - lib/contrast/agent/reporting/details/xss_match.rb
1043
+ - lib/contrast/agent/reporting/details/xxe_details.rb
1044
+ - lib/contrast/agent/reporting/details/xxe_match.rb
1045
+ - lib/contrast/agent/reporting/details/xxe_wrapper.rb
1029
1046
  - lib/contrast/agent/reporting/input_analysis/input_analysis.rb
1030
1047
  - lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb
1031
1048
  - lib/contrast/agent/reporting/input_analysis/input_type.rb