contrast-agent 3.15.0 → 3.16.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent.rb +2 -9
  3. data/lib/contrast/agent/assess/contrast_event.rb +142 -70
  4. data/lib/contrast/agent/assess/events/source_event.rb +1 -1
  5. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +10 -3
  6. data/lib/contrast/agent/assess/policy/policy_node.rb +15 -10
  7. data/lib/contrast/agent/assess/policy/policy_scanner.rb +7 -1
  8. data/lib/contrast/agent/assess/policy/propagator/insert.rb +1 -1
  9. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +0 -3
  10. data/lib/contrast/agent/assess/policy/propagator/select.rb +1 -3
  11. data/lib/contrast/agent/assess/policy/propagator/splat.rb +0 -5
  12. data/lib/contrast/agent/assess/policy/propagator/split.rb +12 -13
  13. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +21 -14
  14. data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +4 -5
  15. data/lib/contrast/agent/assess/policy/trigger_method.rb +39 -14
  16. data/lib/contrast/agent/assess/policy/trigger_node.rb +31 -37
  17. data/lib/contrast/agent/assess/property/evented.rb +5 -18
  18. data/lib/contrast/agent/assess/property/tagged.rb +9 -3
  19. data/lib/contrast/agent/assess/property/updated.rb +0 -5
  20. data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +58 -5
  21. data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +23 -8
  22. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +82 -14
  23. data/lib/contrast/agent/assess/tag.rb +1 -1
  24. data/lib/contrast/agent/at_exit_hook.rb +5 -5
  25. data/lib/contrast/agent/patching/policy/after_load_patch.rb +5 -5
  26. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +20 -20
  27. data/lib/contrast/agent/patching/policy/module_policy.rb +10 -10
  28. data/lib/contrast/agent/patching/policy/policy.rb +16 -2
  29. data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +3 -5
  30. data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
  31. data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +1 -0
  32. data/lib/contrast/agent/request.rb +34 -34
  33. data/lib/contrast/agent/static_analysis.rb +6 -6
  34. data/lib/contrast/agent/version.rb +1 -1
  35. data/lib/contrast/api/communication/socket_client.rb +36 -1
  36. data/lib/contrast/api/decorators/address.rb +13 -13
  37. data/lib/contrast/api/decorators/message.rb +1 -0
  38. data/lib/contrast/api/decorators/trace_event.rb +20 -18
  39. data/lib/contrast/components/app_context.rb +39 -30
  40. data/lib/contrast/components/contrast_service.rb +9 -9
  41. data/lib/contrast/components/settings.rb +20 -23
  42. data/lib/contrast/config/service_configuration.rb +4 -2
  43. data/lib/contrast/configuration.rb +1 -1
  44. data/lib/contrast/extension/assess/array.rb +7 -3
  45. data/lib/contrast/extension/assess/erb.rb +5 -0
  46. data/lib/contrast/extension/assess/eval_trigger.rb +6 -6
  47. data/lib/contrast/extension/assess/exec_trigger.rb +1 -1
  48. data/lib/contrast/extension/assess/fiber.rb +3 -3
  49. data/lib/contrast/extension/assess/hash.rb +3 -3
  50. data/lib/contrast/extension/assess/kernel.rb +18 -20
  51. data/lib/contrast/extension/assess/marshal.rb +8 -4
  52. data/lib/contrast/extension/assess/regexp.rb +3 -3
  53. data/lib/contrast/extension/assess/string.rb +13 -11
  54. data/lib/contrast/extension/protect/kernel.rb +3 -3
  55. data/lib/contrast/framework/base_support.rb +1 -1
  56. data/lib/contrast/framework/manager.rb +3 -3
  57. data/lib/contrast/framework/rack/patch/session_cookie.rb +9 -9
  58. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +13 -13
  59. data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +10 -10
  60. data/lib/contrast/framework/rails/patch/support.rb +1 -1
  61. data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +11 -11
  62. data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +12 -12
  63. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +3 -3
  64. data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +12 -12
  65. data/lib/contrast/framework/sinatra/patch/base.rb +11 -11
  66. data/lib/contrast/framework/sinatra/support.rb +4 -4
  67. data/lib/contrast/logger/log.rb +7 -2
  68. data/lib/contrast/utils/invalid_configuration_util.rb +2 -5
  69. data/resources/assess/policy.json +31 -12
  70. data/ruby-agent.gemspec +4 -3
  71. data/service_executables/VERSION +1 -1
  72. data/service_executables/linux/contrast-service +0 -0
  73. data/service_executables/mac/contrast-service +0 -0
  74. metadata +31 -17
@@ -17,12 +17,12 @@ module Contrast
17
17
  # report the already-loaded gems.
18
18
  def catchup
19
19
  @_catchup ||= begin
20
- with_contrast_scope do
21
- Contrast::Utils::GemfileReader.instance.map_loaded_classes
22
- send_inventory_message
23
- true
24
- end
25
- end
20
+ with_contrast_scope do
21
+ Contrast::Utils::GemfileReader.instance.map_loaded_classes
22
+ send_inventory_message
23
+ true
24
+ end
25
+ end
26
26
  rescue StandardError => e
27
27
  logger.warn('Unable to run post-initialization static analysis', e)
28
28
  end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '3.15.0'
6
+ VERSION = '3.16.0'
7
7
  end
8
8
  end
@@ -15,7 +15,7 @@ module Contrast
15
15
  # service proxy and tracks the state of that proxy.
16
16
  class SocketClient
17
17
  include Contrast::Components::Interface
18
- access_component :contrast_service, :logging
18
+ access_component :config, :contrast_service, :logging
19
19
 
20
20
  def initialize
21
21
  @socket = init_connection
@@ -34,6 +34,7 @@ module Contrast
34
34
  private
35
35
 
36
36
  def init_connection
37
+ log_connection
37
38
  if CONTRAST_SERVICE.use_tcp?
38
39
  Contrast::Api::Communication::TcpSocket.new(CONTRAST_SERVICE.host, CONTRAST_SERVICE.port)
39
40
  else
@@ -41,6 +42,40 @@ module Contrast
41
42
  end
42
43
  end
43
44
 
45
+ def log_connection
46
+ # The socket is set,
47
+ if CONFIG.root.agent.service.socket
48
+ logger.info('Connecting to the Contrast Service using a UnixSocket socket',
49
+ socket: CONTRAST_SERVICE.socket_path)
50
+ return
51
+ end
52
+ # The host & port are set,
53
+ if CONFIG.root.agent.service.host && CONFIG.root.agent.service.port
54
+ logger.info('Connecting to the Contrast Service using a TCP socket',
55
+ host: CONTRAST_SERVICE.host,
56
+ port: CONTRAST_SERVICE.port)
57
+ return
58
+ end
59
+
60
+ # Or something is not set.
61
+ msg = if CONFIG.root.agent.service.host
62
+ 'Missing a required connection value to the Contrast Service. ' \
63
+ '`agent.service.port` is not set. ' \
64
+ 'Falling back to default TCP socket port.'
65
+ elsif CONFIG.root.agent.service.port
66
+ 'Missing a required connection value to the Contrast Service. ' \
67
+ '`agent.service.host` is not set. ' \
68
+ 'Falling back to default TCP socket host.'
69
+ else
70
+ 'Missing a required connection value to the Contrast Service. ' \
71
+ 'Neither `agent.service.socket` nor the pair of `agent.service.host` and `agent.service.port` are set. '\
72
+ 'Falling back to default TCP socket.'
73
+ end
74
+ logger.warn(msg,
75
+ host: CONTRAST_SERVICE.host,
76
+ port: CONTRAST_SERVICE.port)
77
+ end
78
+
44
79
  def send_message msg
45
80
  return unless msg
46
81
 
@@ -28,19 +28,19 @@ module Contrast
28
28
  # @return [Contrast::Api::Dtm::Address] the address of this server
29
29
  def build_receiver
30
30
  @_build_receiver ||= begin
31
- address = new
32
- address.host = 'localhost'
33
- address.ip = '127.0.0.1'
34
- begin
35
- Timeout.timeout(1) do
36
- address.host = Contrast::Utils::StringUtils.force_utf8(Socket.gethostname)
37
- address.ip = Contrast::Utils::StringUtils.force_utf8(Resolv.getaddress(address.host))
38
- end
39
- rescue StandardError => e
40
- logger.warn('Unable to resolve host or ip', e, address: address)
41
- end
42
- address
43
- end
31
+ address = new
32
+ address.host = 'localhost'
33
+ address.ip = '127.0.0.1'
34
+ begin
35
+ Timeout.timeout(1) do
36
+ address.host = Contrast::Utils::StringUtils.force_utf8(Socket.gethostname)
37
+ address.ip = Contrast::Utils::StringUtils.force_utf8(Resolv.getaddress(address.host))
38
+ end
39
+ rescue StandardError => e
40
+ logger.warn('Unable to resolve host or ip', e, address: address)
41
+ end
42
+ address
43
+ end
44
44
  end
45
45
 
46
46
  # Parse out the sender of the Request and return it as an Address
@@ -1,6 +1,7 @@
1
1
  # Copyright (c) 2020 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/utils/object_share'
4
5
  require 'contrast/utils/string_utils'
5
6
 
6
7
  module Contrast
@@ -47,10 +47,25 @@ module Contrast
47
47
  Contrast::Agent::Assess::Tracker.properties(target)
48
48
  end
49
49
  end
50
+ return unless properties.tracked?
50
51
 
51
- return Contrast::Utils::ObjectShare::EMPTY_ARRAY unless properties.tracked?
52
+ self.taint_ranges += properties.tags_to_dtm
53
+ end
54
+
55
+ def build_parent_ids contrast_event
56
+ contrast_event&.parent_events&.each do |event|
57
+ next unless event
58
+
59
+ parent = Contrast::Api::Dtm::ParentObjectId.new
60
+ parent.id = event.event_id.to_i
61
+ parent_object_ids << parent
62
+ end
63
+ end
52
64
 
53
- properties.tags_to_dtm
65
+ def build_stack contrast_event
66
+ # We delayed doing this as long as possible b/c it's expensive
67
+ stack_dtms = Contrast::Utils::StackTraceUtils.build_assess_stack_array(contrast_event.stack_trace)
68
+ self.stack += stack_dtms
54
69
  end
55
70
 
56
71
  # Class methods for TraceEvent
@@ -71,23 +86,10 @@ module Contrast
71
86
  truncate_ret = Contrast::Utils::ObjectShare::RETURN_KEY != taint_target
72
87
  event_dtm.ret = Contrast::Api::Dtm::TraceEventObject.build(contrast_event.ret, truncate_ret)
73
88
  event_dtm.build_event_args(contrast_event, taint_target)
74
-
75
- taint_ranges = event_dtm.build_taint_ranges(contrast_event, taint_target)
76
- taint_ranges.each do |range|
77
- event_dtm.taint_ranges << range
78
- end
79
-
80
- # We delayed doing this as long as possible b/c it's expensive
81
- stack = Contrast::Utils::StackTraceUtils.build_assess_stack_array(contrast_event.stack_trace)
82
- event_dtm.stack += stack
83
-
89
+ event_dtm.build_parent_ids(contrast_event)
90
+ event_dtm.build_taint_ranges(contrast_event, taint_target)
91
+ event_dtm.build_stack(contrast_event)
84
92
  event_dtm.object_id = contrast_event.event_id.to_i
85
- contrast_event.parent_ids&.each do |id|
86
- parent = Contrast::Api::Dtm::ParentObjectId.new
87
- parent.id = id.to_i
88
- event_dtm.parent_object_ids << parent
89
- end
90
-
91
93
  event_dtm.signature = Contrast::Api::Dtm::TraceEventSignature.build(contrast_event.ret, contrast_event.policy_node, contrast_event.args)
92
94
  event_dtm
93
95
  end
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'rubygems/version'
5
+ require 'contrast/utils/object_share'
5
6
 
6
7
  module Contrast
7
8
  module Components
@@ -15,7 +16,7 @@ module Contrast
15
16
  include Contrast::Components::ComponentBase
16
17
  include Contrast::Components::Interface
17
18
 
18
- access_component :agent, :analysis, :config
19
+ access_component :agent, :analysis, :config, :logging
19
20
 
20
21
  DEFAULT_APP_NAME = 'rails'
21
22
  DEFAULT_APP_PATH = '/'
@@ -28,51 +29,51 @@ module Contrast
28
29
 
29
30
  def server_type
30
31
  @_server_type ||= begin
31
- tmp = CONFIG.root.server.type
32
- tmp = Contrast::Agent.framework_manager.server_type unless Contrast::Utils::StringUtils.present?(tmp)
33
- tmp
34
- end
32
+ tmp = CONFIG.root.server.type
33
+ tmp = Contrast::Agent.framework_manager.server_type unless Contrast::Utils::StringUtils.present?(tmp)
34
+ tmp
35
+ end
35
36
  end
36
37
 
37
38
  def name
38
39
  @_name ||= begin
39
- tmp = CONFIG.root.application.name
40
- tmp = Contrast::Agent.framework_manager.app_name unless Contrast::Utils::StringUtils.present?(tmp)
41
- tmp = File.basename(Dir.pwd) unless Contrast::Utils::StringUtils.present?(tmp)
42
- Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_APP_NAME)
43
- rescue StandardError
44
- DEFAULT_APP_NAME
45
- end
40
+ tmp = CONFIG.root.application.name
41
+ tmp = Contrast::Agent.framework_manager.app_name unless Contrast::Utils::StringUtils.present?(tmp)
42
+ tmp = File.basename(Dir.pwd) unless Contrast::Utils::StringUtils.present?(tmp)
43
+ Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_APP_NAME)
44
+ rescue StandardError
45
+ DEFAULT_APP_NAME
46
+ end
46
47
  end
47
48
 
48
49
  def path
49
50
  @_path ||= begin
50
- tmp = CONFIG.root.application.path
51
- Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_APP_PATH)
52
- rescue StandardError
53
- DEFAULT_APP_PATH
54
- end
51
+ tmp = CONFIG.root.application.path
52
+ Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_APP_PATH)
53
+ rescue StandardError
54
+ DEFAULT_APP_PATH
55
+ end
55
56
  end
56
57
 
57
58
  def server_name
58
59
  @_server_name ||= begin
59
- tmp = CONFIG.root.server.name
60
- tmp = Socket.gethostname unless Contrast::Utils::StringUtils.present?(tmp)
61
- tmp = Contrast::Utils::StringUtils.force_utf8(tmp)
62
- Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_SERVER_NAME)
63
- rescue StandardError
64
- DEFAULT_SERVER_NAME
65
- end
60
+ tmp = CONFIG.root.server.name
61
+ tmp = Socket.gethostname unless Contrast::Utils::StringUtils.present?(tmp)
62
+ tmp = Contrast::Utils::StringUtils.force_utf8(tmp)
63
+ Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_SERVER_NAME)
64
+ rescue StandardError
65
+ DEFAULT_SERVER_NAME
66
+ end
66
67
  end
67
68
 
68
69
  def server_path
69
70
  @_server_path ||= begin
70
- tmp = CONFIG.root.server.path
71
- tmp = Dir.pwd unless Contrast::Utils::StringUtils.present?(tmp)
72
- Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_SERVER_PATH)
73
- rescue StandardError
74
- DEFAULT_SERVER_PATH
75
- end
71
+ tmp = CONFIG.root.server.path
72
+ tmp = Dir.pwd unless Contrast::Utils::StringUtils.present?(tmp)
73
+ Contrast::Utils::StringUtils.truncate(tmp, DEFAULT_SERVER_PATH)
74
+ rescue StandardError
75
+ DEFAULT_SERVER_PATH
76
+ end
76
77
  end
77
78
 
78
79
  def build_app_startup_message
@@ -101,6 +102,14 @@ module Contrast
101
102
  msg.application_tags = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.application.tags
102
103
  msg.library_tags = Contrast::Utils::StringUtils.protobuf_format CONFIG.root.inventory.tags
103
104
  msg.finding_tags = Contrast::Utils::StringUtils.protobuf_format ASSESS.tags
105
+ logger.info('Application context',
106
+ server_name: msg.server_name,
107
+ server_path: msg.server_path,
108
+ server_type: msg.server_type,
109
+ application_name: name,
110
+ application_path: path,
111
+ application_language: Contrast::Utils::ObjectShare::RUBY)
112
+
104
113
  msg
105
114
  end
106
115
 
@@ -26,21 +26,21 @@ module Contrast
26
26
  # Validates the config to decide if it's suitable for starting
27
27
  # the bundled service
28
28
  @_use_bundled_service ||= begin
29
- # Requirement says "must be true" but that
30
- # should be "must not be false" -- oops.
31
- !false?(CONFIG.root.agent.start_bundled_service) &&
32
- # Either a valid host or a valid socket
33
- # Path validity is the service's problem
34
- (LOCALHOST.match?(host) || !!socket_path)
35
- end
29
+ # Requirement says "must be true" but that
30
+ # should be "must not be false" -- oops.
31
+ !false?(CONFIG.root.agent.start_bundled_service) &&
32
+ # Either a valid host or a valid socket
33
+ # Path validity is the service's problem
34
+ (LOCALHOST.match?(host) || !!socket_path)
35
+ end
36
36
  end
37
37
 
38
38
  def host
39
- @_host ||= CONFIG.root.agent.service.host.to_s
39
+ @_host ||= (CONFIG.root.agent.service.host || Contrast::Config::ServiceConfiguration::DEFAULT_HOST).to_s
40
40
  end
41
41
 
42
42
  def port
43
- @_port ||= CONFIG.root.agent.service.port.to_i
43
+ @_port ||= (CONFIG.root.agent.service.port || Contrast::Config::ServiceConfiguration::DEFAULT_PORT).to_i
44
44
  end
45
45
 
46
46
  def socket_path
@@ -55,23 +55,22 @@ module Contrast
55
55
  APPLICATION_STATE_ATTRS = %i[modes_by_id exclusion_matchers disabled_assess_rules].cs__freeze
56
56
 
57
57
  # Meta-define an accessor for each state attribute.
58
- begin
59
- PROTECT_STATE_ATTRS.each do |attr|
60
- define_method(attr) do
61
- protect_state[attr]
62
- end
58
+
59
+ PROTECT_STATE_ATTRS.each do |attr|
60
+ define_method(attr) do
61
+ protect_state[attr]
63
62
  end
63
+ end
64
64
 
65
- ASSESS_STATE_ATTRS.each do |attr|
66
- define_method(attr) do
67
- assess_state[attr]
68
- end
65
+ ASSESS_STATE_ATTRS.each do |attr|
66
+ define_method(attr) do
67
+ assess_state[attr]
69
68
  end
69
+ end
70
70
 
71
- APPLICATION_STATE_ATTRS.each do |attr|
72
- define_method(attr) do
73
- application_state[attr]
74
- end
71
+ APPLICATION_STATE_ATTRS.each do |attr|
72
+ define_method(attr) do
73
+ application_state[attr]
75
74
  end
76
75
  end
77
76
 
@@ -95,18 +94,16 @@ module Contrast
95
94
 
96
95
  def update_from_server_features server_features
97
96
  # protect
98
- begin
99
- @_protect_enabled = nil
100
- protect_state[:enabled] = server_features.protect_enabled?
101
- end
97
+
98
+ @_protect_enabled = nil
99
+ protect_state[:enabled] = server_features.protect_enabled?
102
100
 
103
101
  # assess
104
- begin
105
- @_assess_enabled = nil
106
- assess_state[:enabled] = server_features.assess_enabled?
107
- assess_state[:sampling_settings] = server_features.assess.sampling
108
- Contrast::Utils::Assess::SamplingUtil.instance.update
109
- end
102
+
103
+ @_assess_enabled = nil
104
+ assess_state[:enabled] = server_features.assess_enabled?
105
+ assess_state[:sampling_settings] = server_features.assess.sampling
106
+ Contrast::Utils::Assess::SamplingUtil.instance.update
110
107
  end
111
108
 
112
109
  def update_from_application_settings application_settings
@@ -9,13 +9,15 @@ module Contrast
9
9
  # Common Configuration settings. Those in this section pertain to the
10
10
  # communication between the Agent & the Service.
11
11
  class ServiceConfiguration < BaseConfiguration
12
+ # We don't set these b/c we've been asked to handle the default values of
13
+ # these settings differently, logging when we have to use them.
12
14
  DEFAULT_HOST = '127.0.0.1'
13
15
  DEFAULT_PORT = '30555'
14
16
 
15
17
  KEYS = {
16
18
  enable: EMPTY_VALUE,
17
- host: Contrast::Config::DefaultValue.new(DEFAULT_HOST),
18
- port: Contrast::Config::DefaultValue.new(DEFAULT_PORT),
19
+ host: EMPTY_VALUE,
20
+ port: EMPTY_VALUE,
19
21
  socket: EMPTY_VALUE,
20
22
  logger: Contrast::Config::LoggerConfiguration
21
23
  }.cs__freeze
@@ -238,7 +238,7 @@ module Contrast
238
238
  # @return [Hash, Object] the leaf of each
239
239
  # Contrast::Config::BaseConfiguration will be returned in the N > 0 steps
240
240
  # the Hash will be returned at the end of the 0 level
241
- def convert_to_hash convert=root, hash={}
241
+ def convert_to_hash convert = root, hash = {}
242
242
  case convert
243
243
  when Contrast::Config::BaseConfiguration
244
244
  convert.cs__class::KEYS.each_key do |key|
@@ -44,10 +44,13 @@ module Contrast
44
44
 
45
45
  shift = 0
46
46
  separator_length = separator.nil? ? 0 : separator.to_s.length
47
+ parent_events = []
47
48
  ary.each do |obj|
48
49
  if obj # skip nil here
49
50
  properties.copy_from(obj, ret, shift)
50
51
  shift += obj.to_s.length
52
+ parent_event = Contrast::Agent::Assess::Tracker.properties(obj)&.event
53
+ parent_events << parent_event if parent_event
51
54
  end
52
55
  shift += separator_length
53
56
  end
@@ -60,15 +63,16 @@ module Contrast
60
63
  ary,
61
64
  ret,
62
65
  [separator])
66
+ properties.event.instance_variable_set(:@_parent_events, parent_events)
63
67
  ret
64
68
  end
65
69
  end
66
70
 
67
71
  def instrument_array_track
68
72
  @_instrument_array_track ||= begin
69
- require 'cs__assess_array/cs__assess_array'
70
- true
71
- end
73
+ require 'cs__assess_array/cs__assess_array'
74
+ true
75
+ end
72
76
  rescue StandardError, LoadError => e
73
77
  logger.error('Error loading assess track patch', e)
74
78
  false