contrast-agent 6.4.0 → 6.5.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/ext/cs__contrast_patch/cs__contrast_patch.c +14 -1
  3. data/lib/contrast/agent/assess/finalizers/hash.rb +1 -0
  4. data/lib/contrast/agent/assess/policy/propagation_method.rb +5 -1
  5. data/lib/contrast/agent/assess/policy/propagator/custom.rb +4 -0
  6. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +5 -0
  7. data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -0
  8. data/lib/contrast/agent/assess/policy/source_method.rb +5 -0
  9. data/lib/contrast/agent/assess/policy/trigger_method.rb +8 -2
  10. data/lib/contrast/agent/assess/tracker.rb +12 -0
  11. data/lib/contrast/agent/inventory/dependency_analysis.rb +2 -2
  12. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -1
  13. data/lib/contrast/agent/inventory/policy/datastores.rb +1 -1
  14. data/lib/contrast/agent/inventory/policy/policy.rb +1 -1
  15. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -3
  16. data/lib/contrast/agent/reporting/reporter_heartbeat.rb +1 -3
  17. data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +17 -21
  18. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +1 -1
  19. data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample_activity.rb +26 -3
  20. data/lib/contrast/agent/request_context.rb +8 -0
  21. data/lib/contrast/agent/service_heartbeat.rb +2 -3
  22. data/lib/contrast/agent/static_analysis.rb +1 -1
  23. data/lib/contrast/agent/version.rb +1 -1
  24. data/lib/contrast/agent/worker_thread.rb +10 -0
  25. data/lib/contrast/components/agent.rb +51 -13
  26. data/lib/contrast/components/assess.rb +16 -0
  27. data/lib/contrast/components/contrast_service.rb +1 -1
  28. data/lib/contrast/components/heap_dump.rb +51 -1
  29. data/lib/contrast/components/inventory.rb +19 -13
  30. data/lib/contrast/components/logger.rb +18 -0
  31. data/lib/contrast/config/assess_configuration.rb +28 -0
  32. data/lib/contrast/config/base_configuration.rb +8 -2
  33. data/lib/contrast/config/root_configuration.rb +11 -8
  34. data/lib/contrast/config/service_configuration.rb +4 -4
  35. data/lib/contrast/config.rb +0 -6
  36. data/lib/contrast/extension/object.rb +19 -0
  37. data/lib/contrast/framework/rails/support.rb +4 -1
  38. data/lib/contrast/logger/log.rb +2 -1
  39. data/lib/contrast/utils/assess/event_limit_utils.rb +96 -0
  40. data/lib/contrast/utils/assess/propagation_method_utils.rb +27 -7
  41. data/lib/contrast/utils/log_utils.rb +2 -2
  42. data/lib/contrast/utils/patching/policy/patch_utils.rb +1 -1
  43. data/lib/contrast.rb +4 -19
  44. data/resources/assess/policy.json +4 -12
  45. data/ruby-agent.gemspec +2 -0
  46. metadata +43 -17
  47. data/lib/contrast/config/agent_configuration.rb +0 -63
  48. data/lib/contrast/config/heap_dump_configuration.rb +0 -59
  49. data/lib/contrast/config/inventory_configuration.rb +0 -33
  50. data/lib/contrast/config/logger_configuration.rb +0 -26
@@ -3,6 +3,8 @@
3
3
 
4
4
  require 'rubygems/version'
5
5
  require 'contrast/agent/rule_set'
6
+ require 'contrast/components/logger'
7
+ require 'contrast/components/heap_dump'
6
8
 
7
9
  module Contrast
8
10
  module Components
@@ -13,9 +15,50 @@ module Contrast
13
15
  class Interface
14
16
  include Contrast::Components::ComponentBase
15
17
 
18
+ def initialize hsh = {}
19
+ return unless hsh
20
+
21
+ @_enable = hsh[:enable]
22
+ @_start_bundled_service = hsh[:start_bundled_service]
23
+ @_omit_body = hsh[:omit_body]
24
+ @_service = Contrast::Config::ServiceConfiguration.new(hsh[:service])
25
+ @_logger = Contrast::Components::Logger::Interface.new(hsh[:logger])
26
+ @_ruby = Contrast::Config::RubyConfiguration.new(hsh[:ruby])
27
+ @_heap_dump = Contrast::Components::HeapDump::Interface.new(hsh[:heap_dump])
28
+ end
29
+
30
+ # @return [Boolean, true]
31
+ def start_bundled_service?
32
+ @_start_bundled_service.nil? ? true : @_start_bundled_service
33
+ end
34
+
35
+ def service
36
+ return @_service unless @_service.nil?
37
+
38
+ @_service = Contrast::Config::ServiceConfiguration.new
39
+ end
40
+
41
+ def logger
42
+ return @_logger unless @_logger.nil?
43
+
44
+ @_logger = Contrast::Components::Logger::Interface.new
45
+ end
46
+
47
+ def ruby
48
+ return @_ruby unless @_ruby.nil?
49
+
50
+ @_ruby = Contrast::Config::RubyConfiguration.new
51
+ end
52
+
53
+ def heap_dump
54
+ return @_heap_dump unless @_heap_dump.nil?
55
+
56
+ @_heap_dump = Contrast::Components::HeapDump::Interface.new
57
+ end
58
+
16
59
  def enabled?
17
- @_enabled = !false?(::Contrast::CONFIG.root.enable) if @_enabled.nil?
18
- @_enabled
60
+ @_enable = !false?(::Contrast::CONFIG.root.enable) if @_enable.nil?
61
+ @_enable
19
62
  end
20
63
 
21
64
  def disabled?
@@ -23,11 +66,11 @@ module Contrast
23
66
  end
24
67
 
25
68
  def enable!
26
- @_enabled = true
69
+ @_enable = true
27
70
  end
28
71
 
29
72
  def disable!
30
- @_enabled = false
73
+ @_enable = false
31
74
  Contrast::Agent::TracePointHook.disable
32
75
  Contrast::Agent.thread_watcher&.shutdown!
33
76
  end
@@ -41,8 +84,7 @@ module Contrast
41
84
  end
42
85
 
43
86
  def patch_yield?
44
- @_patch_yield = !false?(::Contrast::CONFIG.root.agent.ruby.propagate_yield) if @_patch_yield.nil?
45
- @_patch_yield
87
+ !false?(ruby.propagate_yield)
46
88
  end
47
89
 
48
90
  def interpolation_enabled?
@@ -52,18 +94,14 @@ module Contrast
52
94
  end
53
95
 
54
96
  def omit_body?
55
- @_omit_body = true?(::Contrast::CONFIG.root.agent.omit_body) if @_omit_body.nil?
56
97
  @_omit_body
57
98
  end
58
99
 
59
100
  def exception_control
60
101
  @_exception_control ||= {
61
- enable: true?(::Contrast::CONFIG.root.agent.ruby.exceptions.capture),
62
- status:
63
- ::Contrast::CONFIG.root.agent.ruby.exceptions.override_status || 403,
64
- message:
65
- ::Contrast::CONFIG.root.agent.ruby.exceptions.override_message ||
66
- Contrast::Utils::ObjectShare::OVERRIDE_MESSAGE
102
+ enable: true?(ruby.exceptions.capture),
103
+ status: ruby.exceptions.override_status || 403,
104
+ message: ruby.exceptions.override_message || Contrast::Utils::ObjectShare::OVERRIDE_MESSAGE
67
105
  }
68
106
  end
69
107
 
@@ -118,6 +118,22 @@ module Contrast
118
118
  ::Contrast::SETTINGS.assess_state.session_id
119
119
  end
120
120
 
121
+ def max_source_events
122
+ ::Contrast::CONFIG.root.assess.max_context_source_events
123
+ end
124
+
125
+ def max_propagation_events
126
+ ::Contrast::CONFIG.root.assess.max_propagation_events
127
+ end
128
+
129
+ def time_limit_threshold
130
+ ::Contrast::CONFIG.root.assess.time_limit_threshold
131
+ end
132
+
133
+ def max_rule_reported
134
+ ::Contrast::CONFIG.root.assess.max_rule_reported
135
+ end
136
+
121
137
  private
122
138
 
123
139
  def forcibly_enabled?
@@ -29,7 +29,7 @@ module Contrast
29
29
 
30
30
  # Requirement says "must be true" but that
31
31
  # should be "must not be false" -- oops.
32
- @_use_bundled_service ||= !false?(::Contrast::CONFIG.root.agent.start_bundled_service) &&
32
+ @_use_bundled_service ||= !false?(::Contrast::CONFIG.root.agent.start_bundled_service?) &&
33
33
  # Either a valid host or a valid socket
34
34
  # Path validity is the service's problem
35
35
  (LOCALHOST.match?(host) || !!socket_path)
@@ -2,11 +2,61 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'contrast/components/base'
5
- require 'contrast/components/heap_dump'
5
+ require 'contrast/config/base_configuration'
6
6
 
7
7
  module Contrast
8
8
  module Components
9
9
  module HeapDump
10
+ # Interface used to build the HeapDump settings and component.
11
+ class Interface
12
+ include Contrast::Config::BaseConfiguration
13
+
14
+ DEFAULT_PATH = 'contrast_heap_dumps' # saved
15
+ DEFAULT_MS = 10_000
16
+ DEFAULT_COUNT = 5
17
+
18
+ def initialize hsh = {}
19
+ return unless hsh
20
+
21
+ @_enable = hsh[:enable]
22
+ @_path = hsh[:path]
23
+ @_delay_ms = hsh[:delay_ms]
24
+ @_window_ms = hsh[:window_ms]
25
+ @_count = hsh[:count]
26
+ @_clean = hsh[:clean]
27
+ end
28
+
29
+ # @return [Boolean, String] should dumps be taken
30
+ def enable
31
+ @_enable.nil? ? Contrast::Utils::ObjectShare::FALSE : @_enable
32
+ end
33
+
34
+ # @return [String, DEFAULT_PATH] dir to which dumps should be
35
+ def path
36
+ @_path ||= DEFAULT_PATH
37
+ end
38
+
39
+ # @return [Integer, DEFAULT_MS] time, in ms, after initialization
40
+ def delay_ms
41
+ @_delay_ms ||= DEFAULT_MS
42
+ end
43
+
44
+ # @return [Integer, DEFAULT_MS] ms between each dump
45
+ def window_ms
46
+ @_window_ms ||= DEFAULT_MS
47
+ end
48
+
49
+ # @return [Integer, DEFAULT_COUNT] number of dumps to take
50
+ def count
51
+ @_count ||= DEFAULT_COUNT
52
+ end
53
+
54
+ # @return [Boolean, String] remove temporary objects or not
55
+ def clean
56
+ @_clean.nil? ? Contrast::Utils::ObjectShare::FALSE : @_clean
57
+ end
58
+ end
59
+
10
60
  # A wrapper build around the Common Agent Configuration project to allow
11
61
  # for access of the values contained in its
12
62
  # parent_configuration_spec.yaml.
@@ -1,29 +1,35 @@
1
1
  # Copyright (c) 2022 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/base'
5
+
4
6
  module Contrast
5
7
  module Components
6
8
  module Inventory
7
- # A wrapper build around the Common Agent Configuration project to allow
8
- # for access of the values contained in its
9
- # parent_configuration_spec.yaml.
10
- # Specifically, this allows for querying the state of the Inventory
11
- # product.
9
+ # Interface component for Inventory settings used to store the values from
10
+ # settings file and assert state with check methods.
12
11
  class Interface
13
12
  include Contrast::Components::ComponentBase
14
13
 
15
- def enabled?
16
- @_enabled = !false?(::Contrast::CONFIG.root.inventory.enable) if @_enabled.nil?
17
- @_enabled
14
+ # @return [Array, nil] tags
15
+ attr_accessor :tags
16
+
17
+ def initialize hsh = {}
18
+ return unless hsh
19
+
20
+ @enable = !false?(hsh[:enable])
21
+ @analyze_libraries = !false?(hsh[:analyze_libraries])
22
+ @tags = hsh[:tags]
18
23
  end
19
24
 
20
- def analyze_libraries?
21
- @_analyze_libraries = !false?(::Contrast::CONFIG.root.inventory.analyze_libraries) if @_analyze_libraries.nil?
22
- @_analyze_libraries
25
+ # return [Boolean]
26
+ def enable
27
+ @enable.nil? ? true : @enable
23
28
  end
24
29
 
25
- def tags
26
- ::Contrast::CONFIG.root.inventory&.tags
30
+ # return [Boolean]
31
+ def analyze_libraries
32
+ @analyze_libraries.nil? ? true : @analyze_libraries
27
33
  end
28
34
  end
29
35
  end
@@ -28,8 +28,26 @@ module Contrast
28
28
  end
29
29
  end
30
30
 
31
+ # So This class here follows the update for the configuration
32
+ # and from know on ( if it's as we planned it to be) it will hold the
33
+ # instance methods and will initialize new instances for where they're needed
31
34
  class Interface
32
35
  include InstanceMethods
36
+
37
+ # @return [String, nil]
38
+ attr_accessor :path
39
+ # @return [String, nil]
40
+ attr_accessor :level
41
+ # @return [String, nil]
42
+ attr_accessor :progname
43
+
44
+ def initialize hsh = {}
45
+ return unless hsh
46
+
47
+ @path = hsh[:path]
48
+ @level = hsh[:level]
49
+ @progname = hsh[:progname]
50
+ end
33
51
  end
34
52
  end
35
53
  end
@@ -15,6 +15,10 @@ module Contrast
15
15
  attr_writer :enable_scan_response, :enable_dynamic_sources, :sampling, :rules, :stacktraces
16
16
 
17
17
  DEFAULT_STACKTRACES = 'ALL'
18
+ DEFAULT_MAX_SOURCE_EVENTS = 50_000
19
+ DEFAULT_MAX_PROPAGATION_EVENTS = 50_000
20
+ DEFAULT_MAX_RULE_REPORTED = 50_000
21
+ DEFAULT_MAX_RULE_TIME_THRESHOLD = 300_000
18
22
 
19
23
  def initialize hsh = {}
20
24
  return unless hsh
@@ -27,6 +31,10 @@ module Contrast
27
31
  @sampling = Contrast::Config::SamplingConfiguration.new(hsh[:sampling])
28
32
  @rules = Contrast::Config::AssessRulesConfiguration.new(hsh[:rules])
29
33
  @stacktraces = hsh[:stacktraces]
34
+ @max_context_source_events = hsh[:max_context_source_events]
35
+ @max_propagation_events = hsh[:max_propagation_events]
36
+ @max_rule_reported = hsh[:max_rule_reported]
37
+ @time_limit_threshold = hsh[:time_limit_threshold]
30
38
  end
31
39
 
32
40
  # @return [Boolean, true]
@@ -58,6 +66,26 @@ module Contrast
58
66
  def stacktraces
59
67
  @stacktraces ||= DEFAULT_STACKTRACES
60
68
  end
69
+
70
+ # @return [int] max number of context source events in single request
71
+ def max_context_source_events
72
+ @max_context_source_events ||= DEFAULT_MAX_SOURCE_EVENTS
73
+ end
74
+
75
+ # @return [int] max number of propagation events in single request
76
+ def max_propagation_events
77
+ @max_propagation_events ||= DEFAULT_MAX_PROPAGATION_EVENTS
78
+ end
79
+
80
+ # @return [int] max number of rules reported within time_limit_threshold
81
+ def max_rule_reported
82
+ @max_rule_reported ||= DEFAULT_MAX_RULE_REPORTED
83
+ end
84
+
85
+ # @return [int] max ms threshold for reporting rules
86
+ def time_limit_threshold
87
+ @time_limit_threshold ||= DEFAULT_MAX_RULE_TIME_THRESHOLD
88
+ end
61
89
  end
62
90
  end
63
91
  end
@@ -10,12 +10,18 @@ module Contrast
10
10
  # Configuration settings to usable Ruby classes.
11
11
  module BaseConfiguration
12
12
  extend Forwardable
13
+ AT_UNDERSCORE = '@_'
13
14
 
14
15
  def to_hash
15
16
  hsh = {}
16
17
  instance_variables.each do |iv|
17
- # strip the '@' to get the key
18
- key = iv.to_s[1..]
18
+ # strip the '@' of '@_' to get the key
19
+ string_iv = iv.to_s
20
+ key = if string_iv.include?(AT_UNDERSCORE)
21
+ string_iv[2..]
22
+ else
23
+ string_iv[1..]
24
+ end
19
25
  hsh[key] = send(key.to_sym)
20
26
  end
21
27
  hsh
@@ -1,6 +1,9 @@
1
1
  # Copyright (c) 2022 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/agent'
5
+ require 'contrast/components/inventory'
6
+
4
7
  module Contrast
5
8
  module Config
6
9
  # The base of the Common Configuration settings.
@@ -9,7 +12,7 @@ module Contrast
9
12
 
10
13
  # @return [Contrast::Config::ApiConfiguration]
11
14
  attr_writer :api
12
- # @return [Contrast::Config::AgentConfiguration]
15
+ # @return [Contrast::Components::Agent::Interface]
13
16
  attr_writer :agent
14
17
  # @return [Contrast::Config::ApplicationConfiguration]
15
18
  attr_writer :application
@@ -17,7 +20,7 @@ module Contrast
17
20
  attr_writer :server
18
21
  # @return [Contrast::Config::AssessConfiguration]
19
22
  attr_writer :assess
20
- # @return [Contrast::Config::InventoryConfiguration]
23
+ # @return [Contrast::Components::Inventory::Interface]
21
24
  attr_writer :inventory
22
25
  # @return [Contrast::Config::ProtectConfiguration]
23
26
  attr_writer :protect
@@ -32,11 +35,11 @@ module Contrast
32
35
 
33
36
  @api = Contrast::Config::ApiConfiguration.new(hsh[:api])
34
37
  @enable = hsh[:enable]
35
- @agent = Contrast::Config::AgentConfiguration.new(hsh[:agent])
38
+ @agent = Contrast::Components::Agent::Interface.new(hsh[:agent])
36
39
  @application = Contrast::Config::ApplicationConfiguration.new(hsh[:application])
37
40
  @server = Contrast::Config::ServerConfiguration.new(hsh[:server])
38
41
  @assess = Contrast::Config::AssessConfiguration.new(hsh[:assess])
39
- @inventory = Contrast::Config::InventoryConfiguration.new(hsh[:inventory])
42
+ @inventory = Contrast::Components::Inventory::Interface.new(hsh[:inventory])
40
43
  @protect = Contrast::Config::ProtectConfiguration.new(hsh[:protect])
41
44
  @service = Contrast::Config::ServiceConfiguration.new(hsh[:service])
42
45
  end
@@ -46,9 +49,9 @@ module Contrast
46
49
  @api ||= Contrast::Config::ApiConfiguration.new
47
50
  end
48
51
 
49
- # @return [Contrast::Config::AgentConfiguration]
52
+ # @return [Contrast::Components::Agent::Interface]
50
53
  def agent
51
- @agent ||= Contrast::Config::AgentConfiguration.new
54
+ @agent ||= Contrast::Components::Agent::Interface.new
52
55
  end
53
56
 
54
57
  # @return [Contrast::Config::ApplicationConfiguration]
@@ -66,9 +69,9 @@ module Contrast
66
69
  @assess ||= Contrast::Config::AssessConfiguration.new
67
70
  end
68
71
 
69
- # @return [Contrast::Config::InventoryConfiguration]
72
+ # @return [Contrast::Components::Inventory::Interface]
70
73
  def inventory
71
- @inventory ||= Contrast::Config::InventoryConfiguration.new
74
+ @inventory ||= Contrast::Components::Inventory::Interface.new
72
75
  end
73
76
 
74
77
  # @return [Contrast::Config::ProtectConfiguration]
@@ -1,7 +1,7 @@
1
1
  # Copyright (c) 2022 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/config/logger_configuration'
4
+ require 'contrast/components/logger'
5
5
 
6
6
  module Contrast
7
7
  module Config
@@ -31,13 +31,13 @@ module Contrast
31
31
  @host = hsh[:host]
32
32
  @port = hsh[:port]
33
33
  @socket = hsh[:socket]
34
- @logger = Contrast::Config::LoggerConfiguration.new(hsh[:logger])
34
+ @logger = Contrast::Components::Logger::Interface.new(hsh[:logger])
35
35
  @bypass = hsh[:bypass]
36
36
  end
37
37
 
38
- # @return [Contrast::Config::LoggerConfiguration]
38
+ # @return [Contrast::Components::Logger::Interface]
39
39
  def logger
40
- @logger ||= Contrast::Config::LoggerConfiguration.new
40
+ @logger ||= Contrast::Components::Logger::Interface.new
41
41
  end
42
42
 
43
43
  # @return [Boolean, false]
@@ -11,10 +11,6 @@ module Contrast
11
11
  end
12
12
 
13
13
  require 'contrast/config/base_configuration'
14
-
15
- require 'contrast/config/logger_configuration'
16
-
17
- require 'contrast/config/heap_dump_configuration'
18
14
  require 'contrast/config/service_configuration'
19
15
  require 'contrast/config/exception_configuration'
20
16
  require 'contrast/config/assess_rules_configuration'
@@ -24,10 +20,8 @@ require 'contrast/config/sampling_configuration'
24
20
 
25
21
  require 'contrast/config/ruby_configuration'
26
22
  require 'contrast/config/api_configuration'
27
- require 'contrast/config/agent_configuration'
28
23
  require 'contrast/config/application_configuration'
29
24
  require 'contrast/config/server_configuration'
30
25
  require 'contrast/config/assess_configuration'
31
- require 'contrast/config/inventory_configuration'
32
26
  require 'contrast/config/protect_configuration'
33
27
  require 'contrast/config/root_configuration'
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ # Some developers override various methods on Object, which can often involve
5
+ # changing expected method parity/behavior which in turn prevents us from being
6
+ # able to reliably use affected methods.
7
+ # We alias these method so that we always have access to them.
8
+ #
9
+ # Because we use these methods in constructing classes (e.g., calling #freeze
10
+ # on constants within class definitions) we do this aliasing ASAP.
11
+ class Object
12
+ alias_method :cs__class, :class
13
+ alias_method :cs__freeze, :freeze
14
+ alias_method :cs__frozen?, :frozen?
15
+ alias_method :cs__is_a?, :is_a?
16
+ alias_method :cs__method, :method
17
+ alias_method :cs__respond_to?, :respond_to?
18
+ alias_method :cs__singleton_class, :singleton_class
19
+ end
@@ -135,8 +135,11 @@ module Contrast
135
135
  # @return [bool] whether the router is an engine or not.
136
136
  def engine_route? route
137
137
  return false unless route&.app&.app
138
+ return false unless route.app.is_a?(::ActionDispatch::Routing::Mapper::Constraints) ||
139
+ route.app.is_a?(::ActionDispatch::Routing::RouteSet::Dispatcher)
138
140
 
139
- route.app.is_a?(::ActionDispatch::Routing::Mapper::Constraints) && route.app.app < ::Rails::Engine
141
+ clazz = route.app.app.is_a?(Class) ? route.app.app : route.app.app.cs__class
142
+ clazz < ::Rails::Engine
140
143
  end
141
144
 
142
145
  # Recursively get final route traversing engines as required. Because this can only be called once, we store
@@ -134,7 +134,8 @@ module Contrast
134
134
 
135
135
  enable_trace_timing if current_level_const == ::Ougai::Logging::TRACE
136
136
 
137
- @_logger = build(path: current_path, level_const: current_level_const)
137
+ progname = Contrast::CONFIG.root.agent.logger.progname
138
+ @_logger = build(path: current_path, level_const: current_level_const, progname: progname)
138
139
  # If we're logging to a new path, then let's start it w/ our helpful
139
140
  # data gathering messages
140
141
  log_update if path_change
@@ -0,0 +1,96 @@
1
+ # Copyright (c) 2022 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
2
+ # frozen_string_literal: true
3
+
4
+ require 'contrast/components/logger'
5
+
6
+ module Contrast
7
+ module Utils
8
+ module Assess
9
+ # EventLimitUtils is used to check and validate the number of source, propagation, or trigger events collected
10
+ # during the reporting time frame
11
+ module EventLimitUtils
12
+ include Contrast::Components::Logger::InstanceMethods
13
+ # Checks to see if the event limit for the policy type has been met or exceeded
14
+ # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] method to check for event limit
15
+ def event_limit? method_policy
16
+ return false unless (context = Contrast::Agent::REQUEST_TRACKER.current)
17
+
18
+ if method_policy.source_node
19
+ max = (::Contrast::ASSESS.max_source_events ||
20
+ Contrast::Config::AssessConfiguration::DEFAULT_MAX_SOURCE_EVENTS)
21
+ return at_limit?(method_policy, context.source_event_count, max)
22
+
23
+ end
24
+ if method_policy.propagation_node
25
+ max = (::Contrast::ASSESS.max_propagation_events ||
26
+ Contrast::Config::AssessConfiguration::DEFAULT_MAX_PROPAGATION_EVENTS)
27
+ return at_limit?(method_policy, context.propagation_event_count, max)
28
+ end
29
+
30
+ false # policy does not have limit
31
+ end
32
+
33
+ def event_limit_for_rule? rule_id
34
+ if Contrast::Utils::Timer.now_ms > threshold_time_limit
35
+ @_rule_counts = nil
36
+ @_threshold_time_limit = nil
37
+ threshold_time_limit
38
+ end
39
+ rule_counts[rule_id] += 1
40
+ # TODO: RUBY-1680 remove default
41
+ rule_counts[rule_id] >=
42
+ (::Contrast::ASSESS.max_rule_reported || Contrast::Config::AssessConfiguration::DEFAULT_MAX_RULE_REPORTED)
43
+ end
44
+
45
+ # Increments the event count for the type of event that is being tracked
46
+ #
47
+ # @param node [Contrast::Agent::Assess::Policy::PolicyNode] policy to increment
48
+ def increment_event_count node
49
+ return unless (context = Contrast::Agent::REQUEST_TRACKER.current)
50
+
51
+ context.source_event_count += 1 if node.cs__is_a?(Contrast::Agent::Assess::Policy::SourceNode)
52
+ context.propagation_event_count += 1 if node.cs__is_a?(Contrast::Agent::Assess::Policy::PropagationNode)
53
+ end
54
+
55
+ private
56
+
57
+ # helper method to check limit and log when necessary
58
+ def at_limit? method_policy, current_count, event_max
59
+ if current_count == event_max
60
+ logger.warn('Event Limit Reached:',
61
+ {
62
+ count: current_count,
63
+ max: event_max,
64
+ policy: method_policy.method_name,
65
+ node: method_policy
66
+ })
67
+ # increment to be over count for logging purposes
68
+ increment_event_count(method_policy)
69
+ return true
70
+ elsif current_count > event_max
71
+ # increment to be over count for logging purposes
72
+ increment_event_count(method_policy)
73
+ logger.warn('Event Limit Exceeded:',
74
+ {
75
+ count: current_count,
76
+ policy: method_policy.method_name,
77
+ node: method_policy
78
+ })
79
+ return true
80
+ end
81
+ false
82
+ end
83
+
84
+ def rule_counts
85
+ @_rule_counts ||= Hash.new { |h, k| h[k] = 0 }
86
+ end
87
+
88
+ # the time threshold for which to track rule counts resets when now >= threshold_time_limit
89
+ # @return [Integer]
90
+ def threshold_time_limit
91
+ @_threshold_time_limit ||= Contrast::Utils::Timer.now_ms + (::Contrast::ASSESS.time_limit_threshold || 0)
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -92,20 +92,24 @@ module Contrast
92
92
  # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
93
93
  # the invocation of the patched method.
94
94
  # @param target [Object] the thing to which to propagate
95
+ # @param propagation_data [Contrast::Agent::Assess::Events::EventData] this will hold the
96
+ # object [Object] the Object on which the method was invoked
97
+ # args [Array<Object>] the Arguments with which the method was invoked
95
98
  # @return [Boolean]
96
- def can_propagate? propagation_node, preshift, target
99
+ def can_propagate? propagation_node, preshift, target, propagation_data
97
100
  return false unless appropriate_target?(propagation_node, target)
98
101
  return true if Contrast::Utils::Assess::TrackingUtil.tracked?(target)
99
- if propagation_node.use_original_object?
100
- # return true since we don't have preshift while using the original object.
101
- return true
102
- end
103
- return false unless preshift
102
+ return false unless appropriate_source?(propagation_node, propagation_data, preshift)
104
103
 
105
104
  propagation_node.sources.each do |source|
106
105
  case source
107
106
  when Contrast::Utils::ObjectShare::OBJECT_KEY
108
- return true if Contrast::Utils::Assess::TrackingUtil.tracked?(preshift.object)
107
+ source_object = if propagation_node.use_original_object?
108
+ propagation_data.object
109
+ else
110
+ preshift.object
111
+ end
112
+ return true if Contrast::Utils::Assess::TrackingUtil.tracked?(source_object)
109
113
  else
110
114
  # has to be P, there's no ret source type (yet? ever?)
111
115
  return true if preshift.args && Contrast::Utils::Assess::TrackingUtil.tracked?(preshift.args[source])
@@ -129,6 +133,22 @@ module Contrast
129
133
 
130
134
  Contrast::Agent::Assess::Tracker.trackable?(target)
131
135
  end
136
+
137
+ # A source is appropriate if it is available for propagation
138
+ #
139
+ # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
140
+ # propagation event.
141
+ # @param propagation_data [Contrast::Agent::Assess::Events::EventData] this will hold the
142
+ # object [Object] the Object on which the method was invoked
143
+ # args [Array<Object>] the Arguments with which the method was invoked
144
+ # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
145
+ # the invocation of the patched method.
146
+ # @return [Boolean]
147
+ def appropriate_source? propagation_node, propagation_data, preshift
148
+ return true if preshift
149
+
150
+ propagation_node.use_original_object? && propagation_data&.object
151
+ end
132
152
  end
133
153
  end
134
154
  end