contrast-agent 6.6.4 → 6.6.5

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 (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