contrast-agent 7.2.0 → 7.3.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +62 -23
  3. data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +37 -4
  4. data/lib/contrast/agent/protect/rule/base.rb +5 -1
  5. data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +27 -11
  6. data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +0 -1
  7. data/lib/contrast/agent/protect/rule/cmdi/cmdi_input_classification.rb +2 -2
  8. data/lib/contrast/agent/protect/rule/input_classification/base.rb +191 -0
  9. data/lib/contrast/agent/protect/rule/input_classification/base64_statistic.rb +71 -0
  10. data/lib/contrast/agent/protect/rule/input_classification/cached_result.rb +37 -0
  11. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +109 -0
  12. data/lib/contrast/agent/protect/rule/input_classification/encoding_rates.rb +47 -0
  13. data/lib/contrast/agent/protect/rule/input_classification/extendable.rb +80 -0
  14. data/lib/contrast/agent/protect/rule/input_classification/lru_cache.rb +198 -0
  15. data/lib/contrast/agent/protect/rule/input_classification/match_rates.rb +66 -0
  16. data/lib/contrast/agent/protect/rule/input_classification/rates.rb +53 -0
  17. data/lib/contrast/agent/protect/rule/input_classification/statistics.rb +115 -0
  18. data/lib/contrast/agent/protect/rule/input_classification/utils.rb +23 -0
  19. data/lib/contrast/agent/protect/rule/no_sqli/no_sqli_input_classification.rb +17 -7
  20. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +18 -15
  21. data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +2 -2
  22. data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +18 -15
  23. data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +19 -17
  24. data/lib/contrast/agent/reporting/attack_result/attack_result.rb +6 -0
  25. data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +2 -7
  26. data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +11 -0
  27. data/lib/contrast/agent/reporting/input_analysis/input_type.rb +33 -1
  28. data/lib/contrast/agent/reporting/masker/masker_utils.rb +1 -1
  29. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +1 -0
  30. data/lib/contrast/agent/reporting/reporting_events/application_defend_attacker_activity.rb +1 -0
  31. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +1 -1
  32. data/lib/contrast/agent/telemetry/base.rb +28 -2
  33. data/lib/contrast/agent/telemetry/base64_hash.rb +55 -0
  34. data/lib/contrast/agent/telemetry/cache_hash.rb +55 -0
  35. data/lib/contrast/agent/telemetry/client.rb +10 -2
  36. data/lib/contrast/agent/telemetry/{hash.rb → exception_hash.rb} +1 -1
  37. data/lib/contrast/agent/telemetry/input_analysis_cache_event.rb +27 -0
  38. data/lib/contrast/agent/telemetry/input_analysis_encoding_event.rb +26 -0
  39. data/lib/contrast/agent/telemetry/input_analysis_event.rb +91 -0
  40. data/lib/contrast/agent/telemetry/metric_event.rb +12 -0
  41. data/lib/contrast/agent/telemetry/startup_metrics_event.rb +0 -8
  42. data/lib/contrast/agent/version.rb +1 -1
  43. data/lib/contrast/components/config.rb +4 -4
  44. data/lib/contrast/components/protect.rb +11 -1
  45. data/lib/contrast/components/sampling.rb +15 -10
  46. data/lib/contrast/config/diagnostics/environment_variables.rb +3 -1
  47. data/lib/contrast/config/yaml_file.rb +8 -0
  48. data/lib/contrast/framework/rails/support.rb +3 -0
  49. data/lib/contrast/utils/assess/event_limit_utils.rb +13 -13
  50. data/lib/contrast/utils/metrics_hash.rb +1 -1
  51. data/lib/contrast/utils/object_share.rb +2 -1
  52. data/lib/contrast/utils/response_utils.rb +12 -0
  53. data/lib/contrast/utils/timer.rb +2 -0
  54. data/lib/contrast.rb +9 -2
  55. data/ruby-agent.gemspec +1 -1
  56. metadata +21 -6
  57. data/lib/contrast/utils/input_classification_base.rb +0 -169
@@ -0,0 +1,55 @@
1
+ # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/agent/telemetry/input_analysis_cache_event'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Telemetry
9
+ # This hash will store the telemetry data for the Protect InputAnalysis cache.
10
+ class CacheHash < Hash
11
+ include Contrast::Components::Logger::InstanceMethods
12
+ # Set per request:
13
+ HASH_SIZE_LIMIT = 100
14
+
15
+ # Wrapper to set a value in this Telemetry::Hash only if the provided value is of the data_type for this
16
+ # Telemetry::CacheHash or the hash has not reached its limit for unique keys.
17
+ # Saves Array of reportable events.
18
+ #
19
+ # @param key [Object] the key to which to associate the value
20
+ # @param events [array<Object>]
21
+ # @return [Object, nil] echo back out the value as the Hash#[]= method does, or nil if not of the expected
22
+ # data_type
23
+ def []= key, events
24
+ # If telemetry is not running, do not add more as we want to avoid a memory leak.
25
+ return unless Contrast::Agent.telemetry_queue&.running?
26
+ # If the Hash is full, do not add more as we want to avoid consuming all application resources.
27
+ return if at_limit?
28
+ # If the given value is of unexpected type, do not add it to avoid issues later where type is assumed.
29
+ return unless valid_event?(events)
30
+
31
+ super(key, events)
32
+ end
33
+
34
+ # Determine if hash has reached exception event limit.
35
+ #
36
+ # @return [Boolean]
37
+ def at_limit?
38
+ unless length < HASH_SIZE_LIMIT
39
+ logger.debug("[Telemetry] Number of IA cache events exceeds limit of #{ HASH_SIZE_LIMIT }")
40
+ return true
41
+ end
42
+ false
43
+ end
44
+
45
+ private
46
+
47
+ # Checks to see if the given object is a valid event.
48
+ # @param event [Contrast::Agent::Telemetry::InputAnalysisCacheEvent]
49
+ def valid_event? events
50
+ events&.all?(Contrast::Agent::Telemetry::InputAnalysisCacheEvent)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -65,6 +65,7 @@ module Contrast
65
65
  else
66
66
  60
67
67
  end
68
+ logger.debug('[Telemetry] received response.', response_code: status_code)
68
69
  ready_after if status_code == 429
69
70
  end
70
71
 
@@ -76,6 +77,8 @@ module Contrast
76
77
  return true if event.cs__is_a?(Contrast::Agent::Telemetry::Event)
77
78
  return true if event.cs__is_a?(Contrast::Agent::Telemetry::StartupMetricsEvent)
78
79
  return true if event.cs__is_a?(Contrast::Agent::Telemetry::Exception::Event)
80
+ return true if event.cs__is_a?(Contrast::Agent::Telemetry::InputAnalysisCacheEvent)
81
+ return true if event.cs__is_a?(Contrast::Agent::Telemetry::InputAnalysisEncodingEvent)
79
82
 
80
83
  false
81
84
  end
@@ -93,12 +96,17 @@ module Contrast
93
96
  "#{ Contrast::Agent::Telemetry::Base::URL }#{ endpoint }#{ path }"
94
97
  end
95
98
 
96
- # Helper Method to get json representation of Telemetry Event data, handles error on to_json
99
+ # Helper Method to get json representation of Telemetry Event data, handles error on to_json.
100
+ # Generating bodies for exceptions and startup metrics is different.
97
101
  #
98
102
  # @param event [Contrast::Agent::Telemetry::Event, Array<Contrast::Agent::Telemetry::Exception::Event>]
99
103
  # @return [String] - JSON
100
104
  def get_event_json event
101
- Array(event.to_controlled_hash).to_json
105
+ if event.cs__is_a?(Contrast::Agent::Telemetry::Exception::Event)
106
+ return Array(event.to_controlled_hash).to_json
107
+ end
108
+
109
+ [event.to_controlled_hash].to_json
102
110
  rescue Exception => e # rubocop:disable Lint/RescueException
103
111
  logger.error('[Telemetry] Unable to convert TelemetryEvent to JSON string', e, hsh)
104
112
  raise(e)
@@ -10,7 +10,7 @@ module Contrast
10
10
  # This is the Telemetry::Hash, which will store Contrast::Agent::Telemetry::Exception::Event, so we can push
11
11
  # freely, without worrying about validating the event before that. Telemetry::Hash has a max size of events,
12
12
  # default is 10 events
13
- class Hash < Hash
13
+ class ExceptionHash < Hash
14
14
  include Contrast::Components::Logger::InstanceMethods
15
15
  HASH_SIZE_LIMIT = 10
16
16
 
@@ -0,0 +1,27 @@
1
+ # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/agent/telemetry/input_analysis_event'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Telemetry
9
+ # Event to report all gather information from the Input Analysis Cache statistics, hits and misses.
10
+ class InputAnalysisCacheEvent < Contrast::Agent::Telemetry::InputAnalysisEvent
11
+ NAME = 'InputAnalysis cache event'
12
+ PATH = '/protect_input_analysis_cache'
13
+
14
+ private
15
+
16
+ # Creates the tags for the event
17
+ #
18
+ # @param rule_id [String]
19
+ # @param match_rates [Contrast::Agent::Protect::Rule::InputClassification::MatchRates]
20
+ def add_tags rule_id, match_rates
21
+ super(rule_id, match_rates)
22
+ @tags['score_level'] = match_rates&.score_level.dup&.to_s || NOT_APPLICABLE
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/agent/telemetry/input_analysis_event'
5
+
6
+ module Contrast
7
+ module Agent
8
+ module Telemetry
9
+ # Event to report all gather information from the Input Analysis Cache statistics, hits and misses.
10
+ class InputAnalysisEncodingEvent < Contrast::Agent::Telemetry::InputAnalysisEvent
11
+ NAME = 'InputAnalysis encoding event'
12
+ PATH = '/protect_input_analysis_encoding'
13
+
14
+ private
15
+
16
+ # Creates the tags for the event
17
+ #
18
+ # @param _rule_id [String]
19
+ # @param encode_rates [Contrast::Agent::Protect::Rule::InputClassification::EncodingRates]
20
+ def add_tags _rule_id, encode_rates
21
+ super(nil, encode_rates)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,91 @@
1
+ # Copyright (c) 2023 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/metric_event'
6
+ require 'contrast/agent/version'
7
+ require 'contrast/components/logger'
8
+
9
+ module Contrast
10
+ module Agent
11
+ module Telemetry
12
+ # Event to report all gather information from the Input Analysis metrics.
13
+ class InputAnalysisEvent < Contrast::Agent::Telemetry::MetricEvent
14
+ include Contrast::Components::Logger::InstanceMethods
15
+ NOT_APPLICABLE = 'n/a'
16
+
17
+ attr_reader :fields
18
+
19
+ # Override the name for any derived classes
20
+ NAME = 'InputAnalysis event'
21
+ # Override the path for any derived classes
22
+ PATH = '/protect_input_analysis'
23
+
24
+ # @param rule_id [String] the rule name.
25
+ # @param rates [Contrast::Agent::Protect::Rule::InputClassification::Rates] base class for all rates.
26
+ def initialize rule_id, rates
27
+ super()
28
+ @fields = MetricsHash.new(Integer)
29
+ add_tags(rule_id, rates)
30
+ generate_fields(rates)
31
+ end
32
+
33
+ # Returns the name of the event.
34
+ #
35
+ # @return [String]
36
+ def name
37
+ cs__class::NAME
38
+ end
39
+
40
+ # Returns the path to report the event.
41
+ #
42
+ # @return [String]
43
+ def path
44
+ cs__class::PATH
45
+ end
46
+
47
+ # Override the empty check for any derived classes if needed.
48
+ def empty?
49
+ super && Contrast::Utils::DuckUtils.empty_duck?(@tags)
50
+ end
51
+
52
+ private
53
+
54
+ # Creates the tags for the event. Overrides the base class to add the rule_id and match_rate.
55
+ #
56
+ # @param rule_id [String, nil]
57
+ # @param rates [Contrast::Agent::Protect::Rule::InputClassification::Rates]
58
+ def add_tags rule_id, rates
59
+ return if Contrast::Utils::DuckUtils.empty_duck?(rates)
60
+ return unless rates.cs__is_a?(Contrast::Agent::Protect::Rule::InputClassification::Rates)
61
+
62
+ @tags['event_type'] = name # rubocop:disable Security/Module/Name
63
+ @tags['test_environment'] = ENV['CONTRAST_AGENT_TELEMETRY_TEST'] == '1' ? 'true' : 'false'
64
+ @tags['rule_id'] = rule_id if rule_id
65
+ @tags['input_type'] = rates&.input_type.dup&.to_s || NOT_APPLICABLE
66
+ add_system_tags
67
+ end
68
+
69
+ # Creates the fields for the event.
70
+ # Override if needed.
71
+ #
72
+ # @param rates [Contrast::Agent::Protect::Rule::InputClassification::Rates] base class for all rates.
73
+ def generate_fields rates
74
+ return if Contrast::Utils::DuckUtils.empty_duck?(rates)
75
+ return unless rates.cs__is_a?(Contrast::Agent::Protect::Rule::InputClassification::Rates)
76
+
77
+ rates.to_fields.each { |field, value| @fields[field] = value.dup }
78
+ end
79
+
80
+ # Adds the system tags to the event.
81
+ def add_system_tags
82
+ @tags['agent_version'] = VERSION
83
+ @tags['ruby_version'] = RUBY_VERSION
84
+ @tags['os_type'] = sys_info['os_type'] == 'Darwin' ? 'MacOS' : 'Linux'
85
+ @tags['os_arch'] = sys_info['os_arch']
86
+ @tags['os_version'] = sys_info['os_version']
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -3,6 +3,8 @@
3
3
 
4
4
  require 'contrast/utils/metrics_hash'
5
5
  require 'contrast/agent/telemetry/event'
6
+ require 'contrast/utils/duck_utils'
7
+ require 'contrast/utils/os'
6
8
 
7
9
  module Contrast
8
10
  module Agent
@@ -10,6 +12,7 @@ module Contrast
10
12
  # This class will hold the basic information for a Telemetry Event
11
13
  class MetricEvent < Contrast::Agent::Telemetry::Event
12
14
  include Contrast::Utils
15
+ include Contrast::Utils::OS
13
16
 
14
17
  attr_reader :fields
15
18
 
@@ -19,6 +22,15 @@ module Contrast
19
22
  @fields['_filler'] = 0
20
23
  end
21
24
 
25
+ def sys_info
26
+ @sys_info ||= get_system_information if @sys_info.nil?
27
+ @sys_info
28
+ end
29
+
30
+ def empty?
31
+ Contrast::Utils::DuckUtils.empty_duck?(@fields)
32
+ end
33
+
22
34
  def to_controlled_hash **_args
23
35
  super.merge!({ fields: @fields })
24
36
  end
@@ -4,7 +4,6 @@
4
4
  require 'contrast/utils/metrics_hash'
5
5
  require 'contrast/agent/telemetry/metric_event'
6
6
  require 'contrast/agent/version'
7
- require 'contrast/utils/os'
8
7
 
9
8
  module Contrast
10
9
  module Agent
@@ -15,8 +14,6 @@ module Contrast
15
14
  # application framework and version and server framework
16
15
  # It will be initialized and send in Middleware#agent_startup_routine
17
16
  class StartupMetricsEvent < Contrast::Agent::Telemetry::MetricEvent
18
- include Contrast::Utils::OS
19
-
20
17
  APP_AND_SERVER_DATA = ::Contrast::APP_CONTEXT.app_and_server_information.cs__freeze
21
18
  # Multi-tenant Production Environments
22
19
  SAAS_DEFAULT = { addr: 'app.contrastsecurity.com', type: 'SAAS_DEFAULT' }.cs__freeze
@@ -82,11 +79,6 @@ module Contrast
82
79
  end
83
80
  end
84
81
 
85
- def sys_info
86
- @sys_info ||= get_system_information if @sys_info.nil?
87
- @sys_info
88
- end
89
-
90
82
  private
91
83
 
92
84
  # Here we extract the TeamServer url type
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Contrast
5
5
  module Agent
6
- VERSION = '7.2.0'
6
+ VERSION = '7.3.0'
7
7
  end
8
8
  end
@@ -25,7 +25,7 @@ module Contrast
25
25
  # time than to silently fail to deliver functionality.
26
26
  module Config
27
27
  CONTRAST_ENV_MARKER = 'CONTRAST__'
28
- CONTRAST_LOG = 'contrast_agent.log'
28
+ CONTRAST_LOG = 'contrast.log'
29
29
  CONTRAST_NAME = 'Contrast Agent'
30
30
  DATE_TIME = '%Y-%m-%dT%H:%M:%S.%L%z'
31
31
 
@@ -63,7 +63,7 @@ module Contrast
63
63
  env_overrides
64
64
  validate
65
65
  rescue ArgumentError => e
66
- proto_logger.error('Configuration failed with error: ', e)
66
+ proto_logger.error('[PROTO_LOGGER] Configuration failed with error: ', e)
67
67
  end
68
68
  alias_method :rebuild, :build
69
69
 
@@ -157,7 +157,7 @@ module Contrast
157
157
  # @return [boolean]
158
158
  def valid_session_metadata?
159
159
  if !session_id&.empty? && !session_metadata&.empty?
160
- proto_logger.error(SESSION_VARIABLES)
160
+ proto_logger.error("[PROTO_LOGGER] #{ SESSION_VARIABLES }")
161
161
  return false
162
162
  end
163
163
  true
@@ -172,7 +172,7 @@ module Contrast
172
172
  msg << API_KEY unless api_key
173
173
  msg << API_SERVICE_KEY unless api_service_key
174
174
  msg << API_USERNAME unless api_username
175
- msg.any? { |m| proto_logger.error(m) }
175
+ msg.any? { |m| proto_logger.error("[PROTO_LOGGER] #{ m }") }
176
176
  msg.empty?
177
177
  end
178
178
 
@@ -15,12 +15,14 @@ module Contrast
15
15
  include Contrast::Config::BaseConfiguration
16
16
 
17
17
  CANON_NAME = 'protect'
18
- CONFIG_VALUES = %w[enabled?].cs__freeze
18
+ CONFIG_VALUES = %w[enabled? normalize_base64?].cs__freeze
19
19
  RULES = 'rules'
20
20
  MODE = 'mode'
21
21
 
22
22
  # @return [Boolean, nil]
23
23
  attr_accessor :enable
24
+ # @return [Boolean, nil]
25
+ attr_accessor :normalize_base64
24
26
  # @return [String]
25
27
  attr_reader :canon_name
26
28
  # @return [Array]
@@ -36,6 +38,7 @@ module Contrast
36
38
  @_exceptions = Contrast::Config::ExceptionConfiguration.new(hsh[:exceptions])
37
39
  @_rules = Contrast::Config::ProtectRulesConfiguration.new(hsh[:rules])
38
40
  @enable = hsh[:enable]
41
+ @normalize_base64 = hsh[:normalize_base64]
39
42
  @agent_lib = hsh[:agent_lib]
40
43
  end
41
44
 
@@ -68,6 +71,13 @@ module Contrast
68
71
  ::Contrast::SETTINGS.protect_state.enabled == true
69
72
  end
70
73
 
74
+ # Check to determine if the base64 decoding is required for user inputs.
75
+ def normalize_base64?
76
+ @normalize_base64 = Contrast::CONFIG.protect.normalize_base64 if @normalize_base64.nil?
77
+
78
+ true?(@normalize_base64)
79
+ end
80
+
71
81
  # Current Configuration for the protect rules
72
82
  #
73
83
  # @return [Contrast::Config::ProtectRulesConfiguration]
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/components/base'
5
+ require 'contrast/utils/duck_utils'
5
6
 
6
7
  module Contrast
7
8
  module Components
@@ -27,7 +28,7 @@ module Contrast
27
28
  config_settings = ::Contrast::CONFIG.assess&.sampling
28
29
  settings = ::Contrast::SETTINGS&.assess_state&.sampling_settings
29
30
  {
30
- enabled: enabled?(config_settings, settings),
31
+ enabled: enabled?(config_settings&.enable, settings&.enabled),
31
32
  baseline: check_baseline(config_settings, settings),
32
33
  request_frequency: check_request_frequency(config_settings, settings),
33
34
  response_frequency: check_response_frequency(config_settings, settings),
@@ -43,13 +44,20 @@ module Contrast
43
44
 
44
45
  private
45
46
 
46
- # @param config_settings [Contrast::Config::SamplingConfiguration] the Sampling configuration as provided by
47
+ # @param config_value [Boolean, nil] the Sampling configuration as provided by
47
48
  # local user input
48
- # @param settings [Contrast::Agent::Reporting::Settings::Sampling, nil] the Sampling settings as provided by
49
+ # @param settings_value [Boolean, nil] the Sampling settings as provided by
49
50
  # TeamServer
50
51
  # @return [Boolean] the resolution of the config_settings, settings, and default value
51
- def enabled? config_settings, settings
52
- true?([config_settings&.enable, settings&.enabled, DEFAULT_SAMPLING_ENABLED].compact[0])
52
+ def enabled? config_value, settings_value
53
+ # Check the Assess State received from TS:
54
+ sampling_enable = settings_value unless Contrast::Utils::DuckUtils.empty_duck?(settings_value)
55
+ # Check for local settings, YAML, ENV, CLI (it's with higher priority than Web Interface)
56
+ # see: https://docs.contrastsecurity.com/en/order-of-precedence.html
57
+ sampling_enable = config_value unless Contrast::Utils::DuckUtils.empty_duck?(config_value)
58
+ # Use default value, unless a false or true is set from local or TS settings.
59
+ sampling_enable = DEFAULT_SAMPLING_ENABLED if Contrast::Utils::DuckUtils.empty_duck?(sampling_enable)
60
+ true?(sampling_enable)
53
61
  end
54
62
 
55
63
  # @param config_settings [Contrast::Config::SamplingConfiguration] the Sampling configuration as provided by
@@ -106,6 +114,8 @@ module Contrast
106
114
  NAME_PREFIX = "#{ CONTRAST }.#{ CANON_NAME }".cs__freeze
107
115
  CONFIG_VALUES = %w[enable baseline request_frequency response_frequency window_ms].cs__freeze
108
116
 
117
+ # @return [Boolean, nil]
118
+ attr_accessor :enable
109
119
  # @return [Integer, nil]
110
120
  attr_accessor :baseline
111
121
  # @return [Integer, nil]
@@ -128,11 +138,6 @@ module Contrast
128
138
  @window_ms = hsh[:window_ms]
129
139
  end
130
140
 
131
- # @return [Boolean, false]
132
- def enable
133
- !!@enable
134
- end
135
-
136
141
  # Converts current configuration to effective config values class and appends them to
137
142
  # EffectiveConfig class.
138
143
  #
@@ -11,7 +11,9 @@ module Contrast
11
11
  # Reads All ENV variables.
12
12
  module EnvironmentVariables
13
13
  class << self
14
- NON_COMMON_ENV = %w[CONTRAST_CONFIG_PATH CONTRAST_AGENT_TELEMETRY_OPTOUT].cs__freeze
14
+ NON_COMMON_ENV = %w[
15
+ CONTRAST_CONFIG_PATH CONTRAST_AGENT_TELEMETRY_OPTOUT CONTRAST_AGENT_TELEMETRY_TEST
16
+ ].cs__freeze
15
17
 
16
18
  # This method will fill the canonical name for each env var and will check for any uncommon ones.
17
19
  #
@@ -11,6 +11,8 @@ module Contrast
11
11
  # instrumentation.
12
12
  module YamlFile
13
13
  CONFIG_FILE_NAME = 'contrast_security'
14
+ CONTRAST_ENV_MARKER = 'CONTRAST__'
15
+
14
16
  EXT = { yml: 'yml', yaml: 'yaml' }.freeze # rubocop:disable Security/Object/Freeze
15
17
 
16
18
  POSSIBLE_TARGET_PATHS = %w[
@@ -89,6 +91,8 @@ module Contrast
89
91
  #
90
92
  def create
91
93
  # rubocop:disable Rails/Output
94
+ return puts("\u{02C3} Contrast configuration set by ENV variables.") if env_config_set?
95
+
92
96
  puts("\u{1F48E} Generating: Contrast Configuration file.")
93
97
  if Contrast::Config::YamlFile.created?
94
98
  puts("\u{2705} Configuration file already exists: #{ Contrast::Config::YamlFile.find! }")
@@ -123,6 +127,10 @@ module Contrast
123
127
  def file_name
124
128
  ENV['CONTRAST_YAML_FILE_TEST_CREATE_CONFIG_FILE_NAME_VALUE'] || CONFIG_FILE_NAME
125
129
  end
130
+
131
+ def env_config_set?
132
+ ENV.keys&.select { |config| config.include?(CONTRAST_ENV_MARKER) }&.any?
133
+ end
126
134
  end
127
135
  end
128
136
  end
@@ -4,6 +4,7 @@
4
4
  require 'contrast/framework/base_support'
5
5
  require 'contrast/framework/rails/patch/support'
6
6
  require 'contrast/utils/string_utils'
7
+ require 'contrast/utils/duck_utils'
7
8
 
8
9
  module Contrast
9
10
  module Framework
@@ -129,6 +130,8 @@ module Contrast
129
130
  if engine_route?(route)
130
131
  new_req = retrieve_request(request.env)
131
132
  new_req.path_info = new_req.path_info.gsub(match.to_s, '')
133
+ # solves the issue when requiring base path '/' without the slash
134
+ new_req.path_info = '/' if Contrast::Utils::DuckUtils.empty_duck?(new_req.path_info)
132
135
  get_full_route(new_req, route.app.app.routes.router, path << match.to_s)
133
136
  else
134
137
  [match, params, route, path]
@@ -70,13 +70,13 @@ module Contrast
70
70
  if current_count == event_max
71
71
  return if event_limit_counts.key?(get_event_limit_key(method_policy, context))
72
72
 
73
- logger.warn('Event Limit Reached:',
74
- {
75
- count: current_count,
76
- max: event_max,
77
- policy: method_policy.method_name,
78
- node: method_policy
79
- })
73
+ logger.debug('Event Limit Reached:',
74
+ {
75
+ count: current_count,
76
+ max: event_max,
77
+ policy: method_policy.method_name,
78
+ node: method_policy
79
+ })
80
80
  # increment to be over count for logging purposes
81
81
  increment_event_count(method_policy)
82
82
  increment_event_limit_logs(method_policy, context)
@@ -87,12 +87,12 @@ module Contrast
87
87
  return if event_limit_counts.key?(get_event_limit_key(method_policy, context))
88
88
 
89
89
  # increment to be over count for logging purposes
90
- logger.warn('Event Limit Exceeded:',
91
- {
92
- count: current_count,
93
- policy: method_policy.method_name,
94
- node: method_policy
95
- })
90
+ logger.debug('Event Limit Exceeded:',
91
+ {
92
+ count: current_count,
93
+ policy: method_policy.method_name,
94
+ node: method_policy
95
+ })
96
96
  increment_event_limit_logs(method_policy, context)
97
97
  return true
98
98
  end
@@ -17,7 +17,7 @@ module Contrast
17
17
  'The key extends the allowed length.',
18
18
  'The provided value is not the right data type'
19
19
  ].cs__freeze
20
- KEY_REGEXP = /[a-zA-Z0-9_-]{1,63}/.cs__freeze
20
+ KEY_REGEXP = /[a-zA-Z0-9._-]{1,63}/.cs__freeze
21
21
 
22
22
  def initialize data_type, *several_variants
23
23
  super
@@ -38,7 +38,8 @@ module Contrast
38
38
  AT = '@'
39
39
  LEFT_ANGLE = '<'
40
40
  COLON_SLASH_SLASH = '://'
41
- DOLLAR_SIGN = '$'
41
+ DOLLAR_SIGN = '$'
42
+ AMPERSAND = '&'
42
43
  CARROT = '^'
43
44
  BANG = '!'
44
45
 
@@ -19,6 +19,7 @@ module Contrast
19
19
  def extract_body body
20
20
  return unless body
21
21
  return if body_is_file?(body)
22
+ return if body_is_sprockets?(body)
22
23
 
23
24
  return handle_rack_body_proxy(body) if body.is_a?(Rack::BodyProxy)
24
25
  return extract_body(body.body) if sub_extractable?(body)
@@ -74,6 +75,17 @@ module Contrast
74
75
 
75
76
  false
76
77
  end
78
+
79
+ # Detects Rails' Sprockets asset pipeline objects passed as body.
80
+ # Returns false if Sprockets is passed as body, the Agent does not
81
+ # support Sprockets::Asset for body extraction.
82
+ #
83
+ # @param body [String, Rack::File, Rack::BodyProxy]
84
+ def body_is_sprockets? body
85
+ return body.cs__is_a?(Sprockets::Asset) if defined?(Sprockets) && defined?(Sprockets::Asset)
86
+
87
+ false
88
+ end
77
89
  end
78
90
  end
79
91
  end
@@ -1,6 +1,8 @@
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 'time'
5
+
4
6
  module Contrast
5
7
  module Utils
6
8
  # Timer is class that can track state about when an event starts and how long it takes
data/lib/contrast.rb CHANGED
@@ -61,10 +61,12 @@ require 'contrast/components/sampling'
61
61
  require 'contrast/components/scope'
62
62
  require 'contrast/components/settings'
63
63
  require 'contrast/utils/routes_sent'
64
- require 'contrast/agent/telemetry/hash'
64
+ require 'contrast/agent/telemetry/exception_hash'
65
65
  require 'contrast/agent/telemetry/telemetry'
66
66
  require 'contrast/agent/telemetry/exception/event'
67
67
  require 'contrast/agent_lib/interface'
68
+ require 'contrast/agent/telemetry/cache_hash'
69
+ require 'contrast/agent/telemetry/base64_hash'
68
70
 
69
71
  module Contrast # :nodoc:
70
72
  CONFIG = Contrast::Components::Config::Interface.new
@@ -82,7 +84,12 @@ module Contrast # :nodoc:
82
84
  end
83
85
 
84
86
  module Contrast
85
- TELEMETRY_EXCEPTIONS = (Contrast::Agent::Telemetry::Hash.new if Contrast::Agent::Telemetry.exceptions_enabled?)
87
+ TELEMETRY_EXCEPTIONS = (if Contrast::Agent::Telemetry.exceptions_enabled?
88
+ Contrast::Agent::Telemetry::ExceptionHash.new
89
+ end)
90
+ TELEMETRY_IA_CACHE = (Contrast::Agent::Telemetry::CacheHash.new if Contrast::Agent::Telemetry::Base.enabled?)
91
+ TELEMETRY_BASE64_HASH = (Contrast::Agent::Telemetry::Base64Hash.new if Contrast::Agent::Telemetry::Base.enabled? &&
92
+ Contrast::PROTECT.normalize_base64?)
86
93
  ROUTES_SENT = Contrast::Utils::RoutesSent.new
87
94
  end
88
95
 
data/ruby-agent.gemspec CHANGED
@@ -121,7 +121,7 @@ def self.add_dependencies spec
121
121
  spec.add_dependency 'rack', '~> 2.0'
122
122
 
123
123
  # bind this directly as we've had issues w/ build changes on bug release
124
- spec.add_dependency 'contrast-agent-lib', '1.1.0'
124
+ spec.add_dependency 'contrast-agent-lib', '1.1.1'
125
125
  end
126
126
 
127
127
  # Enumerate the files required to build the Agent.