contrast-agent 6.15.1 → 6.15.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da127ad5e9e6a680c97012fff322627848cafb812164a7c9eb2b4200790342a8
4
- data.tar.gz: 222870b966c8ebc16dc5cc0fc4452e3a50326b9669c06606dd2e3c798fec1bf9
3
+ metadata.gz: bb55239bd37c7b0d2c3adc47d2e47ff25e331694b9be68522a29f8e4ce8c1220
4
+ data.tar.gz: 41a5a677403dd10c0dcd67673b32e32aa8f8ad04a82d963891f95ceaa25b46a1
5
5
  SHA512:
6
- metadata.gz: 46014984fee94a0a0244ab9374e4699b7e09737720384edd3e584d0c6c19fa3741f2ae34c961b8aea9ba7d4bea9cabab7ed406979ff878b61ffd9db9b4993556
7
- data.tar.gz: d164ae2ab0cad83e3369f8e330a0a8b98d6ca9c36dca768134202e5c9bf1f5aa3130a7e45488f9994ce3044e33805f49c72a79eae4dded097d4f3fd13986ae5a
6
+ metadata.gz: 94aaa0a8ed0b9fb08fb5c206bee3695cd1cc1f3a8b7752fa8c52abfb563a4e58fac60162ab13c4f06ceb785532ae1a76b93dcc8f348e99d29b112b265a88051b
7
+ data.tar.gz: 658421a2a8558bac001eb707340f0bc763012d06b9fdcfe1137fb3ceff6f53966d7cc9af9bced07f08411b5e44af1ad5c4f5805b54abaae0ad71dcc81011fbb9
@@ -33,8 +33,8 @@ module Contrast
33
33
  # Parse the given controller and route from a Rack based application framework in order to create an instance
34
34
  # of this class
35
35
  #
36
- # @param final_controller [Grape::API, Sinatra::Base] the controller responsible for the definition of the
37
- # entrypoint of the route actively being executed
36
+ # @param final_controller [Class<Grape::API>, Class<Sinatra::Base>] the controller responsible for the
37
+ # definition of the entrypoint of the route actively being executed
38
38
  # @param method [String] the HTTP request method of the route actively being executed
39
39
  # @param route_pattern [Grape::Router::Route, Mustermann::Sinatra] the pattern to which the url maps
40
40
  # @param url [String] the literal url of the route actively being executed
@@ -135,6 +135,8 @@ module Contrast
135
135
  @observed_route = Contrast::Agent::Reporting::ObservedRoute.new
136
136
  reporting_route = Contrast::Agent.framework_manager.get_route_information(@request)
137
137
  append_to_observed_route(reporting_route)
138
+ rescue StandardError => e
139
+ logger.error('Unable to determine current route', e)
138
140
  end
139
141
  end
140
142
  end
@@ -7,12 +7,13 @@ require 'contrast/agent/telemetry/client'
7
7
  require 'contrast/agent/thread/worker_thread'
8
8
  require 'contrast/agent/telemetry/telemetry'
9
9
  require 'contrast/agent/telemetry/exception'
10
+ require 'contrast/utils/job_servers_running'
10
11
 
11
12
  module Contrast
12
13
  module Agent
13
14
  module Telemetry
14
15
  # This class will initialize and hold everything needed for the telemetry
15
- class Base < WorkerThread
16
+ class Base < WorkerThread # rubocop:disable Metrics/ClassLength
16
17
  include Contrast::Components::Logger::InstanceMethods
17
18
  # this is where we will send the data from the agents
18
19
  URL = 'https://telemetry.ruby.contrastsecurity.com/'
@@ -48,6 +49,8 @@ module Contrast
48
49
  private
49
50
 
50
51
  def telemetry_enabled?
52
+ return false if Contrast::AGENT.disabled? || Contrast::Utils::JobServersRunning.job_servers_running?
53
+
51
54
  opt_out_telemetry = return_value(:telemetry_opt_outs).to_s
52
55
  return false if opt_out_telemetry.casecmp?('true') || opt_out_telemetry == '1'
53
56
 
@@ -56,7 +59,9 @@ module Contrast
56
59
  @_client = Contrast::Agent::Telemetry::Client.new
57
60
  ip_opt_out_telemetry = @_client.initialize_connection(URL)
58
61
  if ip_opt_out_telemetry.nil?
59
- logger.warn("[Telemetry] Connection was not established properly!!! \n
62
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
63
+ # an infinite loop w/ telemetry
64
+ logger.debug("[Telemetry] Connection was not established properly!!! \n
60
65
  Telemetry reporting will be disabled!")
61
66
  return false
62
67
  end
@@ -74,6 +79,8 @@ module Contrast
74
79
  end
75
80
 
76
81
  def attempt_to_start?
82
+ return unless super
83
+
77
84
  unless cs__class.enabled?
78
85
  logger.info('[Telemetry] Telemetry service is disabled!')
79
86
  return false
@@ -92,7 +99,7 @@ module Contrast
92
99
 
93
100
  def send_event event
94
101
  if ::Contrast::AGENT.disabled?
95
- logger.warn('[Telemetry] Attempted to queue event with Agent disabled', caller: caller, event: event)
102
+ logger.debug('[Telemetry] Attempted to queue event with Agent disabled', caller: caller, event: event)
96
103
  return
97
104
  end
98
105
 
@@ -148,7 +155,9 @@ module Contrast
148
155
  sleep(retry_sleep_time) unless retry_sleep_time.nil?
149
156
  end
150
157
  rescue StandardError => e
151
- logger.error('[Telemetry] Could not send message to service from telemetry queue.', e)
158
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
159
+ # an infinite loop w/ telemetry
160
+ logger.debug('[Telemetry] Could not send message to service from telemetry queue.', e)
152
161
  stop!
153
162
  end
154
163
  end
@@ -100,7 +100,9 @@ module Contrast
100
100
  def get_event_json event
101
101
  Array(event.to_controlled_hash).to_json
102
102
  rescue Exception => e # rubocop:disable Lint/RescueException
103
- logger.error('[Telemetry] Unable to convert TelemetryEvent to JSON string', e, hsh)
103
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
104
+ # an infinite loop w/ telemetry
105
+ logger.debug('[Telemetry] Unable to convert TelemetryEvent to JSON string', e, hsh)
104
106
  raise(e)
105
107
  end
106
108
  end
@@ -94,6 +94,12 @@ module Contrast
94
94
  def telemetry_exceptions_enabled?
95
95
  opts_out_telemetry = return_value(:telemetry_opt_outs).to_s
96
96
  return false if opts_out_telemetry.casecmp?('true') || opts_out_telemetry == '1'
97
+ # Double check if telemetry is enabled, this includes a check for Agent enable setting in the
98
+ # config. In case of disabled Agent the queue won't be created and the Telemetry would not
99
+ # be enabled. This check is here to prevent a loop of error creations that could no be added
100
+ # to a queue ( because the client cannot be initialized if the Agent is disabled or settings are
101
+ # missing).
102
+ return false unless Contrast::Agent::Telemetry::Base.enabled?
97
103
 
98
104
  true
99
105
  end
@@ -7,6 +7,7 @@ require 'contrast/agent/reporting/reporting_workers/reporting_workers'
7
7
  require 'contrast/agent/telemetry/base'
8
8
  require 'contrast/agent/protect/input_analyzer/worth_watching_analyzer'
9
9
  require 'contrast/config/diagnostics'
10
+ require 'contrast/utils/job_servers_running'
10
11
 
11
12
  module Contrast
12
13
  module Agent
@@ -28,6 +29,11 @@ module Contrast
28
29
  attr_reader :reporter_app_settings_worker
29
30
 
30
31
  def initialize
32
+ if Contrast::AGENT.disabled? || Contrast::Utils::JobServersRunning.job_servers_running?
33
+ logger.info('Agent Disabled, shutting down all threads...')
34
+ return
35
+ end
36
+
31
37
  @heapdump_util = Contrast::Utils::HeapDumpUtil.new
32
38
  @reporter = Contrast::Agent::Reporter.new
33
39
  @reporter_heartbeat = Contrast::Agent::ReportingWorkers::ReporterHeartbeat.new
@@ -37,7 +43,7 @@ module Contrast
37
43
  @worth_watching_analyzer = Contrast::Agent::Protect::WorthWatchingInputAnalyzer.new unless protect_disabled?
38
44
  end
39
45
 
40
- # @return [Hash, nil] map of process to thread startup status
46
+ # @return [Array<Contrast::Agent::WorkerThread>] map of process to thread startup status
41
47
  def startup!
42
48
  check_before_start
43
49
 
@@ -29,7 +29,7 @@ module Contrast
29
29
  #
30
30
  # @return [Boolean]
31
31
  def attempt_to_start?
32
- true
32
+ Contrast::AGENT.enabled?
33
33
  end
34
34
 
35
35
  def clean_properties
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '6.15.1'
6
+ VERSION = '6.15.3'
7
7
  end
8
8
  end
@@ -9,6 +9,7 @@ require 'contrast/components/security_logger'
9
9
  require 'contrast/components/heap_dump'
10
10
  require 'contrast/components/ruby_component'
11
11
  require 'contrast/components/polling'
12
+ require 'contrast/agent/hooks/tracepoint_hook'
12
13
 
13
14
  module Contrast
14
15
  module Components
@@ -23,6 +24,8 @@ module Contrast
23
24
  attr_reader :canon_name
24
25
  # @return [Array]
25
26
  attr_reader :config_values
27
+ # @return [Boolean]
28
+ attr_accessor :enable
26
29
 
27
30
  CANON_NAME = 'agent'
28
31
  CONFIG_VALUES = %w[enabled? omit_body?].cs__freeze
@@ -53,6 +53,7 @@ module Contrast
53
53
  # @return [String,nil]
54
54
  attr_reader :config_file
55
55
 
56
+ CONTRAST_ENV_MARKER = 'CONTRAST__'
56
57
  DEFAULT_YAML_PATH = 'contrast_security.yaml'
57
58
  MILLISECOND_MARKER = '_ms'
58
59
  CONVERSION = {}.cs__freeze
@@ -67,6 +68,14 @@ module Contrast
67
68
  config_kv = deep_symbolize_all_keys(load_config)
68
69
  config_sources = assign_source_to(config_kv, Contrast::Components::Config::Sources::YAML)
69
70
 
71
+ unless cli_options
72
+ cli_options = {}
73
+ ENV.each do |key, value|
74
+ next unless key.to_s.start_with?(CONTRAST_ENV_MARKER)
75
+
76
+ cli_options[key] = value
77
+ end
78
+ end
70
79
  # Overlay CLI options - they take precedence over config file
71
80
  cli_options = deep_symbolize_all_keys(cli_options)
72
81
  if cli_options
@@ -11,6 +11,7 @@ require 'contrast/framework/rack/support'
11
11
  require 'contrast/framework/rails/support'
12
12
  require 'contrast/framework/sinatra/support'
13
13
  require 'contrast/utils/class_util'
14
+ require 'contrast/utils/job_servers_running'
14
15
 
15
16
  module Contrast
16
17
  module Framework
@@ -28,6 +29,8 @@ module Contrast
28
29
  ].cs__freeze
29
30
 
30
31
  def initialize
32
+ return if Contrast::AGENT.disabled? || Contrast::Utils::JobServersRunning.job_servers_running?
33
+
31
34
  @_frameworks = SUPPORTED_FRAMEWORKS.map do |framework_klass|
32
35
  next unless enable_framework_support?(framework_klass.detection_class)
33
36
 
@@ -116,8 +119,8 @@ module Contrast
116
119
  # @param request [Contrast::Agent::Request] the current request.
117
120
  # @return [Contrast::Agent::Reporting::RouteCoverage] the current route as a Dtm.
118
121
  def get_route_information request
119
- @_frameworks.lazy.map { |framework_support| framework_support.current_route_coverage(request) }.
120
- reject(&:nil?).first # rubocop:disable Style/CollectionCompact
122
+ @_frameworks&.lazy&.map { |framework_support| framework_support.current_route_coverage(request) }&.
123
+ reject(&:nil?)&.first
121
124
  end
122
125
 
123
126
  # Sometimes the framework we want to instrument is loaded after our agent code. To catch that case, we'll detect
@@ -67,30 +67,39 @@ module Contrast
67
67
  # Given the current request - return a RouteCoverage object
68
68
 
69
69
  # @param request [Contrast::Agent::Request] a contrast tracked request.
70
- # @param controller [::Sinatra::Base] optionally use this controller instead of global ::Sinatra::Base.
70
+ # @param _controller [::Sinatra::Base] optionally use this controller instead of global ::Sinatra::Base.
71
71
  # @return [Contrast::Agent::Reporting::RouteCoverage, nil] a Dtm describing the route
72
72
  # matched to the request if a match was found.
73
- def current_route_coverage request, controller = ::Sinatra::Base, full_route = nil
74
- return unless sinatra_controller?(controller)
75
-
73
+ def current_route_coverage request, _controller = ::Sinatra::Base, full_route = nil
76
74
  method = request.env[::Rack::REQUEST_METHOD] # GET, PUT, POST, etc...
77
-
75
+ route = _cleaned_route(request)
78
76
  # Find route match--checking superclasses if necessary.
79
- final_controller, route_pattern = _route_recurse(controller, method, _cleaned_route(request))
80
- return unless final_controller && route_pattern
77
+ sinatra_controllers.each do |potential_controller|
78
+ next unless sinatra_controller?(potential_controller)
79
+
80
+ next if potential_controller.nil? || potential_controller.cs__class == NilClass
81
81
 
82
- full_route ||= request.env[::Rack::PATH_INFO]
82
+ route_patterns = potential_controller.routes.fetch(method) { [] }.
83
+ map(&:first)
84
+ route_pattern = route_patterns.find do |matcher|
85
+ matcher.params(route) # ::Mustermann::Sinatra match.
86
+ end
87
+ next unless route_pattern
83
88
 
84
- new_route_coverage = Contrast::Agent::Reporting::RouteCoverage.new
85
- new_route_coverage.attach_rack_based_data(final_controller, method, route_pattern, full_route)
86
- new_route_coverage
89
+ full_route ||= request.env[::Rack::PATH_INFO]
90
+ new_route_coverage = Contrast::Agent::Reporting::RouteCoverage.new
91
+ new_route_coverage.attach_rack_based_data(potential_controller, method, route_pattern, full_route)
92
+ return new_route_coverage
93
+ end
94
+ nil
87
95
  end
88
96
 
89
97
  # Search object space for sinatra controllers--any class that subclasses ::Sinatra::Base.
90
98
  #
91
- # @return [Array<::Sinatra::Base>] sinatra controlelrs
99
+ # @return [Array<Class<::Sinatra::Base>>] sinatra controlelrs
92
100
  def sinatra_controllers
93
- [::Sinatra::Base] + ObjectSpace.each_object(Class).select { |clazz| sinatra_controller?(clazz) }
101
+ @_sinatra_controllers ||=
102
+ [::Sinatra::Base] + ObjectSpace.each_object(Class).select { |clazz| sinatra_controller?(clazz) }
94
103
  end
95
104
 
96
105
  def retrieve_request env
@@ -112,31 +121,6 @@ module Contrast
112
121
 
113
122
  private
114
123
 
115
- # Given a controller and a route to match against, find the route_pattern and class that will serve the
116
- # route. This is recursive as Sinatra's routing is recursive from subclass to super.
117
- #
118
- # @param controller [Sinatra::Base, #routes] a Sinatra application.
119
- # @param method [::Rack::REQUEST_METHOD] GET, POST, PUT, etc...
120
- # @param route [String] the relative route passed from Rack.
121
- # @return [Array[Sinatra::Base, Mustermann::Sinatra], nil] Either the controller that
122
- # will handle the route along with the route pattern or nil if no match.
123
- def _route_recurse controller, method, route
124
- return if controller.nil? || controller.cs__class == NilClass
125
-
126
- route_patterns = controller.routes.fetch(method) { [] }.
127
- map(&:first)
128
- route_pattern = route_patterns&.find do |matcher|
129
- matcher.params(route) # ::Mustermann::Sinatra match.
130
- end
131
-
132
- return controller, route_pattern if route_pattern
133
-
134
- # Check routes defined in superclass if present.
135
- return unless controller.superclass&.instance_variable_get(:@routes)
136
-
137
- _route_recurse(controller.superclass, method, route)
138
- end
139
-
140
124
  # Get route and do some cleanup matching that of Sinatra::Base#process_route.
141
125
  #
142
126
  # @param request [Contrast::Agent::Request] a contrast tracked request.
@@ -28,7 +28,6 @@ module Funchook
28
28
  logger.warn('Unable to find funchook')
29
29
  else
30
30
  @path = absolute_path(actual_path_segments)
31
- logger.info('Funchook found', path: @path)
32
31
  end
33
32
  @path
34
33
  end
@@ -40,12 +40,38 @@ module Contrast
40
40
 
41
41
  private
42
42
 
43
+ # There's a weird circular import in our code that we don't have time to untangle. This is 100% bad code and
44
+ # I'm sorry to the future team, but this is all we got.
45
+ #
46
+ # If the configuration we need is not available, we'll skip out for now - it means the agent isn't ready. We
47
+ # won't get telemetry exceptions at this point, but that means the agent hasn't initialized and there's a
48
+ # chance we're not supposed to. Essentially, we fail closed.
49
+ #
50
+ # Once we have the agent initialized, we'll use the value to check. Since it cannot change once set, we'll use
51
+ # the saved value.
52
+ #
53
+ # - HM
54
+ #
55
+ # @return [Boolean]
56
+ def buildable?
57
+ if @_buildable.nil?
58
+ return false unless defined?(Contrast) &&
59
+ defined?(Contrast::Agent) &&
60
+ defined?(Contrast::Agent::Telemetry)
61
+
62
+ @_buildable = Contrast::Agent::Telemetry.exceptions_enabled?
63
+ end
64
+ @_buildable
65
+ end
66
+
43
67
  # @param type [ALIASED_FATAL, ALIASED_ERROR, ALIASED_WARN] the type of error, used to indicate the function used
44
68
  # for logging
45
69
  # @param message [String] the exception message
46
70
  # @param exception [Exception] The exception or error
47
71
  # @param data [Object] Any structured data
48
72
  def build_exception type, message = nil, exception = nil, data = nil
73
+ return unless buildable?
74
+
49
75
  stack_trace = wrapped_caller_locations
50
76
  caller_idx = stack_trace&.find_index { |stack| stack.to_s.include?(type) } || 0
51
77
  # The caller_stack is the method in which the error occurred, so has to be above this method
@@ -44,14 +44,15 @@ module Contrast
44
44
  update(route.signature)
45
45
  if (observation = route.observations[0])
46
46
  update(observation.verb)
47
+ else
48
+ update(request.request_method)
47
49
  end
48
- return
49
- end
50
-
51
- return unless request ||= context&.request
50
+ else
51
+ return unless request ||= context&.request
52
52
 
53
- update(request.normalized_uri) # the normalized URL used to access the method in the route.
54
- update(request.request_method) # The HTTP method used in the request
53
+ update(request.normalized_uri) # the normalized URL used to access the method in the route.
54
+ update(request.request_method)
55
+ end
55
56
  end
56
57
 
57
58
  # Update to CRC checksum the event source name and source type.
@@ -1,15 +1,13 @@
1
1
  # Copyright (c) 2023 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/components/logger'
4
+ require 'contrast/components/ruby_component'
5
5
 
6
6
  module Contrast
7
7
  module Utils
8
8
  # A module that detects whether any job servers attached to
9
9
  # the application are running
10
10
  module JobServersRunning
11
- extend Contrast::Components::Logger::InstanceMethods
12
-
13
11
  class << self
14
12
  def job_servers_running?
15
13
  sidekiq_running? || rake_running?
@@ -20,7 +18,6 @@ module Contrast
20
18
  def sidekiq_running?
21
19
  return unless defined?(Sidekiq) && Sidekiq.cs__respond_to?(:server?) && Sidekiq.server?
22
20
 
23
- logger.trace('Detected the spawn of a Sidekiq process')
24
21
  true
25
22
  end
26
23
 
@@ -32,13 +29,18 @@ module Contrast
32
29
  return
33
30
  end
34
31
 
35
- disabled_rake_tasks = ::Contrast::APP_CONTEXT.disabled_agent_rake_tasks
32
+ # This might be called before component even exist, so we backup to
33
+ # default disabled rake tasks.
34
+ disabled_rake_tasks = if Contrast.const_defined?(:APP_CONTEXT) # rubocop:disable Security/Module/ConstDefined
35
+ Contrast::APP_CONTEXT.disabled_agent_rake_tasks
36
+ else
37
+ Contrast::Components::Ruby::Interface::DISABLED_RAKE_TASK_LIST
38
+ end
36
39
  has_disabled_task = Rake.application.top_level_tasks.any? do |top_level_task|
37
40
  disabled_rake_tasks.include?(top_level_task)
38
41
  end
39
42
  return false unless has_disabled_task
40
43
 
41
- logger.trace('Detected startup within Rake task')
42
44
  true
43
45
  end
44
46
  end
@@ -51,7 +51,7 @@ module Contrast
51
51
  logger.extend(Contrast::Logger::Application)
52
52
  logger.extend(Contrast::Logger::Request)
53
53
  logger.extend(Contrast::Logger::Time)
54
- logger.extend(Contrast::Logger::AliasedLogging) if Contrast::Agent::Telemetry.exceptions_enabled?
54
+ logger.extend(Contrast::Logger::AliasedLogging)
55
55
  end
56
56
 
57
57
  # Determine the valid path to which to log, given the precedence of config > settings > default.
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/config/yaml_file'
5
+ require 'contrast/utils/job_servers_running'
5
6
 
6
7
  module Contrast
7
8
  module Utils
@@ -30,6 +31,9 @@ module Contrast
30
31
  logger.error('!!! CONFIG FILE IS INVALID - DISABLING CONTRAST AGENT !!!')
31
32
  elsif ::Contrast::AGENT.disabled?
32
33
  logger.warn('Contrast disabled by configuration. Continuing without instrumentation.')
34
+ elsif Contrast::Utils::JobServersRunning.job_servers_running?
35
+ logger.info('Server job detected disabling Agent...')
36
+ ::Contrast::AGENT.disable!
33
37
  else
34
38
  ::Contrast::AGENT.enable!
35
39
  end
@@ -43,7 +43,9 @@ module Contrast
43
43
  logger.debug('Client verified', service: service_name, url: url)
44
44
  net_http_client
45
45
  rescue StandardError => e
46
- logger.error('Connection failed', e, service: service_name, url: url)
46
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
47
+ # an infinite loop w/ telemetry
48
+ logger.debug('Connection failed', e, service: service_name, url: url)
47
49
  nil
48
50
  end
49
51
 
@@ -72,7 +74,9 @@ module Contrast
72
74
  Errno::ETIMEDOUT, Errno::ESHUTDOWN, Errno::EHOSTDOWN, Errno::EHOSTUNREACH, Errno::EISCONN,
73
75
  Errno::ECONNABORTED, Errno::ENETRESET, Errno::ENETUNREACH => e
74
76
 
75
- logger.warn("#{ service_name } connection failed", e.message)
77
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
78
+ # an infinite loop w/ telemetry
79
+ logger.debug("#{ service_name } connection failed", e.message)
76
80
  false
77
81
  end
78
82
 
@@ -110,7 +114,9 @@ module Contrast
110
114
  client.key = OpenSSL::PKey::RSA.new(File.read(Contrast::API.certification_key_file)).to_s
111
115
  end
112
116
  rescue Errno::ENOENT => e
113
- logger.error('Custom certificates failed', e.message)
117
+ # TODO: RUBY-2033 we cannot log the error above debug level here b/c it results in
118
+ # an infinite loop w/ telemetry
119
+ logger.debug('Custom certificates failed', e.message)
114
120
  end
115
121
 
116
122
  # sets default setting for client validation of certificates and
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: contrast-agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.15.1
4
+ version: 6.15.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - galen.palmer@contrastsecurity.com
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: exe
15
15
  cert_chain: []
16
- date: 2023-02-16 00:00:00.000000000 Z
16
+ date: 2023-02-23 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: bundler
@@ -678,22 +678,22 @@ email:
678
678
  executables: []
679
679
  extensions:
680
680
  - ext/cs__common/extconf.rb
681
- - ext/cs__assess_basic_object/extconf.rb
682
- - ext/cs__assess_hash/extconf.rb
681
+ - ext/cs__assess_marshal_module/extconf.rb
682
+ - ext/cs__assess_yield_track/extconf.rb
683
+ - ext/cs__scope/extconf.rb
683
684
  - ext/cs__assess_kernel/extconf.rb
684
- - ext/cs__assess_string_interpolation/extconf.rb
685
- - ext/cs__contrast_patch/extconf.rb
685
+ - ext/cs__assess_array/extconf.rb
686
+ - ext/cs__os_information/extconf.rb
686
687
  - ext/cs__assess_string/extconf.rb
688
+ - ext/cs__assess_hash/extconf.rb
687
689
  - ext/cs__assess_regexp/extconf.rb
688
- - ext/cs__tests/extconf.rb
689
690
  - ext/cs__assess_module/extconf.rb
690
- - ext/cs__assess_yield_track/extconf.rb
691
- - ext/cs__assess_fiber_track/extconf.rb
692
- - ext/cs__scope/extconf.rb
691
+ - ext/cs__assess_string_interpolation/extconf.rb
692
+ - ext/cs__tests/extconf.rb
693
693
  - ext/cs__assess_test/extconf.rb
694
- - ext/cs__os_information/extconf.rb
695
- - ext/cs__assess_marshal_module/extconf.rb
696
- - ext/cs__assess_array/extconf.rb
694
+ - ext/cs__assess_fiber_track/extconf.rb
695
+ - ext/cs__assess_basic_object/extconf.rb
696
+ - ext/cs__contrast_patch/extconf.rb
697
697
  extra_rdoc_files: []
698
698
  files:
699
699
  - ".clang-format"