contrast-agent 7.2.0 → 7.3.0

Sign up to get free protection for your applications and to get access to all the features.
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.