contrast-agent 4.11.0 → 4.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +1 -0
  3. data/ext/cs__assess_module/cs__assess_module.c +48 -0
  4. data/ext/cs__assess_module/cs__assess_module.h +7 -0
  5. data/ext/cs__common/cs__common.c +24 -7
  6. data/ext/cs__common/cs__common.h +12 -2
  7. data/ext/cs__contrast_patch/cs__contrast_patch.c +48 -11
  8. data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -2
  9. data/ext/cs__os_information/cs__os_information.c +31 -0
  10. data/ext/cs__os_information/cs__os_information.h +7 -0
  11. data/ext/{cs__protect_kernel → cs__os_information}/extconf.rb +0 -0
  12. data/lib/contrast/agent/assess/contrast_event.rb +1 -1
  13. data/lib/contrast/agent/assess/contrast_object.rb +1 -1
  14. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
  15. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
  16. data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
  17. data/lib/contrast/agent/assess/policy/preshift.rb +19 -6
  18. data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
  19. data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
  20. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
  21. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -0
  22. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
  23. data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
  24. data/lib/contrast/agent/assess/policy/trigger_method.rb +45 -110
  25. data/lib/contrast/agent/assess/policy/trigger_node.rb +62 -21
  26. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
  27. data/lib/contrast/agent/assess/property/tagged.rb +66 -189
  28. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
  29. data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
  30. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -0
  31. data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
  32. data/lib/contrast/agent/middleware.rb +14 -62
  33. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +0 -1
  34. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -44
  35. data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
  36. data/lib/contrast/agent/patching/policy/patch.rb +37 -238
  37. data/lib/contrast/agent/patching/policy/patcher.rb +15 -50
  38. data/lib/contrast/agent/reporting/report.rb +21 -0
  39. data/lib/contrast/agent/reporting/reporter.rb +142 -0
  40. data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
  41. data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
  42. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
  43. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
  44. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
  45. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
  46. data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
  47. data/lib/contrast/agent/request.rb +2 -81
  48. data/lib/contrast/agent/request_context.rb +18 -126
  49. data/lib/contrast/agent/request_context_extend.rb +138 -0
  50. data/lib/contrast/agent/request_handler.rb +7 -3
  51. data/lib/contrast/agent/response.rb +2 -73
  52. data/lib/contrast/agent/rule_set.rb +2 -4
  53. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +94 -0
  54. data/lib/contrast/agent/static_analysis.rb +5 -3
  55. data/lib/contrast/agent/telemetry.rb +137 -0
  56. data/lib/contrast/agent/telemetry_event.rb +33 -0
  57. data/lib/contrast/agent/thread_watcher.rb +66 -11
  58. data/lib/contrast/agent/version.rb +1 -1
  59. data/lib/contrast/agent.rb +21 -1
  60. data/lib/contrast/api/communication/connection_status.rb +10 -7
  61. data/lib/contrast/api/communication/messaging_queue.rb +37 -3
  62. data/lib/contrast/api/communication/response_processor.rb +15 -8
  63. data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
  64. data/lib/contrast/api/communication/socket.rb +6 -8
  65. data/lib/contrast/api/communication/socket_client.rb +29 -12
  66. data/lib/contrast/api/communication/speedracer.rb +37 -1
  67. data/lib/contrast/api/communication/tcp_socket.rb +4 -3
  68. data/lib/contrast/api/communication/unix_socket.rb +1 -0
  69. data/lib/contrast/api/decorators/finding.rb +45 -0
  70. data/lib/contrast/components/api.rb +90 -0
  71. data/lib/contrast/components/app_context.rb +10 -41
  72. data/lib/contrast/components/app_context_extend.rb +78 -0
  73. data/lib/contrast/components/assess.rb +7 -0
  74. data/lib/contrast/components/base.rb +23 -0
  75. data/lib/contrast/components/config.rb +92 -13
  76. data/lib/contrast/components/contrast_service.rb +11 -0
  77. data/lib/contrast/components/sampling.rb +2 -2
  78. data/lib/contrast/config/agent_configuration.rb +1 -1
  79. data/lib/contrast/config/api_configuration.rb +27 -0
  80. data/lib/contrast/config/api_proxy_configuration.rb +14 -0
  81. data/lib/contrast/config/application_configuration.rb +2 -3
  82. data/lib/contrast/config/assess_configuration.rb +3 -2
  83. data/lib/contrast/config/base_configuration.rb +17 -28
  84. data/lib/contrast/config/certification_configuration.rb +15 -0
  85. data/lib/contrast/config/env_variables.rb +18 -0
  86. data/lib/contrast/config/heap_dump_configuration.rb +6 -6
  87. data/lib/contrast/config/inventory_configuration.rb +1 -5
  88. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  89. data/lib/contrast/config/request_audit_configuration.rb +18 -0
  90. data/lib/contrast/config/root_configuration.rb +1 -0
  91. data/lib/contrast/config/ruby_configuration.rb +6 -6
  92. data/lib/contrast/config/service_configuration.rb +2 -2
  93. data/lib/contrast/config.rb +1 -1
  94. data/lib/contrast/configuration.rb +4 -2
  95. data/lib/contrast/extension/assess/array.rb +5 -7
  96. data/lib/contrast/framework/manager.rb +22 -44
  97. data/lib/contrast/framework/manager_extend.rb +50 -0
  98. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
  99. data/lib/contrast/framework/rails/patch/support.rb +31 -29
  100. data/lib/contrast/framework/rails/railtie.rb +1 -1
  101. data/lib/contrast/framework/sinatra/support.rb +2 -1
  102. data/lib/contrast/logger/application.rb +4 -0
  103. data/lib/contrast/logger/log.rb +8 -103
  104. data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
  105. data/lib/contrast/utils/assess/property/tagged_utils.rb +165 -0
  106. data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
  107. data/lib/contrast/utils/assess/tracking_util.rb +20 -15
  108. data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
  109. data/lib/contrast/utils/class_util.rb +65 -54
  110. data/lib/contrast/utils/exclude_key.rb +20 -0
  111. data/lib/contrast/utils/findings.rb +62 -0
  112. data/lib/contrast/utils/hash_digest.rb +10 -73
  113. data/lib/contrast/utils/hash_digest_extend.rb +86 -0
  114. data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
  115. data/lib/contrast/utils/heap_dump_util.rb +2 -65
  116. data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
  117. data/lib/contrast/utils/io_util.rb +1 -1
  118. data/lib/contrast/utils/log_utils.rb +108 -0
  119. data/lib/contrast/utils/lru_cache.rb +4 -2
  120. data/lib/contrast/utils/metrics_hash.rb +59 -0
  121. data/lib/contrast/utils/middleware_utils.rb +87 -0
  122. data/lib/contrast/utils/net_http_base.rb +158 -0
  123. data/lib/contrast/utils/object_share.rb +1 -0
  124. data/lib/contrast/utils/os.rb +23 -0
  125. data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
  126. data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
  127. data/lib/contrast/utils/request_utils.rb +88 -0
  128. data/lib/contrast/utils/response_utils.rb +97 -0
  129. data/lib/contrast/utils/substitution_utils.rb +167 -0
  130. data/lib/contrast/utils/tag_util.rb +9 -9
  131. data/lib/contrast/utils/telemetry.rb +79 -0
  132. data/lib/contrast/utils/telemetry_client.rb +90 -0
  133. data/lib/contrast/utils/telemetry_identifier.rb +130 -0
  134. data/lib/contrast.rb +19 -1
  135. data/resources/assess/policy.json +12 -6
  136. data/resources/deadzone/policy.json +86 -5
  137. data/ruby-agent.gemspec +7 -6
  138. data/service_executables/VERSION +1 -1
  139. data/service_executables/linux/contrast-service +0 -0
  140. data/service_executables/mac/contrast-service +0 -0
  141. metadata +68 -26
  142. data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
  143. data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
  144. data/lib/contrast/config/default_value.rb +0 -17
  145. data/lib/contrast/extension/protect/kernel.rb +0 -29
@@ -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) }
@@ -5,6 +5,7 @@ require 'contrast/agent/assess/tag'
5
5
  require 'contrast/utils/object_share'
6
6
  require 'contrast/utils/string_utils'
7
7
  require 'contrast/utils/tag_util'
8
+ require 'contrast/utils/assess/property/tagged_utils'
8
9
 
9
10
  module Contrast
10
11
  module Agent
@@ -13,6 +14,7 @@ module Contrast
13
14
  # This module serves to hold the functionality required for the
14
15
  # management of our dataflow tags.
15
16
  module Tagged
17
+ include Contrast::Utils::Assess::TaggedUtils
16
18
  # Is any tag present?
17
19
  # Creating Tags is expensive and we check for Tags all the time on
18
20
  # untracked things. ALWAYS!!! call this method before checking if an
@@ -22,148 +24,6 @@ module Contrast
22
24
  instance_variable_defined?(:@_tags) && tags.any?
23
25
  end
24
26
 
25
- # Is the given tag present?
26
- # Used in testing, so found by `be_tagged`, if you're grepping for it
27
- #
28
- # @param label [Symbol] the tag to check for
29
- # @return [Boolean]
30
- def tagged? label
31
- tracked? && tags.key?(label)
32
- end
33
-
34
- # Similar to #tracked?, but limited to a given range.
35
- #
36
- # @param start [Integer] the inclusive start index to check.
37
- # @param finish [Integer] the exclusive end index to check.
38
- # @return [Boolean]
39
- def any_tags_between? start, finish
40
- return false unless tracked?
41
-
42
- tags.each_value do |tag_array|
43
- return true if tag_array.any? { |tag| tag.overlaps?(start, finish) }
44
- end
45
- false
46
- end
47
-
48
- # Find all of the ranges that span a given index. This is used
49
- # in propagation when we need to shift tags about. For instance, in
50
- # the append method when we need to see if any tag at the end needs
51
- # to be expanded out to the size of the new String.
52
- #
53
- # Note: Tags do not know their key, so this is only the range covered
54
- #
55
- # @param idx [Integer] the index to check for tags
56
- # @return [Array<Contrast::Agent::Assess::Tag>] a set of all the Tags
57
- # covering the given index. This is only the ranges, not the names.
58
- def tags_at idx
59
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
60
-
61
- at = []
62
- tags.each_value do |tag_array|
63
- tag_array.each do |tag|
64
- if tag.covers?(idx)
65
- at << tag
66
- elsif tag.above?(idx)
67
- break
68
- end
69
- end
70
- end
71
- at
72
- end
73
-
74
- # given a range, select all tags in that range the selected tags are
75
- # shifted such that the start index of the new tag (0) aligns with
76
- # the given start index in the range
77
- #
78
- # current tags: 5-15
79
- # range : 5-10
80
- # result : 0-05
81
- #
82
- # Note that we disable Lint/DuplicateBranch in this branch in order
83
- # list out all tag range cases in the proper order to make this
84
- # easier to understand
85
- #
86
- # @param range [Range] the span to check, inclusive to exclusive
87
- # @return [Hash{String => Contrast::Agent::Assess::Tag}] the hash of
88
- # key to tags
89
- def tags_at_range range
90
- return Contrast::Utils::ObjectShare::EMPTY_HASH unless tracked?
91
-
92
- at = Hash.new { |h, k| h[k] = [] }
93
- tags.each_pair do |key, value|
94
- add = nil
95
- value.each do |tag|
96
- within_range = resize_to_range(tag, range)
97
- if within_range
98
- add ||= []
99
- add << within_range
100
- end
101
- end
102
- next unless add&.any?
103
-
104
- at[key] = add
105
- end
106
- at
107
- end
108
-
109
- # Given a tag name and range object, add a new tag to this
110
- # collection. If the given range touches an existing tag,
111
- # we'll combine the two, adjusting the existing one and
112
- # dropping this new one.
113
- #
114
- # @param label [String] the name of the tag
115
- # @param range [Range] the Range that the tag covers, inclusive to
116
- # exclusive
117
- def add_tag label, range
118
- length = range.end - range.begin
119
- tag = Contrast::Agent::Assess::Tag.new(label, length, range.begin)
120
- existing = fetch_tag(label)
121
- tags[label] = Contrast::Utils::TagUtil.ordered_merge(existing, tag)
122
- end
123
-
124
- def set_tags label, tag_ranges
125
- tags[label] = tag_ranges
126
- end
127
-
128
- # Remove all tags with a given label
129
- def delete_tags label
130
- tags.delete(label) if tracked?
131
- end
132
-
133
- # Reset the tag hash
134
- def clear_tags
135
- tags.clear if tracked?
136
- end
137
-
138
- # Returns a list of all current tag labels, most likely to be used for
139
- # a splat operation
140
- def tag_keys
141
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless tracked?
142
-
143
- tags.keys
144
- end
145
-
146
- # Calls merge to combine touching or overlapping tags
147
- # Deletes empty tags
148
- def cleanup_tags
149
- return unless tracked?
150
-
151
- Contrast::Utils::TagUtil.merge_tags(tags)
152
- tags.delete_if { |_, value| value.empty? }
153
- end
154
-
155
- # We'll use this as a helper method to retrieve tags from the hash.
156
- # Because the hash auto-populates an empty array when we try to
157
- # access a tag in it, we cannot use the [] method without side
158
- # effect. To get around this, we'll use a fetch work around.
159
- #
160
- # @param label [Symbol] the label to look up
161
- # @return [Array<Contrast::Agent::Assess::Tag>] all the tags with
162
- # that label
163
- def fetch_tag label
164
- tags.fetch(label, nil) if tracked?
165
- end
166
-
167
27
  # Remove all tags within the given ranges.
168
28
  # This does not delete an entire tag if part of that tag is
169
29
  # outside this range, meaning we may reduce sizes of tags
@@ -218,20 +78,9 @@ module Contrast
218
78
  end
219
79
  end
220
80
 
221
- # Because of the auto-fill thing, we should not allow direct access to
222
- # the tags hash. Instead, the methods above should be used to do
223
- # operations like add, delete, and fetch.
224
- #
225
- # CONTRAST-22914
226
- # please do NOT expose this w/ an attr_reader / accessor. there are
227
- # helper methods in this class that safely access the hash. the tags
228
- # method is private to avoid the side effect of a direct lookup with
229
- # `[]` adding an empty array to the hash.
230
- def tags
231
- @_tags ||= Hash.new { |h, k| h[k] = [] }
232
- end
233
-
234
81
  # Remove the tag ranges covering the given range
82
+ # and appends any trailing value that might
83
+ # exist after removal of range
235
84
  def remove_tags range
236
85
  return unless tracked?
237
86
 
@@ -242,19 +91,7 @@ module Contrast
242
91
  value.each do |tag|
243
92
  comparison = tag.compare_range(range.begin, range.end)
244
93
  # ABOVE and BELOW are not affected by this check
245
- case comparison
246
- when Contrast::Agent::Assess::Tag::LOW_SPAN
247
- tag.update_end(range.begin)
248
- when Contrast::Agent::Assess::Tag::WITHIN
249
- remove << tag
250
- when Contrast::Agent::Assess::Tag::WITHOUT
251
- new_tag = tag.clone
252
- new_tag.update_start(range.end)
253
- add << new_tag
254
- tag.update_end(range.begin)
255
- when Contrast::Agent::Assess::Tag::HIGH_SPAN
256
- tag.update_start(range.end)
257
- end
94
+ tags_remove_comparison comparison, tag, remove, add, range
258
95
  end
259
96
  value.delete_if { |tag| remove.include?(tag) }
260
97
  Contrast::Utils::TagUtil.ordered_merge(value, add)
@@ -263,6 +100,29 @@ module Contrast
263
100
  full_delete.each { |key| tags.delete(key) }
264
101
  end
265
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
+
266
126
  # Shift the tag ranges covering the given range
267
127
  # We assume this is for a deletion, meaning we
268
128
  # have to move tags to the left
@@ -306,34 +166,51 @@ module Contrast
306
166
  comparison = tag.compare_range(range.begin, range.end)
307
167
  length = range.end - range.begin
308
168
  # BELOW is not affected by this check
309
- case comparison
310
- # part of the tag is being inserted on
311
- when Contrast::Agent::Assess::Tag::LOW_SPAN
312
- new_tag = tag.clone
313
- new_tag.update_start(range.begin)
314
- new_tag.shift(length)
315
- add << new_tag
316
- tag.update_end(range.begin)
317
- # the tag exists in the inserted range. it is partially shifted
318
- when Contrast::Agent::Assess::Tag::WITHIN
319
- tag.shift(length)
320
- # the tag spans the range. leave the part below alone
321
- when Contrast::Agent::Assess::Tag::WITHOUT # rubocop:disable Lint/DuplicateBranch
322
- new_tag = tag.clone
323
- new_tag.update_start(range.begin)
324
- new_tag.shift(length)
325
- add << new_tag
326
- tag.update_end(range.begin)
327
- when Contrast::Agent::Assess::Tag::HIGH_SPAN, Contrast::Agent::Assess::Tag::ABOVE # rubocop:disable Lint/DuplicateBranch
328
- tag.shift(length)
329
- end
169
+ shift_tags_comparison comparison, add, tag, length, range
330
170
  end
331
171
  Contrast::Utils::TagUtil.ordered_merge(value, add)
332
172
  end
333
173
  end
334
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
+
335
199
  private
336
200
 
201
+ # Because of the auto-fill thing, we should not allow direct access to
202
+ # the tags hash. Instead, the methods above should be used to do
203
+ # operations like add, delete, and fetch.
204
+ #
205
+ # CONTRAST-22914
206
+ # please do NOT expose this w/ an attr_reader / accessor. there are
207
+ # helper methods in this class that safely access the hash. the tags
208
+ # method is private to avoid the side effect of a direct lookup with
209
+ # `[]` adding an empty array to the hash.
210
+ def tags
211
+ @_tags ||= Hash.new { |h, k| h[k] = [] }
212
+ end
213
+
337
214
  # Given a tag, compare it to a given range and, if any part of that tag is within the range, return a new tag
338
215
  # covering the union of the original tag and the range. This new tag will start at the
339
216
  # max(tag.start, range.start) and end at min(tag.end, range.end)
@@ -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
@@ -35,6 +35,12 @@ module Contrast
35
35
  end
36
36
  end
37
37
 
38
+ def validate
39
+ return if class_name
40
+
41
+ raise(ArgumentError, "#{ node_class } #{ id } did not have a proper class name. Unable to create.")
42
+ end
43
+
38
44
  def module_names
39
45
  @_module_names ||= Set.new(deadzones.map(&:class_name))
40
46
  end
@@ -71,6 +71,7 @@ module Contrast
71
71
 
72
72
  # Populate the library_usages field of the Activity message using the data stored in the @gemdigest_cache.
73
73
  #
74
+ # TODO: RUBY-1355
74
75
  # @param activity [Contrast::Api::Dtm::Activity] the message to which to append the usage data
75
76
  def generate_library_usage activity
76
77
  return unless enabled?
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/utils/metrics_hash'
5
+ require 'contrast/agent/telemetry_event'
6
+
7
+ module Contrast
8
+ module Agent
9
+ # This class will hold the basic information for a Telemetry Event
10
+ class MetricTelemetryEvent < Contrast::Agent::TelemetryEvent
11
+ include Contrast::Utils
12
+
13
+ attr_reader :fields
14
+
15
+ def initialize
16
+ super
17
+ @fields = MetricsHash.new(Numeric)
18
+ @fields['_filler'] = 0
19
+ end
20
+
21
+ def to_hash **_args
22
+ super.merge!({ fields: @fields })
23
+ end
24
+ end
25
+ end
26
+ end
@@ -10,8 +10,11 @@ require 'contrast/utils/object_share'
10
10
  require 'contrast/components/logger'
11
11
  require 'contrast/components/scope'
12
12
  require 'contrast/utils/heap_dump_util'
13
+ require 'contrast/utils/telemetry'
13
14
  require 'contrast/agent/request_handler'
14
15
  require 'contrast/agent/static_analysis'
16
+ require 'contrast/agent/startup_metrics_telemetry_event'
17
+ require 'contrast/utils/middleware_utils'
15
18
 
16
19
  require 'contrast/utils/timer'
17
20
 
@@ -23,6 +26,7 @@ module Contrast
23
26
  class Middleware
24
27
  include Contrast::Components::Logger::InstanceMethods
25
28
  include Contrast::Components::Scope::InstanceMethods
29
+ include Contrast::Utils::MiddlewareUtils
26
30
 
27
31
  attr_reader :app
28
32
 
@@ -64,21 +68,6 @@ module Contrast
64
68
 
65
69
  private
66
70
 
67
- def setup_agent
68
- ::Contrast::SETTINGS.reset_state
69
-
70
- inform_deprecations
71
-
72
- if ::Contrast::CONFIG.invalid?
73
- ::Contrast::AGENT.disable!
74
- logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
75
- elsif ::Contrast::AGENT.disabled?
76
- logger.warn('Contrast disabled by configuration. Continuing without instrumentation.')
77
- else
78
- ::Contrast::AGENT.enable!
79
- end
80
- end
81
-
82
71
  # Startup the Agent as part of the initialization process:
83
72
  # - start the service sending thread, responsible for sending and processing messages
84
73
  # - start the heartbeat thread, which triggers service startup
@@ -89,6 +78,13 @@ module Contrast
89
78
  Contrast::Agent.thread_watcher.ensure_running?
90
79
  end
91
80
 
81
+ if Contrast::Agent::Telemetry.enabled?
82
+ logger.debug_with_time('middleware: sending startup metrics telemetry event') do
83
+ event = Contrast::Agent::StartupMetricsTelemetryEvent.new
84
+ Contrast::Agent.thread_watcher.telemetry_queue.send_event(event)
85
+ end
86
+ end
87
+
92
88
  logger.debug_with_time('middleware: instrument shared libraries and patch') do
93
89
  Contrast::Agent::Patching::Policy::Patcher.patch
94
90
  end
@@ -165,6 +161,9 @@ module Contrast
165
161
  with_contrast_scope do
166
162
  context.extract_after(response) # update context with final response information
167
163
 
164
+ # Build and report all collected findings prior response
165
+ Contrast::Agent::FINDINGS.report_collected_findings unless Contrast::Agent::FINDINGS.collection.empty?
166
+
168
167
  if Contrast::Agent.framework_manager.streaming?(env)
169
168
  context.reset_activity
170
169
  request_handler.stream_safe_postfilter
@@ -178,53 +177,6 @@ module Contrast
178
177
 
179
178
  logger.error('Unable to execute agent post_call', e)
180
179
  end
181
-
182
- def application_code env
183
- logger.trace_with_time('application') do
184
- app.call(env)
185
- end
186
- rescue Contrast::SecurityException => e
187
- logger.trace('Security Exception raised during application lifecycle to prevent an attack', e)
188
- raise e
189
- end
190
-
191
- SECURITY_EXCEPTION_MARKER = 'Contrast::SecurityException'
192
- # We're only going to suppress SecurityExceptions indicating a blocked attack. And, only if the
193
- # config.agent.ruby.exceptions.capture? is set
194
- def handle_exception exception
195
- if security_exception?(exception)
196
- exception_control = ::Contrast::AGENT.exception_control
197
- raise exception unless exception_control[:enable]
198
-
199
- [exception_control[:status], {}, [exception_control[:message]]]
200
- else
201
- logger.debug('Re-throwing original error', exception)
202
- raise exception
203
- end
204
- end
205
-
206
- # Is the given exception one raised by our Protect code?
207
- #
208
- # @param exception [Exception]
209
- # @return [Boolean]
210
- def security_exception? exception
211
- exception.is_a?(Contrast::SecurityException) || exception.message&.include?(SECURITY_EXCEPTION_MARKER)
212
- end
213
-
214
- # As we deprecate support to prepare to remove dead code, we need to inform our users still relying on the now
215
- # deprecated and soon to be removed functionality. This method handles doing that by leveraging the standard
216
- # Kernel#warn approach
217
- def inform_deprecations
218
- # Ruby 2.5 is currently in security maintenance, meaning int is only receiving updates for security issues. It
219
- # will move to eol on 31 March 2021. As such, we can remove support for it in Q3. We'll begin the deprecation
220
- # warnings now so that customers have time to reach out if they'll be impacted.
221
- # TODO: RUBY-715 remove this part of the method, leaving it empty if there are no other deprecations, when we
222
- # drop 2.5 support.
223
- return unless RUBY_VERSION < '2.6.0'
224
-
225
- Kernel.warn('[Contrast Security] [DEPRECATION] Support for Ruby 2.5 will be removed in April 2021. '\
226
- 'Please contact Customer Support prior if you require continued support.')
227
- end
228
180
  end
229
181
  end
230
182
  end
@@ -46,7 +46,6 @@ module Contrast
46
46
  path_part = "cs__assess_#{ p }"
47
47
  Contrast::Extension::Assess::InstrumentHelper.instrument "#{ path_part }/#{ path_part }"
48
48
  end
49
- Contrast::Extension::Assess::InstrumentHelper.instrument 'cs__protect_kernel/cs__protect_kernel'
50
49
  true
51
50
  end
52
51
  end
@@ -1,12 +1,15 @@
1
1
  # Copyright (c) 2021 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
2
  # frozen_string_literal: true
3
3
 
4
+ require 'contrast/agent/patching/policy/method_policy_extend'
5
+
4
6
  module Contrast
5
7
  module Agent
6
8
  module Patching
7
9
  module Policy
8
10
  # This class is used to map each method to the trigger node that applies to it
9
11
  class MethodPolicy
12
+ extend Contrast::Agent::Patching::Policy::MethodPolicyExtend
10
13
  attr_reader :deadzone_node, :inventory_node, :propagation_node, :protect_node, :trigger_node
11
14
  attr_accessor :source_node, :method_name, :method_visibility, :instance_method
12
15
 
@@ -60,50 +63,6 @@ module Contrast
60
63
  # defined by #nodes.
61
64
  @_method_scopes ||= nodes.flat_map(&:method_scope).tap(&:compact!).tap(&:uniq!)
62
65
  end
63
-
64
- class << self
65
- # Given a Contrast::Agent::Patching::Policy::ModulePolicy, parse
66
- # out its information for the given method in order to construct a
67
- # Contrast::Agent::Patching::Policy::MethodPolicy
68
- #
69
- # @param method_name [Symbol] the name of the method for this policy
70
- # @param module_policy [Contrast::Agent::Patching::Policy::ModulePolicy]
71
- # the entire policy for this module
72
- # @param instance_method [Boolean] true if this method is an
73
- # instance method
74
- # @return [Contrast::Agent::Patching::Policy::MethodPolicy]
75
- def build_method_policy method_name, module_policy, instance_method
76
- source_node = find_method_node(module_policy.source_nodes, method_name, instance_method)
77
- propagation_node = find_method_node(module_policy.propagator_nodes, method_name, instance_method)
78
- trigger_node = find_method_node(module_policy.trigger_nodes, method_name, instance_method)
79
- protect_node = find_method_node(module_policy.protect_nodes, method_name, instance_method)
80
- inventory_node = find_method_node(module_policy.inventory_nodes, method_name, instance_method)
81
- deadzone_node = find_method_node(module_policy.deadzone_nodes, method_name, instance_method)
82
- method_visibility = find_visibility(source_node, propagation_node, trigger_node, protect_node,
83
- inventory_node, deadzone_node)
84
- MethodPolicy.new(method_name: method_name,
85
- method_visibility: method_visibility,
86
- instance_method: instance_method,
87
- source_node: source_node,
88
- propagation_node: propagation_node,
89
- trigger_node: trigger_node,
90
- protect_node: protect_node,
91
- inventory_node: inventory_node,
92
- deadzone_node: deadzone_node)
93
- end
94
-
95
- def find_method_node nodes, method_name, is_instance_method
96
- return unless nodes
97
-
98
- nodes.find do |node|
99
- node.instance_method? == is_instance_method && node.method_name == method_name
100
- end
101
- end
102
-
103
- def find_visibility *nodes
104
- nodes.find { |node| node }&.method_visibility
105
- end
106
- end
107
66
  end
108
67
  end
109
68
  end