contrast-agent 7.3.2 → 7.4.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/contrast/agent/protect/rule/base.rb +5 -1
  3. data/lib/contrast/agent/protect/rule/cmdi/cmd_injection.rb +17 -5
  4. data/lib/contrast/agent/protect/rule/input_classification/base.rb +7 -2
  5. data/lib/contrast/agent/protect/rule/input_classification/encoding.rb +1 -1
  6. data/lib/contrast/agent/protect/rule/path_traversal/path_traversal.rb +8 -1
  7. data/lib/contrast/agent/protect/rule/sqli/sqli.rb +8 -1
  8. data/lib/contrast/agent/protect/state.rb +110 -0
  9. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +2 -0
  10. data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +2 -0
  11. data/lib/contrast/agent/reporting/reporting_events/finding.rb +1 -0
  12. data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +4 -0
  13. data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +2 -0
  14. data/lib/contrast/agent/reporting/reporting_events/observed_library_usage.rb +2 -0
  15. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +9 -5
  16. data/lib/contrast/agent/reporting/reporting_events/reportable_hash.rb +30 -6
  17. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +1 -1
  18. data/lib/contrast/agent/reporting/reporting_utilities/resend.rb +1 -1
  19. data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +1 -5
  20. data/lib/contrast/agent/reporting/settings/protect.rb +3 -3
  21. data/lib/contrast/agent/reporting/settings/sampling.rb +5 -4
  22. data/lib/contrast/agent/version.rb +1 -1
  23. data/lib/contrast/components/agent.rb +3 -5
  24. data/lib/contrast/components/api.rb +3 -3
  25. data/lib/contrast/components/assess_rules.rb +1 -2
  26. data/lib/contrast/components/base.rb +1 -2
  27. data/lib/contrast/components/config/sources.rb +23 -0
  28. data/lib/contrast/components/logger.rb +19 -0
  29. data/lib/contrast/components/protect.rb +55 -14
  30. data/lib/contrast/components/sampling.rb +5 -12
  31. data/lib/contrast/components/security_logger.rb +17 -0
  32. data/lib/contrast/components/settings.rb +110 -76
  33. data/lib/contrast/config/certification_configuration.rb +1 -1
  34. data/lib/contrast/config/configuration_files.rb +0 -2
  35. data/lib/contrast/config/diagnostics/config.rb +3 -3
  36. data/lib/contrast/config/diagnostics/effective_config.rb +1 -1
  37. data/lib/contrast/config/diagnostics/environment_variables.rb +21 -11
  38. data/lib/contrast/config/diagnostics/monitor.rb +1 -1
  39. data/lib/contrast/config/diagnostics/singleton_tools.rb +170 -0
  40. data/lib/contrast/config/diagnostics/source_config_value.rb +14 -9
  41. data/lib/contrast/config/diagnostics/tools.rb +23 -84
  42. data/lib/contrast/config/request_audit_configuration.rb +1 -1
  43. data/lib/contrast/config/server_configuration.rb +3 -15
  44. data/lib/contrast/configuration.rb +5 -2
  45. data/lib/contrast/framework/manager.rb +4 -3
  46. data/lib/contrast/framework/manager_extend.rb +3 -1
  47. data/lib/contrast/framework/rack/support.rb +11 -2
  48. data/lib/contrast/utils/log_utils.rb +1 -1
  49. data/lib/contrast/utils/request_utils.rb +1 -1
  50. data/lib/contrast/utils/timer.rb +1 -1
  51. metadata +4 -2
@@ -0,0 +1,170 @@
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
+ module Contrast
5
+ module Config
6
+ module Diagnostics
7
+ # Tools to help with the config diagnostics, called directly from the module.
8
+ module SingletonTools
9
+ API_CREDENTIALS = %w[api_key service_key].cs__freeze
10
+ CONTRAST_MARK = 'CONTRAST_'
11
+
12
+ # Creates new config instances for each read config entry from the flat generated configs.
13
+ #
14
+ # @param flats [Array] of flatten configs produced by #flatten_settings
15
+ # @param source [Boolean] flag to set the desired value class, it may be a effective or source value.
16
+ # @param cli [Boolean] flag to check if the value comes from cli.
17
+ # @return [Array<Contrast::Config::Diagnostics::SourceConfigValue>]
18
+ def to_config_values flats, source: false, cli: false
19
+ config_value_klass = if source
20
+ Contrast::Config::Diagnostics::SourceConfigValue
21
+ else
22
+ Contrast::Config::Diagnostics::EffectiveConfigValue
23
+ end
24
+ settings = []
25
+ flats.each do |entry|
26
+ entry.each do |key, value|
27
+ efc_value = config_value_klass.new.tap do |config_value|
28
+ config_value.canonical_name = key
29
+ if cli && key.to_s.include?(CONTRAST_MARK)
30
+ config_value.canonical_name = key.gsub(Contrast::Utils::ObjectShare::DOUBLE_UNDERSCORE,
31
+ Contrast::Utils::ObjectShare::PERIOD).downcase
32
+ end
33
+ config_value.key = key
34
+ config_value.value = if API_CREDENTIALS.include?(key.to_s)
35
+ Contrast::Configuration::EFFECTIVE_REDACTED
36
+ else
37
+ value_to_s(value)
38
+ end
39
+ end
40
+ next unless efc_value
41
+
42
+ settings << efc_value
43
+ end
44
+ end
45
+ settings
46
+ end
47
+
48
+ # Flattens out the read settings from file, env or contrast ui.
49
+ # example: {"agent.polling.server_settings_ms"=>"50000"}
50
+ #
51
+ # If cli is set we avoid adding the path and additional '.' to the key.
52
+ #
53
+ # @param data [Hash, nil]
54
+ # @param path [String] where to look for settings.
55
+ # @param config [Hash] symbolized config to fetch keys from.
56
+ # @param cli [Boolean] does the config come from cli.
57
+ def flatten_settings data, path = [], config: Contrast::CONFIG.config.loaded_config, cli: false
58
+ return [] unless data
59
+
60
+ data.each_with_object([]) do |(k, v), entries|
61
+ if v.cs__is_a?(Hash)
62
+ entries.concat(flatten_settings(v, path.dup.append(k.to_sym)))
63
+ else
64
+ if API_CREDENTIALS.include?(k.to_s)
65
+ entries << { k.to_s => Contrast::Configuration::EFFECTIVE_REDACTED } if cli
66
+ entries << { "#{ path.join('.') }.#{ k }" => Contrast::Configuration::EFFECTIVE_REDACTED } unless cli
67
+ next
68
+ end
69
+ entries << { k.to_s => value_to_s(config.dig(*path, k)) } if cli
70
+ entries << { "#{ path.join('.') }.#{ k }" => value_to_s(config.dig(*path, k)) } unless cli
71
+ end
72
+ end.flatten # rubocop:disable Style/MethodCalledOnDoEndBlock
73
+ end
74
+
75
+ # Update the stored config values to ensure that we know about the correct values,
76
+ # and that the sources are correct for entries updated from the UI.
77
+ #
78
+ # @param parts [Array<Symbols>, String] the path to the setting in config
79
+ # Accepts Array: [:agent :enable] or String: 'agent.enable'
80
+ # @param value [String, Integer, Array, nil] the value for the configuration setting
81
+ # @param source_type [String] the source of the configuration setting
82
+ def update_config parts, value, source_type
83
+ parts_array, string = handle_parts_array(parts)
84
+ path = string ? parts : parts_array.join('.')
85
+ return unless parts_array
86
+
87
+ # Check to see whether the source has been overridden by local settings,
88
+ # Before updating from Contrast UI.
89
+ if source_type == Contrast::Components::Config::Sources::CONTRAST_UI &&
90
+ Contrast::CONFIG.sources.source_overridden?(path)
91
+
92
+ return
93
+ end
94
+
95
+ level = Contrast::CONFIG.config.loaded_config
96
+ parts_array[0...-1]&.each do |segment|
97
+ level[segment] ||= {}
98
+ level = level[segment]
99
+ end
100
+ return unless level.cs__is_a?(Hash)
101
+
102
+ level[parts_array[-1]] = value
103
+ Contrast::CONFIG.sources.set(path, source_type)
104
+ end
105
+
106
+ # Recursively converts each value to string.
107
+ #
108
+ # @param value [Hash, nil]
109
+ def value_to_s value
110
+ case value
111
+ when String
112
+ if Contrast::Utils::DuckUtils.empty_duck?(value)
113
+ Contrast::Config::Diagnostics::SourceConfigValue::NULL
114
+ else
115
+ value
116
+ end
117
+ when Array
118
+ handle_array_to_s(value)
119
+ when Hash
120
+ handle_hash_to_s(value)
121
+ when TrueClass, FalseClass, Symbol, Integer
122
+ value.to_s
123
+ else
124
+ Contrast::Config::Diagnostics::SourceConfigValue::NULL
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ # Checks the type of path and converts it to array.
131
+ # If the path is string it splits it by '.' and converts each element to symbol.
132
+ #
133
+ # @param parts [Array<Symbols>, String] the path to the setting in config
134
+ # @return [Array<Symbols>, String]
135
+ def handle_parts_array parts
136
+ string = false
137
+ arr = if parts.cs__is_a?(String)
138
+ string = true
139
+ parts.split('.')&.map&.each(&:to_sym)
140
+ else
141
+ parts
142
+ end
143
+ [arr, string]
144
+ end
145
+
146
+ # @param hash [Hash]
147
+ # @return [Hash]
148
+ def handle_hash_to_s hash
149
+ hash&.each_with_object({}) do |(k, v), m| # rubocop:disable Style/HashTransformValues
150
+ m[k] = if v.cs__is_a?(Hash)
151
+ value_to_s(v)
152
+ elsif v.cs__is_a?(Array)
153
+ v.map(&:to_s)
154
+ else
155
+ v.to_s
156
+ end
157
+ end
158
+ end
159
+
160
+ # @param array [Array]
161
+ # @return [String]
162
+ def handle_array_to_s array
163
+ return Contrast::Config::Diagnostics::SourceConfigValue::NULL if Contrast::Utils::DuckUtils.empty_duck?(array)
164
+
165
+ array.join(',')
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -1,11 +1,16 @@
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/utils/object_share'
5
+ require 'contrast/utils/duck_utils'
6
+
4
7
  module Contrast
5
8
  module Config
6
9
  module Diagnostics
7
10
  # All config values from all sources, stored in a easy to write representation.
8
11
  class SourceConfigValue
12
+ NULL = 'null'
13
+
9
14
  # @return [String] Name of the config starting form root of yaml config.
10
15
  attr_accessor :canonical_name
11
16
  # @return [String] Name of the config.
@@ -20,23 +25,23 @@ module Contrast
20
25
  def to_controlled_hash
21
26
  {
22
27
  canonical_name: canonical_name,
23
- name: key,
24
- value: value.cs__is_a?(Array) ? value.map(&:to_s) : value.to_s,
25
- source: source,
26
- filename: filename
27
- }.compact
28
+ name: key || Contrast::Utils::ObjectShare::EMPTY_STRING,
29
+ value: Contrast::Config::Diagnostics::Tools.value_to_s(value),
30
+ source: source || Contrast::Utils::ObjectShare::EMPTY_STRING,
31
+ filename: filename || Contrast::Utils::ObjectShare::EMPTY_STRING
32
+ }
28
33
  end
29
34
 
30
35
  def to_source_hash
31
36
  {
32
37
  canonical_name: canonical_name,
33
- name: key,
34
- value: value.cs__is_a?(Array) ? value.map(&:to_s) : value.to_s
35
- }.compact
38
+ name: key || Contrast::Utils::ObjectShare::EMPTY_STRING,
39
+ value: Contrast::Config::Diagnostics::Tools.value_to_s(value)
40
+ }
36
41
  end
37
42
 
38
43
  # Assigns file name of the config iv viable, Currently supported formats for config file are *.yaml
39
- # and *.yml
44
+ # and *.yml. For the config loaded from file the filename is kept as source.
40
45
  #
41
46
  # @param source [String] name of the source file yaml | yml
42
47
  # @return [Array<String>, nil]
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'contrast/utils/object_share'
5
5
  require 'contrast/config/diagnostics/effective_config_value'
6
+ require 'contrast/config/diagnostics/singleton_tools'
6
7
  require 'contrast/utils/duck_utils'
7
8
 
8
9
  module Contrast
@@ -11,79 +12,11 @@ module Contrast
11
12
  # Diagnostics tools to be included in config components.
12
13
  module Tools
13
14
  CHECK = 'd'
14
- CONTRAST_MARK = 'CONTRAST_'
15
- class << self
16
- # Creates new config instances for each read config entry from the flat generated configs.
17
- #
18
- # @param flats [Array] of flatten configs produced by #flatten_settings
19
- # @param source [Boolean] flag to set the desired value class, it may be a effective or source value.
20
- # @param cli [Boolean] flag to check if the value comes from cli.
21
- # @return [Array<Contrast::Config::Diagnostics::SourceConfigValue>]
22
- def to_config_values flats, source: false, cli: false
23
- config_value_klass = if source
24
- Contrast::Config::Diagnostics::SourceConfigValue
25
- else
26
- Contrast::Config::Diagnostics::EffectiveConfigValue
27
- end
28
- settings = []
29
- flats.each do |entry|
30
- entry.each do |key, value|
31
- efc_value = config_value_klass.new.tap do |config_value|
32
- config_value.canonical_name = Contrast::Utils::ObjectShare::CONTRAST_DOT + key unless cli
33
- if cli && key.to_s.include?(CONTRAST_MARK)
34
- config_value.canonical_name = key.gsub(Contrast::Utils::ObjectShare::DOUBLE_UNDERSCORE,
35
- Contrast::Utils::ObjectShare::PERIOD).downcase
36
- end
37
- config_value.key = key
38
- config_value.value = value_to_s(value)
39
- end
40
- settings << efc_value if efc_value
41
- end
42
- end
43
- settings
44
- end
45
-
46
- # Flattens out the read settings from file, env or contrast ui.
47
- # example: {"agent.polling.server_settings_ms"=>"50000"}
48
- #
49
- # If cli is set we avoid adding the path and additional '.' to the key.
50
- #
51
- # @param data [Hash, nil]
52
- # @param path [String] where to look for settings.
53
- # @param config [Hash] symbolized config to fetch keys from.
54
- # @param cli [Boolean] does the config come from cli.
55
- def flatten_settings data, path = [], config: Contrast::CONFIG.config.loaded_config, cli: false
56
- return [] unless data
57
-
58
- data.each_with_object([]) do |(k, v), entries|
59
- if v.cs__is_a?(Hash)
60
- entries.concat(flatten_settings(v, path.dup.append(k.to_sym)))
61
- else
62
- entries << { k.to_s => config.dig(*path, k).to_s } if cli
63
- entries << { "#{ path.join('.') }.#{ k }" => config.dig(*path, k).to_s } unless cli
64
- end
65
- end.flatten # rubocop:disable Style/MethodCalledOnDoEndBlock
66
- end
67
15
 
68
- # Recursively converts each value to string.
69
- #
70
- # @param value [Hash, nil]
71
- def value_to_s value
72
- return if value.nil?
73
- return value if value.cs__is_a?(String)
74
-
75
- value&.each_with_object({}) do |(k, v), m| # rubocop:disable Style/HashTransformValues
76
- m[k] = if v.cs__is_a?(Hash)
77
- value_to_s(v)
78
- elsif v.cs__is_a?(Array)
79
- v.map(&:to_s)
80
- else
81
- v.to_s
82
- end
83
- end
84
- end
85
- end
16
+ extend Contrast::Config::Diagnostics::SingletonTools
86
17
 
18
+ # TODO: RUBY-2113 deprecate name_prefix
19
+ #
87
20
  # Converts current configuration from array of values to effective config values class and appends them to
88
21
  # EffectiveConfig class. Must be used inside Config Components only.
89
22
  #
@@ -91,13 +24,15 @@ module Contrast
91
24
  # @param config_values [] array of the names of values.
92
25
  # @param canonical_prefix [String] starting of the path to config => api.proxy...
93
26
  # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url
94
- def add_effective_config_values effective_config, config_values, canonical_prefix, name_prefix
27
+ def add_effective_config_values(effective_config,
28
+ config_values,
29
+ canonical_prefix,
30
+ name_prefix = canonical_prefix)
95
31
  return if config_values.to_s.empty?
96
32
 
97
33
  config_values.each do |config_value_name|
98
34
  Contrast::Config::Diagnostics::EffectiveConfigValue.new.tap do |new_effective_value|
99
- next if Contrast::Utils::DuckUtils.empty_duck?((config_value = send(config_value_name.to_sym)))
100
-
35
+ config_value = send(config_value_name.to_sym)
101
36
  fill_effective_value(new_effective_value, config_value, config_value_name, canonical_prefix, name_prefix)
102
37
  effective_config.values << new_effective_value
103
38
  rescue StandardError => e
@@ -107,6 +42,8 @@ module Contrast
107
42
  end
108
43
  end
109
44
 
45
+ # TODO: RUBY-2113 deprecate name_prefix
46
+ #
110
47
  # Converts current configuration from single value to effective config values class and appends them to
111
48
  # EffectiveConfig class. Must be used inside Config Components only.
112
49
  #
@@ -115,10 +52,12 @@ module Contrast
115
52
  # @param config_value [String, Boolean] value of the config.
116
53
  # @param canonical_prefix [String] starting of the path to config => api.proxy...
117
54
  # @param name_prefix [String] the name of the config prefix => contrast.api_key, contrast.url
118
- def add_single_effective_value effective_config, config_name, config_value, canonical_prefix, name_prefix
55
+ def add_single_effective_value(effective_config,
56
+ config_name,
57
+ config_value,
58
+ canonical_prefix,
59
+ name_prefix = canonical_prefix)
119
60
  Contrast::Config::Diagnostics::EffectiveConfigValue.new.tap do |new_effective_value|
120
- break if Contrast::Utils::DuckUtils.empty_duck?(config_value)
121
-
122
61
  fill_effective_value(new_effective_value, config_value, config_name, canonical_prefix, name_prefix)
123
62
  effective_config.values << new_effective_value
124
63
  rescue StandardError => e
@@ -139,7 +78,11 @@ module Contrast
139
78
  # @return filled_new_effective_config [Contrast::Config::Diagnostics::EffectiveConfigValue]
140
79
  def fill_effective_value new_effective_value, config_value, config_value_name, canonical_prefix, name_prefix
141
80
  find_source(new_effective_value, canonical_prefix, assign_name(config_value_name), name_prefix)
142
- new_effective_value.value = config_value
81
+ if Contrast::Config::Diagnostics::SingletonTools::API_CREDENTIALS.include?(config_value_name.to_s)
82
+ new_effective_value.value = Contrast::Configuration::EFFECTIVE_REDACTED
83
+ return new_effective_value
84
+ end
85
+ new_effective_value.value = config_value.cs__is_a?(Array) ? config_value.join(',') : config_value.to_s
143
86
  new_effective_value
144
87
  end
145
88
 
@@ -174,14 +117,10 @@ module Contrast
174
117
  # For files we keep the whole path as source.
175
118
  source = Contrast::CONFIG.sources.get(new_effective_value.canonical_name)
176
119
  new_effective_value.assign_filename(source)
177
- new_source = if source.include?(Contrast::Config::LocalSourceValue::YAML_EXT) ||
178
- source.include?(Contrast::Config::LocalSourceValue::YML_EXT)
179
-
120
+ new_source = if Contrast::CONFIG.sources.configuration_file_source?(new_effective_value.canonical_name)
180
121
  Contrast::Components::Config::Sources::APP_CONFIGURATION_FILE
181
- else
182
- Contrast::Components::Config::Sources::DEFAULT_VALUE
183
122
  end
184
- new_effective_value.source = new_source
123
+ new_effective_value.source = new_source || source
185
124
  new_effective_value
186
125
  end
187
126
 
@@ -50,7 +50,7 @@ module Contrast
50
50
  #
51
51
  # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
52
52
  def to_effective_config effective_config
53
- add_effective_config_values(effective_config, CONFIG_VALUES, CANON_NAME, "#{ CONTRAST }.#{ CANON_NAME }")
53
+ add_effective_config_values(effective_config, CONFIG_VALUES, CANON_NAME)
54
54
  end
55
55
  end
56
56
  end
@@ -54,21 +54,9 @@ module Contrast
54
54
  # @param effective_config [Contrast::Config::Diagnostics::EffectiveConfig]
55
55
  def to_effective_config effective_config
56
56
  super
57
- add_single_effective_value(effective_config,
58
- 'type',
59
- Contrast::APP_CONTEXT.server_type,
60
- CANON_NAME,
61
- "#{ CONTRAST }.#{ CANON_NAME }")
62
- add_single_effective_value(effective_config,
63
- 'name',
64
- Contrast::APP_CONTEXT.server_name,
65
- CANON_NAME,
66
- "#{ CONTRAST }.#{ CANON_NAME }")
67
- add_single_effective_value(effective_config,
68
- 'path',
69
- Contrast::APP_CONTEXT.server_path,
70
- CANON_NAME,
71
- "#{ CONTRAST }.#{ CANON_NAME }")
57
+ add_single_effective_value(effective_config, 'type', Contrast::APP_CONTEXT.server_type, CANON_NAME)
58
+ add_single_effective_value(effective_config, 'name', Contrast::APP_CONTEXT.server_name, CANON_NAME)
59
+ add_single_effective_value(effective_config, 'path', Contrast::APP_CONTEXT.server_path, CANON_NAME)
72
60
  end
73
61
  end
74
62
  end
@@ -63,6 +63,7 @@ module Contrast
63
63
  CONFIG_BASE_PATHS = %w[./ config/ /etc/contrast/ruby/ /etc/contrast/ /etc/].cs__freeze
64
64
  KEYS_TO_REDACT = %i[api_key url service_key user_name].cs__freeze
65
65
  REDACTED = '**REDACTED**'
66
+ EFFECTIVE_REDACTED = '****'
66
67
 
67
68
  DEPRECATED_PROPERTIES = %w[
68
69
  CONTRAST__AGENT__SERVICE__ENABLE CONTRAST__AGENT__SERVICE__LOGGER__LEVEL
@@ -146,8 +147,10 @@ module Contrast
146
147
 
147
148
  paths = []
148
149
  # Environment paths takes precedence here. Look first through them.
149
- paths << ENV['CONTRAST_CONFIG_PATH'] if ENV['CONTRAST_CONFIG_PATH']
150
- paths << ENV['CONTRAST_SECURITY_CONFIG'] if ENV['CONTRAST_SECURITY_CONFIG']
150
+ config_path = ENV.fetch('CONTRAST_CONFIG_PATH', nil)
151
+ security_path = ENV.fetch('CONTRAST_SECURITY_CONFIG', nil)
152
+ paths << config_path if config_path
153
+ paths << security_path if security_path
151
154
 
152
155
  extensions.each do |ext|
153
156
  places = CONFIG_BASE_PATHS.product(["#{ basename }.#{ ext }"])
@@ -29,6 +29,7 @@ module Contrast
29
29
  ].cs__freeze
30
30
 
31
31
  def initialize
32
+ @_frameworks = []
32
33
  return if Contrast::AGENT.disabled? || Contrast::Utils::JobServersRunning.job_servers_running?
33
34
 
34
35
  @_frameworks = SUPPORTED_FRAMEWORKS.map do |framework_klass|
@@ -90,9 +91,7 @@ module Contrast
90
91
  # this particular Request
91
92
  # @return [::Rack::Request] either a rack request or subclass thereof.
92
93
  def retrieve_request env
93
- if Contrast::Utils::DuckUtils.empty_duck?(@_frameworks)
94
- return Contrast::Framework::Rack::Support.retrieve_request(env)
95
- end
94
+ return Contrast::Framework::Rack::Support.retrieve_request(env) if @_frameworks.empty?
96
95
 
97
96
  framework = @_frameworks[0]
98
97
 
@@ -115,6 +114,8 @@ module Contrast
115
114
  # @return [Boolean] true if at least one framework is streaming the response; false if none are streaming
116
115
  def streaming? env
117
116
  result = false
117
+ return result if @_frameworks.empty?
118
+
118
119
  @_frameworks.each do |framework|
119
120
  result = framework.streaming?(env)
120
121
  break if result
@@ -29,7 +29,7 @@ module Contrast
29
29
  # @param method_name [Symbol] the method to call on each FrameworkSupport class
30
30
  # @return [Array]
31
31
  def data_for_all_frameworks method_name
32
- @_frameworks.flat_map { |framework| framework.send(method_name) }.
32
+ @_frameworks&.flat_map { |framework| framework.send(method_name) }&.
33
33
  compact
34
34
  end
35
35
 
@@ -39,6 +39,8 @@ module Contrast
39
39
  # @return [Object] - Determined by method to be invoked
40
40
  def first_framework_result method_name, default_value
41
41
  result = nil
42
+ return default_value if @_frameworks.empty?
43
+
42
44
  @_frameworks.each do |framework|
43
45
  result = framework.send(method_name)
44
46
  break if result
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'contrast/framework/base_support'
5
5
  require 'contrast/framework/rack/patch/support'
6
+ require 'contrast/utils/duck_utils'
6
7
 
7
8
  module Contrast
8
9
  module Framework
@@ -10,6 +11,9 @@ module Contrast
10
11
  # Used when Rack is present to define framework specific behavior. For
11
12
  # now, the only part of this implemented is the Patch Support.
12
13
  module Support
14
+ RACK_REQUEST_PATH = 'REQUEST_PATH'
15
+ RACK_SERVER_NAME = 'SERVER_NAME'
16
+
13
17
  extend Contrast::Framework::BaseSupport
14
18
  extend Contrast::Framework::Rack::Patch::Support
15
19
  class << self
@@ -74,8 +78,13 @@ module Contrast
74
78
  def current_route_coverage request, _controller = nil, full_route = nil
75
79
  method = request.env[::Rack::REQUEST_METHOD] # GET, PUT, POST, etc...
76
80
 
77
- full_route ||= request.env[::Rack::PATH_INFO]
78
- return unless full_route && method
81
+ full_route ||= request.env.fetch(::Rack::PATH_INFO, nil)
82
+ full_route = request.env.fetch(RACK_REQUEST_PATH, nil) if Contrast::Utils::DuckUtils.empty_duck?(full_route)
83
+ return unless method
84
+
85
+ # If we are here and have method but the route is "" we might be expecting the home page.
86
+ full_route = '/' if Contrast::Utils::DuckUtils.empty_duck?(full_route) &&
87
+ request.env.fetch(RACK_SERVER_NAME, nil)
79
88
 
80
89
  route_coverage = Contrast::Agent::Reporting::RouteCoverage.new
81
90
  # We might not have controller, or even if there is defined one, it could not bare the name of the
@@ -12,7 +12,7 @@ module Contrast
12
12
  # Method utility used by Contrast::Logger::log
13
13
  module LogUtils
14
14
  DEFAULT_NAME = 'contrast.log'
15
- DEFAULT_LEVEL = ::Ougai::Logging::Severity::INFO
15
+ DEFAULT_LEVEL = 'INFO'
16
16
  VALID_LEVELS = ::Ougai::Logging::Severity::SEV_LABEL
17
17
  STDOUT_STR = 'STDOUT'
18
18
  STDERR_STR = 'STDERR'
@@ -12,7 +12,7 @@ module Contrast
12
12
  NUM_PATTERN = %r{/\d+/}.cs__freeze
13
13
  END_PATTERN = %r{/\d+$}.cs__freeze
14
14
  STATIC_SUFFIXES = /\.(?:js|css|jpeg|jpg|gif|png|ico|woff|svg|pdf|eot|ttf|jar)$/i.cs__freeze
15
- UUID_PATTERN = Regexp.new('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}').cs__freeze # rubocop:disable Metrics/LineLength
15
+ UUID_PATTERN = Regexp.new('[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}').cs__freeze # rubocop:disable Layout/LineLength
16
16
  # Regular expression to match any type of hash pattern that is 16 bytes like uuid with no
17
17
  # slashes, md5, sha1, sha256, etc
18
18
  HASH_PATTERN = Regexp.new('([a-fA-F0-9]{2}){16,}').cs__freeze
@@ -29,7 +29,7 @@ module Contrast
29
29
  #
30
30
  # @return[String]
31
31
  def self.time_now
32
- Time.now.utc.iso8601(7)
32
+ Time.now.utc.iso8601(2)
33
33
  end
34
34
 
35
35
  # Converts time given in ms format form TS to HttpDate.
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: 7.3.2
4
+ version: 7.4.0
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-08-09 00:00:00.000000000 Z
16
+ date: 2023-09-12 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: bundler
@@ -1058,6 +1058,7 @@ files:
1058
1058
  - lib/contrast/agent/protect/rule/xss/xss.rb
1059
1059
  - lib/contrast/agent/protect/rule/xxe/entity_wrapper.rb
1060
1060
  - lib/contrast/agent/protect/rule/xxe/xxe.rb
1061
+ - lib/contrast/agent/protect/state.rb
1061
1062
  - lib/contrast/agent/reactions/disable_reaction.rb
1062
1063
  - lib/contrast/agent/reporting/attack_result/attack_result.rb
1063
1064
  - lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb
@@ -1253,6 +1254,7 @@ files:
1253
1254
  - lib/contrast/config/diagnostics/effective_config_value.rb
1254
1255
  - lib/contrast/config/diagnostics/environment_variables.rb
1255
1256
  - lib/contrast/config/diagnostics/monitor.rb
1257
+ - lib/contrast/config/diagnostics/singleton_tools.rb
1256
1258
  - lib/contrast/config/diagnostics/source_config_value.rb
1257
1259
  - lib/contrast/config/diagnostics/tools.rb
1258
1260
  - lib/contrast/config/diagnostics/user_configuration_file.rb