contrast-agent 4.13.1 → 4.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +1 -0
  3. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
  4. data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
  5. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
  6. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
  7. data/lib/contrast/agent/assess/policy/trigger_method.rb +44 -7
  8. data/lib/contrast/agent/assess/policy/trigger_node.rb +14 -6
  9. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
  10. data/lib/contrast/agent/assess/property/tagged.rb +51 -57
  11. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
  12. data/lib/contrast/agent/metric_telemetry_event.rb +2 -2
  13. data/lib/contrast/agent/middleware.rb +5 -75
  14. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -89
  15. data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
  16. data/lib/contrast/agent/patching/policy/patcher.rb +12 -8
  17. data/lib/contrast/agent/reporting/report.rb +21 -0
  18. data/lib/contrast/agent/reporting/reporter.rb +142 -0
  19. data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
  20. data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
  21. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
  22. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
  23. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
  24. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
  25. data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
  26. data/lib/contrast/agent/request.rb +2 -81
  27. data/lib/contrast/agent/request_context.rb +4 -128
  28. data/lib/contrast/agent/request_context_extend.rb +138 -0
  29. data/lib/contrast/agent/response.rb +2 -73
  30. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +39 -16
  31. data/lib/contrast/agent/static_analysis.rb +1 -1
  32. data/lib/contrast/agent/telemetry.rb +15 -7
  33. data/lib/contrast/agent/telemetry_event.rb +8 -9
  34. data/lib/contrast/agent/thread_watcher.rb +31 -5
  35. data/lib/contrast/agent/version.rb +1 -1
  36. data/lib/contrast/agent.rb +15 -0
  37. data/lib/contrast/api/communication/connection_status.rb +10 -7
  38. data/lib/contrast/api/communication/messaging_queue.rb +37 -3
  39. data/lib/contrast/api/communication/response_processor.rb +15 -8
  40. data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
  41. data/lib/contrast/api/communication/socket.rb +6 -8
  42. data/lib/contrast/api/communication/socket_client.rb +29 -12
  43. data/lib/contrast/api/communication/speedracer.rb +37 -1
  44. data/lib/contrast/api/communication/tcp_socket.rb +4 -3
  45. data/lib/contrast/api/communication/unix_socket.rb +1 -0
  46. data/lib/contrast/api/decorators/finding.rb +45 -0
  47. data/lib/contrast/components/api.rb +56 -0
  48. data/lib/contrast/components/app_context.rb +10 -65
  49. data/lib/contrast/components/app_context_extend.rb +78 -0
  50. data/lib/contrast/components/base.rb +23 -0
  51. data/lib/contrast/components/config.rb +8 -8
  52. data/lib/contrast/components/contrast_service.rb +5 -0
  53. data/lib/contrast/components/sampling.rb +2 -2
  54. data/lib/contrast/config/agent_configuration.rb +1 -1
  55. data/lib/contrast/config/api_configuration.rb +9 -4
  56. data/lib/contrast/config/api_proxy_configuration.rb +14 -0
  57. data/lib/contrast/config/application_configuration.rb +2 -3
  58. data/lib/contrast/config/assess_configuration.rb +3 -3
  59. data/lib/contrast/config/base_configuration.rb +17 -28
  60. data/lib/contrast/config/certification_configuration.rb +15 -0
  61. data/lib/contrast/config/env_variables.rb +2 -9
  62. data/lib/contrast/config/heap_dump_configuration.rb +6 -6
  63. data/lib/contrast/config/inventory_configuration.rb +1 -5
  64. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  65. data/lib/contrast/config/request_audit_configuration.rb +18 -0
  66. data/lib/contrast/config/ruby_configuration.rb +6 -6
  67. data/lib/contrast/config/service_configuration.rb +1 -2
  68. data/lib/contrast/config.rb +0 -1
  69. data/lib/contrast/configuration.rb +1 -2
  70. data/lib/contrast/extension/assess/array.rb +5 -7
  71. data/lib/contrast/framework/manager.rb +8 -32
  72. data/lib/contrast/framework/manager_extend.rb +50 -0
  73. data/lib/contrast/framework/rails/railtie.rb +1 -1
  74. data/lib/contrast/framework/sinatra/support.rb +2 -1
  75. data/lib/contrast/logger/log.rb +8 -103
  76. data/lib/contrast/utils/assess/property/tagged_utils.rb +23 -0
  77. data/lib/contrast/utils/assess/tracking_util.rb +20 -15
  78. data/lib/contrast/utils/assess/trigger_method_utils.rb +1 -1
  79. data/lib/contrast/utils/class_util.rb +18 -14
  80. data/lib/contrast/utils/findings.rb +62 -0
  81. data/lib/contrast/utils/hash_digest.rb +10 -73
  82. data/lib/contrast/utils/hash_digest_extend.rb +86 -0
  83. data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
  84. data/lib/contrast/utils/heap_dump_util.rb +2 -65
  85. data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
  86. data/lib/contrast/utils/io_util.rb +1 -1
  87. data/lib/contrast/utils/log_utils.rb +108 -0
  88. data/lib/contrast/utils/middleware_utils.rb +87 -0
  89. data/lib/contrast/utils/net_http_base.rb +158 -0
  90. data/lib/contrast/utils/object_share.rb +1 -0
  91. data/lib/contrast/utils/request_utils.rb +88 -0
  92. data/lib/contrast/utils/response_utils.rb +97 -0
  93. data/lib/contrast/utils/substitution_utils.rb +167 -0
  94. data/lib/contrast/utils/tag_util.rb +9 -9
  95. data/lib/contrast/utils/telemetry.rb +4 -2
  96. data/lib/contrast/utils/telemetry_client.rb +90 -0
  97. data/lib/contrast/utils/telemetry_identifier.rb +17 -24
  98. data/ruby-agent.gemspec +5 -5
  99. metadata +48 -23
  100. data/lib/contrast/config/default_value.rb +0 -17
  101. data/lib/contrast/utils/requests_client.rb +0 -150
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96cff410085a542c1003dda242268f349bc20f738b82bde70725b695661a79bc
4
- data.tar.gz: 2a5d703693f697785034df1f602ef0f61241ae16edf11dbd174ad9a8da01b72c
3
+ metadata.gz: 2fd48ab0d63bb36fd51e032c360b2ffebe0c387a57e2b70a5e025c1af9667bfb
4
+ data.tar.gz: 7aaf091b2e3fd2ced8c8b11708db4a8e216bf55b7499fc235940e93ebff2ef1a
5
5
  SHA512:
6
- metadata.gz: 409632acc3352b7c172a1aa12c9f5de482b5e5c3c0307b0732c48c7b8cf8d56c5ef5a4c039a0c866dd0c1ea31340b21ee8e4bcc0f2c4c4fdea9cd4fffcbaa212
7
- data.tar.gz: 17b56ee4355a472b637cf1f9059f1bfce01319351713fc3b35e744bc3afad3d4f068cf77dd474032193249cb8f178141d7d7da2aa7b4d2f3a0cc2158e1c55d0c
6
+ metadata.gz: 56fbdd193cc27bcd7e21fe5c4f237276c616e4633344db18b6f27e7e73c99979131cbb9922a2d0cf947c0b666dd3a0d5098b120a805d8cf7b52bc73c6becc535
7
+ data.tar.gz: dce071438c0f996567c9867b4ab59900d2225fc3afc59887ffbd850705981f59f228360899084711fe88c9d566e33cdb20f3d5a9127b30efaa0c689274c84159
data/.simplecov CHANGED
@@ -4,5 +4,6 @@
4
4
  SimpleCov.minimum_coverage line: 94.75
5
5
  SimpleCov.start do
6
6
  add_filter '/spec/'
7
+ add_filter '/lib/contrast/extension/assess/erb.rb'
7
8
  enable_coverage :branch
8
9
  end
@@ -135,14 +135,14 @@ module Contrast
135
135
 
136
136
  converted = []
137
137
  markers.split(Contrast::Utils::ObjectShare::COMMA).each do |t|
138
- case t
139
- when Contrast::Utils::ObjectShare::OBJECT_KEY,
138
+ converted << case t
139
+ when Contrast::Utils::ObjectShare::OBJECT_KEY,
140
140
  Contrast::Utils::ObjectShare::RETURN_KEY
141
141
 
142
- converted << t
143
- else
144
- converted << Integer(t[1..-1])
145
- end
142
+ t
143
+ else
144
+ Integer(t[1..-1])
145
+ end
146
146
  end
147
147
  converted
148
148
  end
@@ -32,8 +32,13 @@ module Contrast
32
32
  mod = trace_point.self
33
33
  return if mod.cs__frozen? || mod.singleton_class?
34
34
 
35
+ different_ruby_version trace_point, provider_values, mod
36
+ end
37
+
38
+ def different_ruby_version trace_point, provider_values, mod
35
39
  # TODO: RUBY-1014 - remove non-AST approach
36
40
  if RUBY_VERSION >= '2.6.0'
41
+ # TODO: RUBY-714 EOL 2.5. That check will be removed and the code will be brought back in here.
37
42
  ast = RubyVM::AbstractSyntaxTree.parse_file(trace_point.path)
38
43
  provider_values.each do |provider|
39
44
  provider.parse(trace_point, ast)
@@ -24,7 +24,7 @@ module Contrast
24
24
  end
25
25
 
26
26
  # find original in the target, copy tags to the new position in target
27
- original_start_index = target[0..target.length / 2 + 1].rindex(source1)
27
+ original_start_index = target[0..(target.length / 2) + 1].rindex(source1)
28
28
  original_start_index ||= 1
29
29
  properties.copy_from(source1, target, original_start_index, propagation_node.untags)
30
30
 
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'contrast/components/logger'
5
5
  require 'contrast/utils/duck_utils'
6
+ require 'contrast/utils/substitution_utils'
6
7
 
7
8
  module Contrast
8
9
  module Agent
@@ -17,9 +18,7 @@ module Contrast
17
18
  class Substitution
18
19
  include Contrast::Components::Logger::InstanceMethods
19
20
  extend Contrast::Components::Logger::InstanceMethods
20
-
21
- CAPTURE_GROUP_REGEXP = /\\[[:digit:]]/.cs__freeze
22
- CAPTURE_NAME_REGEXP = /\\k<[[:alpha:]]/.cs__freeze
21
+ extend Contrast::Utils::SubstitutionUtils
23
22
 
24
23
  class << self
25
24
  # gsub is hard. there are four versions of this method
@@ -38,157 +37,6 @@ module Contrast
38
37
  def sub_tagger patcher, preshift, ret, block
39
38
  substitution_tagger(patcher, preshift, ret, !block.nil?, false)
40
39
  end
41
-
42
- private
43
-
44
- def substitution_tagger patcher, preshift, ret, block, global = true
45
- return ret unless ret
46
-
47
- begin
48
- source = preshift.object
49
- self_tracked = Contrast::Agent::Assess::Tracker.tracked?(source)
50
- args = preshift.args[1]
51
- incoming_tracked = args && determine_tracked(args)
52
- return ret unless self_tracked || incoming_tracked
53
-
54
- parent_events = []
55
- if block
56
- block_sub(self_tracked, source, ret)
57
- elsif args.is_a?(String)
58
- string_sub(parent_events, self_tracked, preshift, ret, args, incoming_tracked, global)
59
- elsif args.is_a?(Hash)
60
- hash_sub(self_tracked, source, ret)
61
- else # Enumerator, only for gsub
62
- pattern_gsub(parent_events, preshift, ret)
63
- end
64
-
65
- if self_tracked
66
- source_properties = Contrast::Agent::Assess::Tracker.properties(source)
67
- parent_event = source_properties&.event
68
- parent_events.prepend(parent_event) if parent_event
69
- end
70
- string_build_event(parent_events, patcher, preshift, ret)
71
- rescue StandardError => e
72
- logger.error('Unable to apply gsub propagator', e)
73
- end
74
- ret
75
- end
76
-
77
- def determine_tracked args
78
- # if there's no arg, it can't be tracked
79
- return false unless args
80
-
81
- # if it's a string, just ask if it's tracked
82
- case args
83
- when String
84
- Contrast::Agent::Assess::Tracker.tracked?(args)
85
- # if it's a hash, ask if it has a tracked string
86
- when Hash
87
- args.values.any? { |value| value.is_a?(String) && Contrast::Agent::Assess::Tracker.tracked?(value) }
88
- # this should never happen
89
- else
90
- false
91
- end
92
- end
93
-
94
- def string_sub parent_events, self_tracked, preshift, ret, incoming, incoming_tracked, global
95
- return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
96
-
97
- incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
98
- parent_events << incoming_properties&.event if incoming_properties&.event
99
-
100
- source = preshift.object
101
-
102
- # We can't efficiently find the places that things were
103
- # copied from regexp / captures. Trading accuracy for
104
- # performance
105
- if incoming.match?(CAPTURE_GROUP_REGEXP) || incoming.match?(CAPTURE_NAME_REGEXP)
106
- properties.splat_from(source, ret) if self_tracked
107
- return
108
- end
109
-
110
- # if it's just a straight insert, that we can do
111
- # Copy the tags from us to the return
112
- ranges = find_string_sub_insert(properties, preshift, incoming, ret, global)
113
-
114
- properties.delete_tags_at_ranges(ranges)
115
- properties.shift_tags(ranges)
116
- return unless incoming_tracked
117
- return unless incoming_properties
118
-
119
- tags = incoming_properties.tag_keys
120
- ranges.each do |range|
121
- tags.each do |tag|
122
- properties.add_tag(tag, range)
123
- end
124
- end
125
- end
126
-
127
- # Find the points at which the new String was placed into the original
128
- #
129
- # @param properties [Contrast::Agent::Assess::Properties] the Properties of the ret
130
- # @param preshift [Contrast::Agent::Assess::PreShift] the capture of the state of the code just prior to
131
- # the invocation of the patched method
132
- # @param incoming [String] the new String going into the substitution
133
- # @param ret [String] the result of the substitution
134
- # @param global [Boolean] if this was a global or single substitution
135
- # @return [Array<Range>] the Ranges where substitution occurred
136
- def find_string_sub_insert properties, preshift, incoming, ret, global
137
- pattern = preshift.args[0]
138
- source = preshift.object
139
-
140
- properties.copy_from(source, ret)
141
- # Figure out where inserts occurred
142
- last_idx = 0
143
- ranges = []
144
- # For each insert, move the tag ranges
145
- while last_idx
146
- idx = source.index(pattern, last_idx)
147
- break unless idx
148
-
149
- last_idx = idx ? idx + 1 : nil
150
- start_index = idx
151
- end_index = idx + incoming.length
152
- ranges << (start_index...end_index)
153
- break unless global
154
- end
155
- ranges
156
- end
157
-
158
- def block_sub self_tracked, source, ret
159
- return unless self_tracked
160
-
161
- properties = Contrast::Agent::Assess::Tracker.properties!(ret)
162
- properties&.splat_from(source, ret)
163
- end
164
-
165
- def hash_sub self_tracked, source, ret
166
- return unless self_tracked
167
-
168
- properties = Contrast::Agent::Assess::Tracker.properties!(ret)
169
- properties&.splat_from(source, ret)
170
- end
171
-
172
- def pattern_gsub parent_events, preshift, ret
173
- source = preshift.object
174
- return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
175
- return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
176
-
177
- source_properties.tag_keys.each do |key|
178
- properties.add_tag(key, 0...1)
179
- end
180
- parent_event = source_properties.event
181
- parent_events << parent_event if parent_event
182
- end
183
-
184
- def string_build_event parent_events, patcher, preshift, ret
185
- return unless Contrast::Agent::Assess::Tracker.tracked?(ret)
186
-
187
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
188
- args = preshift.args
189
- properties.build_event(patcher, ret, preshift.object, ret, args, 2)
190
- properties.event.instance_variable_set(:@_parent_events, parent_events)
191
- end
192
40
  end
193
41
  end
194
42
  end
@@ -7,6 +7,7 @@ require 'contrast/components/logger'
7
7
  require 'contrast/utils/object_share'
8
8
  require 'contrast/utils/sha256_builder'
9
9
  require 'contrast/utils/assess/trigger_method_utils'
10
+ require 'contrast/agent/reporting/reporting_utilities/reporting_storage'
10
11
 
11
12
  module Contrast
12
13
  module Agent
@@ -16,7 +17,7 @@ module Contrast
16
17
  # Contrast::Agent::Assess::Policy::TriggerNode class. Each such method will call to this module just after
17
18
  # invocation in order to determine if the call was done safely. In those cases where it was not, a Finding
18
19
  # report is issued to the Service.
19
- module TriggerMethod
20
+ module TriggerMethod # rubocop:disable Metrics/ModuleLength
20
21
  extend Contrast::Components::Logger::InstanceMethods
21
22
  extend Contrast::Utils::Assess::TriggerMethodUtils
22
23
 
@@ -52,6 +53,14 @@ module Contrast
52
53
  apply_trigger(trigger_node, source, object, ret, *args)
53
54
  end
54
55
 
56
+ def append_to_finding finding, trigger_node, source, object, ret, request, *args
57
+ finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(trigger_node.rule_id)
58
+ finding.version = determine_compliance_version(finding)
59
+ append_events(finding, trigger_node, source, object, ret, args)
60
+ append_route(finding, request)
61
+ append_hash(finding, source, request)
62
+ end
63
+
55
64
  # This converts the source of the finding, and the events leading up to it into a Finding
56
65
  #
57
66
  # @param trigger_node [Contrast::Agent::Assess::Policy::TriggerNode] the node to direct applying this
@@ -63,18 +72,21 @@ module Contrast
63
72
  # @return [Contrast::Api::Dtm::Finding, nil] the Contrast::Api::Dtm::Finding to send to TeamServer or
64
73
  # nil if conditions were not met
65
74
  def build_finding trigger_node, source, object, ret, *args
75
+ content_type = Contrast::Agent::REQUEST_TRACKER.current&.response&.content_type
76
+
77
+ if content_type.nil? && trigger_node.collectable?
78
+ Contrast::Agent::FINDINGS.collect_finding trigger_node, source, object, ret, *args
79
+ return
80
+ end
81
+
66
82
  return unless Contrast::Agent::Assess::Policy::TriggerValidation.valid?(trigger_node, object, ret, args)
67
83
 
68
84
  request = find_request(source)
69
85
  return unless reportable?(request&.env)
70
86
 
87
+ handle_new_finding trigger_node, source, object, ret, request, *args
71
88
  finding = Contrast::Api::Dtm::Finding.new
72
- finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(trigger_node.rule_id)
73
-
74
- append_events(finding, trigger_node, source, object, ret, args)
75
- append_route(finding, request)
76
- append_hash(finding, source, request)
77
- finding.version = determine_compliance_version(finding)
89
+ append_to_finding finding, trigger_node, source, object, ret, request, *args
78
90
  logger.trace('Finding created', node_id: trigger_node.id, source_id: source.__id__,
79
91
  rule: trigger_node.rule_id)
80
92
  report_finding(finding, request)
@@ -110,6 +122,31 @@ module Contrast
110
122
  Contrast::Agent.messaging_queue.send_event_eventually(activity)
111
123
  end
112
124
 
125
+ def handle_new_finding trigger_node, source, object, ret, request, *args
126
+ return unless Contrast::Agent::Reporter.enabled?
127
+
128
+ new_finding_and_reporting trigger_node, source, object, ret, request, *args
129
+ end
130
+
131
+ def new_finding_and_reporting trigger_node, source, object, ret, request, *args
132
+ # sent to reporter
133
+ # here we will generate new type of finding
134
+ ruby_finding = Contrast::Agent::Reporting::Finding.new trigger_node.rule_id
135
+ ruby_finding.attach_data trigger_node, source, object, ret, request, *args
136
+ hash_code = Contrast::Utils::HashDigest.generate_event_hash(ruby_finding, source, request)
137
+ ruby_finding.hash_code = hash_code
138
+ # save the current finding
139
+ Contrast::Agent::Reporting::ReportingStorage[hash_code] = ruby_finding
140
+
141
+ new_preflight = Contrast::Agent::Reporting::Preflight.new
142
+ new_preflight_message = Contrast::Agent::Reporting::PreflightMessage.new
143
+ new_preflight_message.routes << request
144
+ new_preflight_message.hash_code = hash_code
145
+ new_preflight_message.data = "#{ trigger_node.rule_id },#{ hash_code }"
146
+ new_preflight.messages << new_preflight_message
147
+ Contrast::Agent.reporter_queue.send_event_immediately(new_preflight)
148
+ end
149
+
113
150
  private
114
151
 
115
152
  def settings
@@ -22,6 +22,10 @@ module Contrast
22
22
  JSON_RULE_NAME = 'name'
23
23
  JSON_CUSTOM_PATCH = 'custom_patch'
24
24
 
25
+ # Our list with rules to be collected and reported back when we have response
26
+ # from the application. Some rules rely on Content-Type validation.
27
+ COLLECTABLE_RULES = %w[reflected-xss].cs__freeze
28
+
25
29
  attr_reader :rule_id, :required_tags, :disallowed_tags, :good_value, :bad_value
26
30
 
27
31
  def initialize trigger_hash = {}, rule_hash = {}
@@ -67,6 +71,10 @@ module Contrast
67
71
  :TYPE_METHOD
68
72
  end
69
73
 
74
+ def collectable?
75
+ COLLECTABLE_RULES.include?(rule_id)
76
+ end
77
+
70
78
  def rule_disabled?
71
79
  ::Contrast::ASSESS.rule_disabled?(rule_id)
72
80
  end
@@ -160,8 +168,8 @@ module Contrast
160
168
  @disallowed_tags << LIMITED_CHARS
161
169
  @disallowed_tags << CUSTOM_ENCODED
162
170
  @disallowed_tags << CUSTOM_VALIDATED
163
- @disallowed_tags << ENCODER_START + loud_name
164
- @disallowed_tags << VALIDATOR_START + loud_name
171
+ @disallowed_tags << (ENCODER_START + loud_name)
172
+ @disallowed_tags << (VALIDATOR_START + loud_name)
165
173
  end
166
174
 
167
175
  def validate_rule_tags tags
@@ -200,14 +208,14 @@ module Contrast
200
208
  satisfied = tags_at.any? && required_tags.all? { |tag| tags_at.any? { |found| found.label == tag } }
201
209
  # if this range matches all the required tags and we're already
202
210
  # chunking, meaning the previous range matched, do nothing
203
- if satisfied && chunking
204
- start_range += 1
205
- next
206
- end
207
211
 
208
212
  # if we are satisfied and we were not chunking, this represents
209
213
  # the start of the next range, so create a new entry.
210
214
  if satisfied
215
+ if chunking
216
+ start_range += 1
217
+ next
218
+ end
211
219
  ranges << Contrast::Agent::Assess::Tag.new('required', 0, start_range)
212
220
  chunking = true
213
221
  # if we are chunking and not satisfied, this represents the end
@@ -18,7 +18,7 @@ module Contrast
18
18
  # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/rules/dataflow/reflected_xss.md
19
19
  def self.valid? _patcher, _object, _ret, _args
20
20
  content_type = Contrast::Agent::REQUEST_TRACKER.current&.response&.content_type
21
- return true unless content_type
21
+ return false unless content_type
22
22
 
23
23
  content_type = content_type.downcase
24
24
  SAFE_CONTENT_TYPES.none? { |safe_type| content_type.index(safe_type) }
@@ -24,29 +24,6 @@ module Contrast
24
24
  instance_variable_defined?(:@_tags) && tags.any?
25
25
  end
26
26
 
27
- # Is the given tag present?
28
- # Used in testing, so found by `be_tagged`, if you're grepping for it
29
- #
30
- # @param label [Symbol] the tag to check for
31
- # @return [Boolean]
32
- def tagged? label
33
- tracked? && tags.key?(label)
34
- end
35
-
36
- # Similar to #tracked?, but limited to a given range.
37
- #
38
- # @param start [Integer] the inclusive start index to check.
39
- # @param finish [Integer] the exclusive end index to check.
40
- # @return [Boolean]
41
- def any_tags_between? start, finish
42
- return false unless tracked?
43
-
44
- tags.each_value do |tag_array|
45
- return true if tag_array.any? { |tag| tag.overlaps?(start, finish) }
46
- end
47
- false
48
- end
49
-
50
27
  # Remove all tags within the given ranges.
51
28
  # This does not delete an entire tag if part of that tag is
52
29
  # outside this range, meaning we may reduce sizes of tags
@@ -102,6 +79,8 @@ module Contrast
102
79
  end
103
80
 
104
81
  # Remove the tag ranges covering the given range
82
+ # and appends any trailing value that might
83
+ # exist after removal of range
105
84
  def remove_tags range
106
85
  return unless tracked?
107
86
 
@@ -112,19 +91,7 @@ module Contrast
112
91
  value.each do |tag|
113
92
  comparison = tag.compare_range(range.begin, range.end)
114
93
  # ABOVE and BELOW are not affected by this check
115
- case comparison
116
- when Contrast::Agent::Assess::Tag::LOW_SPAN
117
- tag.update_end(range.begin)
118
- when Contrast::Agent::Assess::Tag::WITHIN
119
- remove << tag
120
- when Contrast::Agent::Assess::Tag::WITHOUT
121
- new_tag = tag.clone
122
- new_tag.update_start(range.end)
123
- add << new_tag
124
- tag.update_end(range.begin)
125
- when Contrast::Agent::Assess::Tag::HIGH_SPAN
126
- tag.update_start(range.end)
127
- end
94
+ tags_remove_comparison comparison, tag, remove, add, range
128
95
  end
129
96
  value.delete_if { |tag| remove.include?(tag) }
130
97
  Contrast::Utils::TagUtil.ordered_merge(value, add)
@@ -133,6 +100,29 @@ module Contrast
133
100
  full_delete.each { |key| tags.delete(key) }
134
101
  end
135
102
 
103
+ # This method is for the tags comparison
104
+ # the idea is to move the whole case here
105
+ # @param comparison [String] indicates type of removal is to occur
106
+ # @param tag Contrast::Agent::Assess::Tag
107
+ # @param remove [String] holds removed Tag if exists
108
+ # @param add [String] holds trailing Tag if exists
109
+ # @param range [Range] start and stop for idx for removal
110
+ def tags_remove_comparison comparison, tag, remove, add, range
111
+ case comparison
112
+ when Contrast::Agent::Assess::Tag::LOW_SPAN
113
+ tag.update_end(range.begin)
114
+ when Contrast::Agent::Assess::Tag::WITHIN
115
+ remove << tag
116
+ when Contrast::Agent::Assess::Tag::WITHOUT
117
+ new_tag = tag.clone
118
+ new_tag.update_start(range.end)
119
+ add << new_tag
120
+ tag.update_end(range.begin)
121
+ when Contrast::Agent::Assess::Tag::HIGH_SPAN
122
+ tag.update_start(range.end)
123
+ end
124
+ end
125
+
136
126
  # Shift the tag ranges covering the given range
137
127
  # We assume this is for a deletion, meaning we
138
128
  # have to move tags to the left
@@ -176,32 +166,36 @@ module Contrast
176
166
  comparison = tag.compare_range(range.begin, range.end)
177
167
  length = range.end - range.begin
178
168
  # BELOW is not affected by this check
179
- case comparison
180
- # part of the tag is being inserted on
181
- when Contrast::Agent::Assess::Tag::LOW_SPAN
182
- new_tag = tag.clone
183
- new_tag.update_start(range.begin)
184
- new_tag.shift(length)
185
- add << new_tag
186
- tag.update_end(range.begin)
187
- # the tag exists in the inserted range. it is partially shifted
188
- when Contrast::Agent::Assess::Tag::WITHIN
189
- tag.shift(length)
190
- # the tag spans the range. leave the part below alone
191
- when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
192
- new_tag = tag.clone
193
- new_tag.update_start(range.begin)
194
- new_tag.shift(length)
195
- add << new_tag
196
- tag.update_end(range.begin)
197
- when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE # rubocop:disable Lint/DuplicateBranch
198
- tag.shift(length)
199
- end
169
+ shift_tags_comparison comparison, add, tag, length, range
200
170
  end
201
171
  Contrast::Utils::TagUtil.ordered_merge(value, add)
202
172
  end
203
173
  end
204
174
 
175
+ def shift_tags_comparison comparison, add, tag, length, range
176
+ case comparison
177
+ # part of the tag is being inserted on
178
+ when Contrast::Agent::Assess::Tag::LOW_SPAN
179
+ new_tag = tag.clone
180
+ new_tag.update_start(range.begin)
181
+ new_tag.shift(length)
182
+ add << new_tag
183
+ tag.update_end(range.begin)
184
+ # the tag exists in the inserted range. it is partially shifted
185
+ when Contrast::Agent::Assess::Tag::WITHIN
186
+ tag.shift(length)
187
+ # the tag spans the range. leave the part below alone
188
+ when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
189
+ new_tag = tag.clone
190
+ new_tag.update_start(range.begin)
191
+ new_tag.shift(length)
192
+ add << new_tag
193
+ tag.update_end(range.begin)
194
+ when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE # rubocop:disable Lint/DuplicateBranch
195
+ tag.shift(length)
196
+ end
197
+ end
198
+
205
199
  private
206
200
 
207
201
  # Because of the auto-fill thing, we should not allow direct access to
@@ -4,6 +4,7 @@
4
4
  require 'contrast/agent/assess/policy/trigger_method'
5
5
  require 'contrast/components/logger'
6
6
  require 'contrast/extension/module'
7
+ require 'contrast/agent/reporting/report'
7
8
 
8
9
  module Contrast
9
10
  module Agent
@@ -66,7 +67,8 @@ module Contrast
66
67
  # if it looks like a placeholder / pointer to a config, skip it
67
68
  next unless value_passes?(value)
68
69
 
69
- build_finding(clazz, constant_string)
70
+ new_finding_and_reporting clazz, constant_string
71
+ build_finding clazz, constant_string
70
72
  end
71
73
  end
72
74
 
@@ -138,7 +140,8 @@ module Contrast
138
140
  # sort. We leave it to each rule to properly handle these nodes.
139
141
  return unless value_node_passes?(value)
140
142
 
141
- build_finding(mod, name)
143
+ new_finding_and_reporting mod, name
144
+ build_finding mod, name
142
145
  end
143
146
 
144
147
  # Constants can be set as frozen directly. We need to account for
@@ -161,6 +164,14 @@ module Contrast
161
164
  def build_finding clazz, constant_string
162
165
  class_name = clazz.cs__name
163
166
 
167
+ finding = assign_finding class_name, constant_string
168
+ Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
169
+ rescue StandardError => e
170
+ logger.error('Unable to build a finding for Hardcoded Rule', e)
171
+ nil
172
+ end
173
+
174
+ def assign_finding class_name, constant_string
164
175
  finding = Contrast::Api::Dtm::Finding.new
165
176
  finding.rule_id = Contrast::Utils::StringUtils.protobuf_safe_string(rule_id)
166
177
  finding.version = Contrast::Agent::Assess::Policy::TriggerMethod::CURRENT_FINDING_VERSION
@@ -173,10 +184,33 @@ module Contrast
173
184
  hash = Contrast::Utils::HashDigest.generate_class_scanning_hash(finding)
174
185
  finding.hash_code = Contrast::Utils::StringUtils.protobuf_safe_string(hash)
175
186
  finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
176
- Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
177
- rescue StandardError => e
178
- logger.error('Unable to build a finding for Hardcoded Rule', e)
179
- nil
187
+ finding
188
+ end
189
+
190
+ def new_finding_and_reporting clazz, constant_string
191
+ return unless Contrast::Agent::Reporter.enabled?
192
+
193
+ # sent to reporter
194
+ # and add logger message for the report of the preflight
195
+ new_preflight = Contrast::Agent::Reporting::Preflight.new
196
+ new_preflight_message = Contrast::Agent::Reporting::PreflightMessage.new
197
+ new_preflight_message.hash_code = hash
198
+ new_preflight_message.data = "#{ rule_id },#{ hash }"
199
+ new_preflight.messages << new_preflight_message
200
+
201
+ # extract to new method
202
+ # here we will generate new type of finding
203
+ ruby_finding = Contrast::Agent::Reporting::Finding.new rule_id
204
+ ruby_finding.hash_code = hash
205
+ ruby_finding.properties[SOURCE_KEY] = clazz.cs__name
206
+ ruby_finding.properties[CONSTANT_NAME_KEY] = constant_string
207
+ ruby_finding.properties[CODE_SOURCE_KEY] = constant_string + redacted_marker
208
+ save_and_report_finding ruby_finding, new_preflight
209
+ end
210
+
211
+ def save_and_report_finding ruby_finding, new_preflight
212
+ Contrast::Agent::Reporting::ReportingStorage[hash] = ruby_finding
213
+ Contrast::Agent.reporter_queue.send_event_immediately(new_preflight)
180
214
  end
181
215
  end
182
216
  end
@@ -15,10 +15,10 @@ module Contrast
15
15
  def initialize
16
16
  super
17
17
  @fields = MetricsHash.new(Numeric)
18
- @fields['_filter'] = 0
18
+ @fields['_filler'] = 0
19
19
  end
20
20
 
21
- def to_json **_args
21
+ def to_hash **_args
22
22
  super.merge!({ fields: @fields })
23
23
  end
24
24
  end