contrast-agent 7.5.0 → 7.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__common/cs__common.c +5 -5
  3. data/ext/cs__contrast_patch/cs__contrast_patch.c +2 -1
  4. data/ext/cs__scope/cs__scope.c +6 -5
  5. data/lib/contrast/agent/assess/events/event_data.rb +11 -2
  6. data/lib/contrast/agent/assess/finalizers/freeze.rb +1 -0
  7. data/lib/contrast/agent/assess/finalizers/hash.rb +7 -0
  8. data/lib/contrast/agent/assess/policy/patcher.rb +2 -0
  9. data/lib/contrast/agent/assess/policy/policy.rb +4 -0
  10. data/lib/contrast/agent/assess/policy/policy_node.rb +29 -7
  11. data/lib/contrast/agent/assess/policy/preshift.rb +34 -1
  12. data/lib/contrast/agent/assess/policy/propagation_method.rb +16 -1
  13. data/lib/contrast/agent/assess/policy/propagation_node.rb +40 -1
  14. data/lib/contrast/agent/assess/policy/propagator/append.rb +5 -0
  15. data/lib/contrast/agent/assess/policy/propagator/base.rb +10 -0
  16. data/lib/contrast/agent/assess/policy/propagator/buffer.rb +6 -0
  17. data/lib/contrast/agent/assess/policy/propagator/center.rb +14 -0
  18. data/lib/contrast/agent/assess/policy/propagator/custom.rb +6 -0
  19. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +14 -0
  20. data/lib/contrast/agent/assess/policy/propagator/insert.rb +6 -0
  21. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +38 -0
  22. data/lib/contrast/agent/assess/policy/propagator/next.rb +6 -0
  23. data/lib/contrast/agent/assess/policy/propagator/prepend.rb +5 -0
  24. data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -0
  25. data/lib/contrast/agent/assess/policy/propagator/replace.rb +5 -0
  26. data/lib/contrast/agent/assess/policy/propagator/reverse.rb +5 -0
  27. data/lib/contrast/agent/assess/policy/propagator/select.rb +30 -0
  28. data/lib/contrast/agent/assess/policy/propagator/splat.rb +10 -0
  29. data/lib/contrast/agent/assess/policy/source_node.rb +5 -1
  30. data/lib/contrast/agent/assess/policy/source_validation/cross_site_validator.rb +4 -0
  31. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +16 -0
  32. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +19 -0
  33. data/lib/contrast/agent/assess/policy/trigger_method.rb +8 -1
  34. data/lib/contrast/agent/assess/policy/trigger_node.rb +11 -1
  35. data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +4 -0
  36. data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +6 -0
  37. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +6 -0
  38. data/lib/contrast/agent/assess/rule/response/base_rule.rb +2 -1
  39. data/lib/contrast/agent/hooks/at_exit_hook.rb +1 -0
  40. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +1 -1
  41. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +35 -7
  42. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +8 -2
  43. data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +1 -6
  44. data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +4 -3
  45. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +22 -12
  46. data/lib/contrast/agent/request/request.rb +1 -1
  47. data/lib/contrast/agent/request/request_handler.rb +1 -0
  48. data/lib/contrast/agent/version.rb +1 -1
  49. data/lib/contrast/configuration.rb +1 -1
  50. data/lib/contrast/utils/hash_digest.rb +0 -14
  51. data/lib/contrast/utils/hash_digest_extend.rb +16 -5
  52. data/lib/contrast/utils/json.rb +1 -1
  53. data/lib/contrast/utils/middleware_utils.rb +9 -0
  54. data/lib/contrast/utils/routes_sent.rb +3 -2
  55. data/lib/contrast.rb +2 -2
  56. data/resources/assess/policy.json +50 -1
  57. data/ruby-agent.gemspec +13 -13
  58. metadata +24 -23
@@ -15,6 +15,13 @@ module Contrast
15
15
  # MatchData#match methods, since the last was introduced after
16
16
  # Ruby 3.1.0, but shares similar functionality, except it does not
17
17
  # support ranges.
18
+ #
19
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
20
+ # propagation action required by this method.
21
+ # @param preshift [Object] pre call state of the things.
22
+ # @param ret [Object] the return value of the method.
23
+ # @param _block [Proc] the block passed to the method.
24
+ # @return [Object] the return value of the method.
18
25
  def square_bracket_tagger propagation_node, preshift, ret, _block
19
26
  case ret
20
27
  when Array
@@ -36,6 +43,14 @@ module Contrast
36
43
  ret
37
44
  end
38
45
 
46
+ # Captures is a method that returns an array of MatchData objects.
47
+ #
48
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
49
+ # propagation action required by this method.
50
+ # @param preshift [Object] pre call state of the things.
51
+ # @param ret [Object] the return value of the method.
52
+ # @param _block [Proc] the block passed to the method.
53
+ # @return [Object] the return value of the method.
39
54
  def captures_tagger propagation_node, preshift, ret, _block
40
55
  return unless ret
41
56
 
@@ -52,6 +67,12 @@ module Contrast
52
67
  ret
53
68
  end
54
69
 
70
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
71
+ # propagation action required by this method.
72
+ # @param preshift [Object] pre call state of the things.
73
+ # @param ret [Object] the return value of the method.
74
+ # @param _block [Proc] the block passed to the method.
75
+ # @return [Object] the return value of the method.
55
76
  def to_a_tagger propagation_node, preshift, ret, _block
56
77
  idx = 0
57
78
  while idx < ret.length
@@ -65,6 +86,12 @@ module Contrast
65
86
  ret
66
87
  end
67
88
 
89
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
90
+ # propagation action required by this method.
91
+ # @param preshift [Object] pre call state of the things.
92
+ # @param ret [Object] the return value of the method.
93
+ # @param _block [Proc] the block passed to the method.
94
+ # @return [Object] the return value of the method.
68
95
  def values_at_tagger propagation_node, preshift, ret, _block
69
96
  idx = 0
70
97
  while idx < ret.length
@@ -81,6 +108,9 @@ module Contrast
81
108
 
82
109
  private
83
110
 
111
+ # @param preshift [Object] pre call state of the things.
112
+ # @param index [Integer] the index of the argument to retrieve.
113
+ # @return [Integer] the index of the argument to retrieve.
84
114
  def target_matchdata_index preshift, index
85
115
  if preshift.args[0].is_a?(Range)
86
116
  arg_range = preshift.args[0]
@@ -90,6 +120,14 @@ module Contrast
90
120
  end
91
121
  end
92
122
 
123
+ # Handles the propagation of a single argument.
124
+ #
125
+ # @param argument_index [Integer] the index of the argument to retrieve.
126
+ # @param preshift [Object] pre call state of the things.
127
+ # @param return_value [Object] the return value of the method.
128
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
129
+ # propagation action required by this method.
130
+ # @return [Object] the return value of the method.
93
131
  def square_bracket_single argument_index, preshift, return_value, propagation_node
94
132
  original_start_index = preshift.object.begin(argument_index)
95
133
  original_end_index = preshift.object.end(argument_index)
@@ -14,6 +14,12 @@ module Contrast
14
14
  class << self
15
15
  # String has some silly methods like next. Basically, this flips a
16
16
  # character in a predictable manner
17
+ #
18
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
19
+ # propagation action required by this method.
20
+ # @param preshift [Object] pre call state of the things.
21
+ # @param target [Object] the object to which the source is being appended
22
+ # @return [Object] the target with the tags applied
17
23
  def propagate propagation_node, preshift, target
18
24
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
19
25
 
@@ -13,6 +13,11 @@ module Contrast
13
13
  class << self
14
14
  # For the source, prepend its tags to the target. It's basically the
15
15
  # opposite of append. :-P
16
+ #
17
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
18
+ # propagation action required by this method.
19
+ # @param preshift [Object] pre call state of the things.
20
+ # @param target [Object] the object to which the source is being appended
16
21
  def propagate propagation_node, preshift, target
17
22
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
18
23
 
@@ -37,6 +37,10 @@ module Contrast
37
37
  handle_removal(propagation_node, source, target)
38
38
  end
39
39
 
40
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
41
+ # propagation action required by this method.
42
+ # @param source [Object] the object to which the source is being appended
43
+ # @param target [Object] the object to which the source is being appended
40
44
  def handle_removal propagation_node, source, target
41
45
  return unless source
42
46
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
@@ -13,6 +13,11 @@ module Contrast
13
13
  class << self
14
14
  # Replace means we're replacing the target w/ the source. Anything
15
15
  # on the source should be passed to the target.
16
+ #
17
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
18
+ # propagation action required by this method.
19
+ # @param preshift [Object] pre call state of the things.
20
+ # @param target [Object] the object to which the source is being appended
16
21
  def propagate propagation_node, preshift, target
17
22
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
18
23
 
@@ -12,6 +12,11 @@ module Contrast
12
12
  # overlapping tags.
13
13
  class Reverse < Contrast::Agent::Assess::Policy::Propagator::Base
14
14
  class << self
15
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
16
+ # propagation action required by this method.
17
+ # @param preshift [Object] pre call state of the things.
18
+ # @param target [Object] the object to which the source is being appended
19
+ # @return [Object] the target with the tags applied
15
20
  def propagate propagation_node, preshift, target
16
21
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
17
22
 
@@ -13,6 +13,10 @@ module Contrast
13
13
  # a 'get it right' state soon.
14
14
  class Select
15
15
  class << self
16
+ # @param patcher [Contrast::Agent::Assess::Patcher] the patcher
17
+ # @param preshift [Object] pre call state of the things.
18
+ # @param ret [Object] the return value of the method.
19
+ # @param _block [Proc] the block passed to the method.
16
20
  def select_tagger patcher, preshift, ret, _block
17
21
  source = preshift.object
18
22
  args = preshift.args
@@ -42,6 +46,12 @@ module Contrast
42
46
 
43
47
  private
44
48
 
49
+ # Handles the integer case for select.
50
+ #
51
+ # @param args [Array] the arguments passed to the method
52
+ # @param arg [Object] the first argument passed to the method
53
+ # @param source [String] the source string
54
+ # @return [Range, nil] the range to select from the source string
45
55
  def handle_integer args, arg, source
46
56
  length = args[1] || 1
47
57
  # (void) negative range
@@ -49,11 +59,22 @@ module Contrast
49
59
  arg...(arg + length)
50
60
  end
51
61
 
62
+ # Handles the string case for select.
63
+ #
64
+ # @param arg [String] the first argument passed to the method
65
+ # @param source [String] the source string
66
+ # @return [Range, nil] the range to select from the source string
52
67
  def handle_string arg, source
53
68
  idx = source.index(arg)
54
69
  idx...(idx + arg.length)
55
70
  end
56
71
 
72
+ # Handles the regexp case for select.
73
+ #
74
+ # @param args [Array] the arguments passed to the method
75
+ # @param arg [Regexp] the first argument passed to the method
76
+ # @param source [String] the source string
77
+ # @return [Range, nil] the range to select from the source string
57
78
  def handle_regexp args, arg, source
58
79
  match_data = arg.match(source)
59
80
  # nil has the same meaning as 0. use full match
@@ -61,6 +82,11 @@ module Contrast
61
82
  match_data.begin(group)...match_data.end(group)
62
83
  end
63
84
 
85
+ # Handles the range case for select.
86
+ #
87
+ # @param arg [Range] the first argument passed to the method
88
+ # @param source [String] the source string
89
+ # @return [Range, nil] the range to select from the source string
64
90
  def handle_range arg, source
65
91
  start = arg.begin
66
92
  finish = arg.end
@@ -73,6 +99,10 @@ module Contrast
73
99
  start...finish
74
100
  end
75
101
 
102
+ # Determines the range to select from the source string.
103
+ #
104
+ # @param source [Object] the source string
105
+ # @param args [Array] the arguments passed to the method
76
106
  def determine_select_range source, args
77
107
  arg = args[0]
78
108
  case arg
@@ -11,6 +11,11 @@ module Contrast
11
11
  # tags are unaffected beyond any merging of overlapping tags.
12
12
  class Splat < Contrast::Agent::Assess::Policy::Propagator::Base
13
13
  class << self
14
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node responsible for the
15
+ # propagation action required by this method.
16
+ # @param preshift [Object] pre call state of the things.
17
+ # @param target [Object] the object to which the source is being appended
18
+ # @return [Object] the target with the tags applied
14
19
  def propagate propagation_node, preshift, target
15
20
  tracked_inputs = []
16
21
 
@@ -28,6 +33,11 @@ module Contrast
28
33
  Contrast::Agent::Assess::Tracker.properties(target)&.cleanup_tags
29
34
  end
30
35
 
36
+ # Handles the splatting of tags from the tracked inputs to the target.
37
+ #
38
+ # @param tracked_inputs [Array<String>] storage for the inputs to act on later
39
+ # @param target [Object] the object to which the source is being appended
40
+ # @return [Object] the target with the tags applied
31
41
  def splat_tags tracked_inputs, target
32
42
  return if tracked_inputs.empty?
33
43
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
@@ -22,19 +22,23 @@ module Contrast
22
22
  @tags << SOURCE_TAG
23
23
  end
24
24
 
25
+ # @return [String]
25
26
  def node_class
26
27
  SOURCE
27
28
  end
28
29
 
29
30
  # This is confusing. Sources are Creation action but
30
31
  # Propagation type. Oh and also Type refers to input type,
31
- # like parameter, so we have to call this node_type. :-/
32
+ # like parameter, so we have to call this node_type. :-/\
33
+ #
34
+ # @return [Symbol]
32
35
  def node_type
33
36
  :TYPE_PROPAGATION
34
37
  end
35
38
 
36
39
  # Standard validation + TS trace version two rules:
37
40
  # Must have source and type
41
+ #
38
42
  # @raise[ArgumentError] raises if any of the required fields is missing or invalid
39
43
  def validate
40
44
  super
@@ -16,6 +16,10 @@ module Contrast
16
16
  # ActionDispatch::Http::Headers to convert keys like `referer` to `HTTP_REFERER` before they get to the
17
17
  # Rack::Request#get_header method
18
18
  # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/reflected_xss.md
19
+ #
20
+ # @param tag [String] the tag to be applied.
21
+ # @param source_type [String] the type of the source.
22
+ # @param source_name [String] the name of the source.
19
23
  def self.valid? tag, source_type, source_name
20
24
  return true unless tag == 'CROSS_SITE'
21
25
  return false if source_type == Contrast::Agent::Assess::Policy::SourceMethod::HEADER_KEY_TYPE
@@ -24,6 +24,12 @@ module Contrast
24
24
  }.cs__freeze
25
25
  TEMPLATE_PROPAGATION_NODE = Contrast::Agent::Assess::Policy::PropagationNode.new(NODE_HASH)
26
26
 
27
+ # @param trigger_node [Contrast::Agent::Assess::Policy::MethodPolicy] the node that governs this
28
+ # propagation event.
29
+ # @param _source [Object] the source of the propagation
30
+ # @param object [Object] the object to which the source is being appended
31
+ # @param args [Array] the arguments to the method
32
+ # @param ret [Object] the return value of the method
27
33
  def xss_tilt_trigger trigger_node, _source, object, ret, *args
28
34
  return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
29
35
 
@@ -63,6 +69,11 @@ module Contrast
63
69
 
64
70
  private
65
71
 
72
+ # @param scope [Object] the scope of the template
73
+ # @param erb_template_prerender [String] the template string
74
+ # @param properties [Contrast::Agent::Assess::Policy::Properties] the properties of the return value
75
+ # @param interpolated_inputs [Array<String>] the interpolated inputs
76
+ # @param ret [String] the return value of the method
66
77
  def handle_binding_variables scope, erb_template_prerender, ret, properties, interpolated_inputs
67
78
  binding_variables = scope.instance_variables
68
79
 
@@ -81,6 +92,11 @@ module Contrast
81
92
  end
82
93
  end
83
94
 
95
+ # @param args [Array<String>] the arguments to the method
96
+ # @param erb_template_prerender [String] the template string
97
+ # @param ret [String] the return value of the method
98
+ # @param properties [Contrast::Agent::Assess::Policy::Properties] the properties of the return value
99
+ # @param interpolated_inputs [Array<String>] the interpolated inputs
84
100
  def handle_local_variables args, erb_template_prerender, ret, properties, interpolated_inputs
85
101
  locals = args[1]
86
102
  locals.each do |local_name, local_value|
@@ -15,12 +15,26 @@ module Contrast
15
15
  # a finding if so.
16
16
  class Xpath
17
17
  class << self
18
+ # @param trigger_node [Contrast::Agent::Assess::Policy::MethodPolicy] the node that governs this
19
+ # propagation event.
20
+ # @param _source [Object] the source of the propagation
21
+ # @param object [Object] the object to which the source is being appended
22
+ # @param args [Array<Object>] the arguments to the method
23
+ # @param ret [Object] the return value of the method
24
+ # @return [Object] the return value of the method
18
25
  def xpath_expression_trigger trigger_node, _source, object, ret, *args
19
26
  return ret unless args
20
27
 
21
28
  process(trigger_node, object, ret, *args)
22
29
  end
23
30
 
31
+ # @param trigger_node [Contrast::Agent::Assess::Policy::MethodPolicy] the node that governs this
32
+ # propagation event.
33
+ # @param _source [Object] the source of the propagation
34
+ # @param object [Object] the object to which the source is being appended
35
+ # @param args [Array<Object>] the arguments to the method
36
+ # @param ret [Object] the return value of the method
37
+ # @return [Object] the return value of the method
24
38
  def xpath_oga_trigger trigger_node, _source, object, ret, *args
25
39
  return ret unless args
26
40
 
@@ -32,6 +46,11 @@ module Contrast
32
46
 
33
47
  private
34
48
 
49
+ # @param trigger_node [Contrast::Agent::Assess::Policy::MethodPolicy] the node that governs this
50
+ # propagation event.
51
+ # @param object [Object] the object to which the source is being appended
52
+ # @param ret [Object] the return value of the method
53
+ # @param args [Array<Object>] the arguments to the method
35
54
  def process trigger_node, object, ret, *args
36
55
  args.each do |arg|
37
56
  next unless arg.cs__is_a?(String) || arg.cs__is_a?(Symbol)
@@ -178,6 +178,10 @@ module Contrast
178
178
  Contrast::SETTINGS.excluder.assess_excluded_by_url_and_rule?(rule_id)
179
179
  end
180
180
 
181
+ # Check if the finding should be excluded due to the assess exclusion rules.
182
+ #
183
+ # @param finding [Contrast::Agent::Reporting::Finding]
184
+ # @param rule_id [String]
181
185
  def excluded_by_input_and_rule? finding, rule_id
182
186
  return false unless Contrast::SETTINGS.excluder.exclusions.any?
183
187
  return unless Contrast::Agent::REQUEST_TRACKER.current
@@ -185,7 +189,9 @@ module Contrast
185
189
  Contrast::SETTINGS.excluder.assess_excluded_by_input_and_rule?(finding, rule_id)
186
190
  end
187
191
 
188
- # Handles the Stored Xss rule. If a vector is stored in the database
192
+ # Handles the Stored Xss rule. If a vector is stored in the database.
193
+ #
194
+ # @param finding [Contrast::Agent::Reporting::Finding]
189
195
  def check_for_stored_xss finding
190
196
  return unless finding && finding.rule_id == 'reflected-xss'
191
197
 
@@ -200,6 +206,7 @@ module Contrast
200
206
  end
201
207
  end
202
208
 
209
+ # @param finding [Contrast::Agent::Reporting::Finding]
203
210
  def extract_dynamic_source_info finding
204
211
  return unless finding
205
212
 
@@ -29,7 +29,16 @@ module Contrast
29
29
  # from the application. Some rules rely on Content-Type validation.
30
30
  COLLECTABLE_RULES = %w[reflected-xss].cs__freeze
31
31
 
32
- attr_reader :rule_id, :required_tags, :disallowed_tags, :good_value, :bad_value
32
+ # @return [String]
33
+ attr_reader :rule_id
34
+ # @return [Array<String>]
35
+ attr_reader :required_tags
36
+ # @return [Array<String>]
37
+ attr_reader :disallowed_tags
38
+ # @return [Array<String>]
39
+ attr_reader :good_value
40
+ # @return [Array<String>]
41
+ attr_reader :bad_value
33
42
 
34
43
  ENCODER_START = 'CUSTOM_ENCODED_'
35
44
  # By default, any rule will be triggered if the source
@@ -46,6 +55,7 @@ module Contrast
46
55
  LIMITED_CHARS = 'LIMITED_CHARS'
47
56
  CUSTOM_ENCODED = 'CUSTOM_ENCODED'
48
57
  CUSTOM_VALIDATED = 'CUSTOM_VALIDATED'
58
+
49
59
  def initialize trigger_hash = {}, rule_hash = {}
50
60
  super(trigger_hash)
51
61
  good_value = trigger_hash[JSON_GOOD_VALUE]
@@ -16,6 +16,10 @@ module Contrast
16
16
  NEGATIVE_INFINITY = -POSITIVE_INFINITY
17
17
 
18
18
  class << self
19
+ # @param _patcher [Contrast::Agent::Patcher] the patcher instance
20
+ # @param object [Object] the object that was called
21
+ # @param _ret [Object] the return value of the method
22
+ # @param args [Array<Object>] the arguments passed to the method
19
23
  def valid? _patcher, object, _ret, args
20
24
  # Can arrive here from either:
21
25
  # regexp =~ string
@@ -21,6 +21,12 @@ module Contrast
21
21
  # a valid URL in which the User controls a section prior to the
22
22
  # querystring
23
23
  # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/server_side_request_forgery.md
24
+ #
25
+ # @param patcher [Contrast::Agent::Patcher] the patcher instance
26
+ # @param _object [Object] the object that was called
27
+ # @param _ret [Object] the return value of the method
28
+ # @param args [Array<Object>] the arguments passed to the method
29
+ # @return [Boolean] true if the finding is valid, false otherwise
24
30
  def self.valid? patcher, _object, _ret, args
25
31
  return true if patcher.id.to_s.start_with?(PATH_ONLY_PATCH_MARKER)
26
32
 
@@ -16,6 +16,12 @@ module Contrast
16
16
  # A finding is valid for XSS if the response type is not one of
17
17
  # those assumed to be safe
18
18
  # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/reflected_xss.md
19
+ #
20
+ # @param patcher [Contrast::Agent::Patcher] the patcher instance
21
+ # @param _object [Object] the object that was called
22
+ # @param _ret [Object] the return value of the method
23
+ # @param args [Array<Object>] the arguments passed to the method
24
+ # @return [Boolean] true if the finding is valid, false otherwise
19
25
  def self.valid? _patcher, _object, _ret, _args
20
26
  content_type = Contrast::Agent::REQUEST_TRACKER.current&.response&.content_type
21
27
  return false unless content_type
@@ -73,7 +73,8 @@ module Contrast
73
73
  finding.routes << context.discovered_route if context&.discovered_route
74
74
  build_evidence(evidence, finding)
75
75
  finding.request = Contrast::Agent::Reporting::FindingRequest.convert(context.request) if context&.request
76
- hash = Contrast::Utils::HashDigest.generate_config_hash(finding)
76
+ # Hash must be built last so that finding has full context.
77
+ hash = Contrast::Utils::HashDigest.generate_response_hash(finding, context&.request)
77
78
  finding.hash_code = Contrast::Utils::StringUtils.force_utf8(hash)
78
79
  finding
79
80
  end
@@ -43,6 +43,7 @@ module Contrast
43
43
 
44
44
  def self.report_traces
45
45
  return unless Contrast::ASSESS.enabled?
46
+ return if ENV.fetch('CONTRAST__PIPELINE__RUN', nil) == 'true'
46
47
 
47
48
  collection = Contrast::Agent::Reporting::ReportingStorage.collection
48
49
 
@@ -24,7 +24,7 @@ module Contrast
24
24
  #
25
25
  # @param event [Contrast::Agent::Reporting::ReportingEvent] One of the DTMs valid for the
26
26
  # event field of Contrast::Agent::Reporting::ReportingEvent
27
- # @param response_data [Net::HTTP::Response]
27
+ # @param response_data [Net::HTTPResponse]
28
28
  def audit_event event, response_data = nil
29
29
  return unless ::Contrast::API.request_audit_requests || ::Contrast::API.request_audit_responses
30
30
 
@@ -69,21 +69,46 @@ module Contrast
69
69
  # @param event [Contrast::Agent::Reporting::ReportingEvent] The event to send to TeamServer. Really a
70
70
  # child of the ReportingEvent rather than a literal one.
71
71
  # @param connection [Net::HTTP] open connection
72
- # @return response [Net::HTTP::Response, nil] response from TS if no response
73
72
  def send_event event, connection
74
73
  return unless connection
75
74
  return unless event.valid?
76
75
 
77
76
  logger.debug('[Reporter] Preparing to send reporting event', event_class: event.cs__class)
78
77
  request = build_request(event)
79
- response = connection.request(request)
78
+ begin
79
+ response = connection.request(request)
80
+ rescue StandardError => e
81
+ handle_response_error(event, connection, e)
82
+ return
83
+ end
80
84
  audit.audit_event(event, response) if ::Contrast::API.request_audit_enable
81
- process_settings_response(response, event)
82
- process_preflight_response(event, response, connection)
83
- report_configuration(response, event)
85
+ process_event_response(event, response, connection)
84
86
  response
85
87
  rescue StandardError => e
86
- handle_response_error(event, connection, e)
88
+ logger.error('[Reporter] Error while processing event sent to TeamServer',
89
+ error: e,
90
+ event_type: event&.cs__class)
91
+ end
92
+
93
+ # Only two types of events require processing when returned from TeamServer; preflight and settings.
94
+ # For Settings, we want to update internally and then report the resulting configuration.
95
+ # For preflight, we want to process the response and send findings if requested.
96
+ #
97
+ # @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer
98
+ # @param response [Net::HTTPResponse] the response from TeamServer
99
+ # @param connection [Net::HTTP] open connection
100
+ def process_event_response event, response, connection
101
+ case event
102
+ when Contrast::Agent::Reporting::Preflight
103
+ process_preflight_response(event, response, connection)
104
+ when Contrast::Agent::Reporting::AgentStartup,
105
+ Contrast::Agent::Reporting::ApplicationStartup,
106
+ Contrast::Agent::Reporting::ApplicationSettings,
107
+ Contrast::Agent::Reporting::ServerSettings
108
+
109
+ process_settings_response(response, event)
110
+ report_configuration(response, event)
111
+ end
87
112
  end
88
113
 
89
114
  # Write effective config to file:
@@ -91,7 +116,7 @@ module Contrast
91
116
  # 200 or 304. In case of 304 there will be no new settings and we can write current ones.
92
117
  # This is done on every settings request.
93
118
  #
94
- # @param response [Contrast::Agent::Reporting::Response, nil]
119
+ # @param response [Net::HTTPResponse, nil]
95
120
  # @param event [Contrast::Agent::Reporting::ReportingEvent]
96
121
  def report_configuration response, event
97
122
  return unless response
@@ -109,14 +134,17 @@ module Contrast
109
134
  logger.error('[Reporter] Error while reporting configuration', error: e, event_type: event&.cs__class)
110
135
  end
111
136
 
137
+ # @return [Contrast::Agent::Reporting::ConnectionStatus]
112
138
  def status
113
139
  @_status ||= Contrast::Agent::Reporting::ConnectionStatus.new
114
140
  end
115
141
 
142
+ # @return [Contrast::Agent::Reporting::ResponseHandler]
116
143
  def response_handler
117
144
  @_response_handler ||= Contrast::Agent::Reporting::ResponseHandler.new
118
145
  end
119
146
 
147
+ # @return [Contrast::Config::Diagnostics::Monitor]
120
148
  def diagnostics
121
149
  @_diagnostics ||= Contrast::Config::Diagnostics::Monitor.new(Contrast::LOGGER.path)
122
150
  end
@@ -90,7 +90,8 @@ module Contrast
90
90
  # Handles response processing and sets status
91
91
  #
92
92
  # @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer.
93
- # @param response [Net::HTTP::Response]
93
+ # @param response [Net::HTTPResponse]
94
+ # @return [Contrast::Agent::Reporting::Response]
94
95
  def process_settings_response response, event
95
96
  res = response_handler.process(response, event)
96
97
  if res
@@ -115,7 +116,6 @@ module Contrast
115
116
  return unless response&.body && connection
116
117
 
117
118
  findings_to_return = response.body.split(',').delete_if { |el| el.include?('*') }
118
- mode.resend.reset_rescue_attempts
119
119
  findings_to_return.each do |index|
120
120
  preflight_message = event.messages[index.to_i]
121
121
  preflight_data = preflight_message&.data
@@ -127,6 +127,12 @@ module Contrast
127
127
 
128
128
  send_event(corresponding_finding, connection)
129
129
  end
130
+
131
+ # Event rejected traces should be removed from the set.
132
+ # We could clean this up to know if the message was already deleted by the loop above.
133
+ event.messages&.each do |preflight_message|
134
+ Contrast::Agent::Reporting::ReportingStorage.delete(preflight_message&.data)
135
+ end
130
136
  rescue StandardError => e
131
137
  logger.error('[Reporter] Unable to handle preflight response', e)
132
138
  end
@@ -44,12 +44,7 @@ module Contrast
44
44
  def delete key
45
45
  return unless key
46
46
 
47
- logger.debug('Starting deleting value for', key: key)
48
-
49
- deleted_value = collection.delete(key)
50
- logger.debug('Key wasn\'t found') unless deleted_value
51
-
52
- deleted_value
47
+ collection.delete(key)
53
48
  end
54
49
 
55
50
  # @param rule_id [String] the rule_id
@@ -20,11 +20,12 @@ module Contrast
20
20
 
21
21
  # Process the response from TS
22
22
  #
23
- # @param response [Net::HTTP::Response, nil]
23
+ # @param response [Net::HTTPResponse, nil]
24
24
  # @param event [Contrast::Agent::Reporting::ReportingEvent] The event sent to TeamServer.
25
- # @return response [Net::HTTP::Response, nil]
25
+ # @return response [Net::HTTPResponse, nil]
26
26
  def process response, event
27
27
  logger.debug('[Reporter] Received a response')
28
+ return if event&.cs__is_a?(Contrast::Agent::Reporting::Finding)
28
29
  return unless analyze_response?(response)
29
30
 
30
31
  # Handle the response body and obtain server_features or app_settings
@@ -107,7 +108,7 @@ module Contrast
107
108
  # Handles the errors code received from TS and takes appropriate action.
108
109
  # If we are here the response.code is an error that needs handling [4XX]
109
110
  #
110
- # @param response [Net::HTTP::Response]
111
+ # @param response [Net::HTTPResponse]
111
112
  def handle_error response
112
113
  case response&.code
113
114
  when ERROR_CODES[:message_not_sent]