contrast-agent 4.13.1 → 4.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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