contrast-agent 4.9.0 → 4.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rspec_parallel +6 -0
  4. data/ext/cs__common/cs__common.c +19 -7
  5. data/ext/cs__common/cs__common.h +4 -2
  6. data/ext/cs__contrast_patch/cs__contrast_patch.c +32 -11
  7. data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -4
  8. data/lib/contrast/agent/assess/contrast_event.rb +1 -2
  9. data/lib/contrast/agent/assess/contrast_object.rb +1 -4
  10. data/lib/contrast/agent/assess/finalizers/hash.rb +0 -1
  11. data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
  12. data/lib/contrast/agent/assess/policy/patcher.rb +0 -1
  13. data/lib/contrast/agent/assess/policy/policy_scanner.rb +0 -2
  14. data/lib/contrast/agent/assess/policy/preshift.rb +29 -12
  15. data/lib/contrast/agent/assess/policy/propagation_method.rb +100 -57
  16. data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -2
  17. data/lib/contrast/agent/assess/policy/propagator/match_data.rb +31 -11
  18. data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -9
  19. data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -2
  20. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +1 -0
  21. data/lib/contrast/agent/assess/policy/rewriter_patch.rb +0 -1
  22. data/lib/contrast/agent/assess/policy/source_method.rb +13 -17
  23. data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -1
  24. data/lib/contrast/agent/assess/policy/trigger_method.rb +60 -85
  25. data/lib/contrast/agent/assess/policy/trigger_node.rb +52 -19
  26. data/lib/contrast/agent/assess/property/evented.rb +2 -1
  27. data/lib/contrast/agent/assess/property/tagged.rb +34 -25
  28. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +0 -1
  29. data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
  30. data/lib/contrast/agent/disable_reaction.rb +1 -1
  31. data/lib/contrast/agent/exclusion_matcher.rb +0 -4
  32. data/lib/contrast/agent/inventory/database_config.rb +117 -0
  33. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +6 -5
  34. data/lib/contrast/agent/inventory/policy/datastores.rb +2 -2
  35. data/lib/contrast/agent/middleware.rb +1 -0
  36. data/lib/contrast/agent/patching/policy/after_load_patch.rb +3 -0
  37. data/lib/contrast/agent/patching/policy/after_load_patcher.rb +17 -12
  38. data/lib/contrast/agent/patching/policy/method_policy.rb +54 -9
  39. data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
  40. data/lib/contrast/agent/patching/policy/patch.rb +17 -6
  41. data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
  42. data/lib/contrast/agent/patching/policy/patcher.rb +9 -9
  43. data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
  44. data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
  45. data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
  46. data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
  47. data/lib/contrast/agent/reaction_processor.rb +1 -1
  48. data/lib/contrast/agent/request.rb +9 -4
  49. data/lib/contrast/agent/request_context.rb +51 -33
  50. data/lib/contrast/agent/rule_set.rb +2 -4
  51. data/lib/contrast/agent/scope.rb +32 -20
  52. data/lib/contrast/agent/static_analysis.rb +1 -1
  53. data/lib/contrast/agent/tracepoint_hook.rb +16 -3
  54. data/lib/contrast/agent/version.rb +1 -1
  55. data/lib/contrast/agent.rb +0 -1
  56. data/lib/contrast/api/communication/messaging_queue.rb +12 -6
  57. data/lib/contrast/api/communication/service_lifecycle.rb +4 -1
  58. data/lib/contrast/api/communication/socket_client.rb +4 -4
  59. data/lib/contrast/api/decorators/agent_startup.rb +4 -4
  60. data/lib/contrast/api/decorators/application_startup.rb +6 -5
  61. data/lib/contrast/api/decorators/route_coverage.rb +24 -1
  62. data/lib/contrast/components/agent.rb +5 -2
  63. data/lib/contrast/components/assess.rb +13 -3
  64. data/lib/contrast/components/base.rb +2 -2
  65. data/lib/contrast/components/config.rb +1 -0
  66. data/lib/contrast/components/contrast_service.rb +4 -2
  67. data/lib/contrast/components/logger.rb +13 -8
  68. data/lib/contrast/components/scope.rb +9 -28
  69. data/lib/contrast/config/assess_configuration.rb +1 -0
  70. data/lib/contrast/config/base_configuration.rb +14 -6
  71. data/lib/contrast/configuration.rb +19 -15
  72. data/lib/contrast/extension/assess/array.rb +1 -11
  73. data/lib/contrast/extension/assess/eval_trigger.rb +0 -20
  74. data/lib/contrast/extension/assess/fiber.rb +0 -11
  75. data/lib/contrast/extension/assess/hash.rb +0 -10
  76. data/lib/contrast/extension/assess/kernel.rb +1 -10
  77. data/lib/contrast/extension/assess/marshal.rb +3 -11
  78. data/lib/contrast/extension/assess/regexp.rb +0 -11
  79. data/lib/contrast/extension/assess/string.rb +1 -26
  80. data/lib/contrast/extension/extension.rb +61 -0
  81. data/lib/contrast/framework/grape/support.rb +174 -0
  82. data/lib/contrast/framework/manager.rb +42 -6
  83. data/lib/contrast/framework/rack/support.rb +1 -1
  84. data/lib/contrast/framework/rails/patch/assess_configuration.rb +0 -1
  85. data/lib/contrast/framework/rails/patch/support.rb +6 -3
  86. data/lib/contrast/framework/rails/railtie.rb +1 -1
  87. data/lib/contrast/framework/rails/rewrite/active_record_named.rb +1 -0
  88. data/lib/contrast/framework/rails/support.rb +60 -13
  89. data/lib/contrast/framework/sinatra/support.rb +1 -1
  90. data/lib/contrast/logger/log.rb +89 -15
  91. data/lib/contrast/tasks/config.rb +0 -1
  92. data/lib/contrast/utils/class_util.rb +58 -44
  93. data/lib/contrast/utils/io_util.rb +43 -35
  94. data/lib/contrast/utils/lru_cache.rb +45 -0
  95. data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
  96. data/lib/contrast/utils/tag_util.rb +2 -1
  97. data/lib/contrast.rb +1 -1
  98. data/resources/assess/policy.json +208 -7
  99. data/resources/deadzone/policy.json +91 -0
  100. data/ruby-agent.gemspec +10 -2
  101. data/service_executables/VERSION +1 -1
  102. data/service_executables/linux/contrast-service +0 -0
  103. data/service_executables/mac/contrast-service +0 -0
  104. metadata +74 -26
  105. data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
  106. data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
  107. data/ext/cs__protect_kernel/extconf.rb +0 -5
  108. data/lib/contrast/extension/protect/kernel.rb +0 -39
  109. data/lib/contrast/utils/inventory_util.rb +0 -113
@@ -13,6 +13,75 @@ require 'contrast/logger/time'
13
13
  require 'contrast/components/config'
14
14
 
15
15
  module Contrast
16
+ # This module allows us to dynamically weave timing into our code, so that only when the time is actually needed do
17
+ # we pay the penalty for that timing block
18
+ module TraceTiming
19
+ def methods_to_time
20
+ @_methods_to_time ||= []
21
+ end
22
+
23
+ # Store info about methods for later patching.
24
+ METHOD_INFO = Struct.new(:clazz, :method_name, :custom_msg, :aliased)
25
+
26
+ # Add a method to the list of methods to be trace timed if logger set to TRACE. Enables trace timing after if
27
+ # logger set to TRACE.
28
+ #
29
+ # @params: clazz [Class] the class of the method to time.
30
+ # @params: method [Symbol] the method to time.
31
+ # @params: method [String] optional custom logging message.
32
+ def add_method_to_trace_timing clazz, method, msg = nil
33
+ methods_to_time.append(METHOD_INFO.new(clazz, method, msg, false))
34
+ enable_trace_timing if logger.level == ::Ougai::Logging::TRACE
35
+ end
36
+
37
+ # Add a method to the list of methods to be trace timed if logger set to TRACE. Enables trace timing after if
38
+ # logger set to TRACE.
39
+ #
40
+ # @params: method_spec [METHOD_INFO] specs about the method to be timed.
41
+ # @params: class_method [Boolean] whether this is or isn't a class/module method.
42
+ def trace_time_class_method meth_spec, class_method
43
+ untimed_func_symbol = "untimed_#{ meth_spec.method_name }".to_sym
44
+ send_to = class_method ? meth_spec.clazz.cs__singleton_class : meth_spec.clazz
45
+ meth_spec.clazz.class_eval do
46
+ include Contrast::Components::Logger::InstanceMethods
47
+ extend Contrast::Components::Logger::InstanceMethods
48
+
49
+ send_to.send(:alias_method, untimed_func_symbol, meth_spec.method_name)
50
+ meth_spec.aliased = true
51
+
52
+ log_message = "Elapsed time for #{ meth_spec.method_name }."
53
+ log_message = meth_spec.custom_message if meth_spec.custom_msg
54
+
55
+ send_to.send(:define_method, meth_spec.method_name) do |*args, **kwargs, &block| # rubocop:disable Performance/Kernel/DefineMethod
56
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
57
+ rv = if kwargs.empty?
58
+ send(untimed_func_symbol, *args, &block)
59
+ else
60
+ send(untimed_func_symbol, *args, **kwargs, &block)
61
+ end
62
+ delta = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
63
+ logger.trace(log_message, elapsed: delta * 1000)
64
+ rv
65
+ end
66
+ end
67
+ end
68
+
69
+ # Enable trace timing of methods specified in @_methods_to_time via aliasing.
70
+ def enable_trace_timing
71
+ methods_to_time.each do |meth_spec|
72
+ next if meth_spec.aliased
73
+
74
+ is_class_method = meth_spec.clazz.singleton_methods(false).include?(meth_spec.method_name)
75
+ trace_time_class_method meth_spec, is_class_method
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ module Contrast
82
+ # Used as a wrapper around our logging. The module option specifically adds in a new method for error that raises the
83
+ # logged exception, used in testing so that we can see if anything unexpected happens without it being swallowed
84
+ # while still providing safe options for customers.
16
85
  module Logger
17
86
  # For development set following env var to raise logged exceptions instead of just logging.
18
87
  if ENV['CONTRAST__AGENT__RUBY_MORE_COWBELL']
@@ -20,22 +89,22 @@ module Contrast
20
89
  alias_method :cs__error, :error
21
90
  alias_method :cs__warn, :warn
22
91
 
23
- def error msg, exc, **kwargs
24
- cs__error(msg, exc, **kwargs)
25
- raise exc if exc && exc.cs__class < Exception
26
- end
27
-
28
- def warn msg, exc, **kwargs
29
- cs__warn(msg, exc, **kwargs)
30
- raise exc if exc && exc.cs__class < Exception
92
+ def error *args, **kwargs
93
+ if kwargs.empty?
94
+ cs__error(*args)
95
+ else
96
+ cs__error(*args, **kwargs)
97
+ end
98
+ args.each { |arg| raise arg if arg && arg.cs__class < Exception }
31
99
  end
32
100
  end
33
101
  end
34
102
 
35
- # This class functions to serve as a wrapper around our logging, as we need
36
- # to be able to dynamically update level based on updates to TeamServer.
103
+ # This class functions to serve as a wrapper around our logging, as we need to be able to dynamically update
104
+ # level based on updates to TeamServer.
37
105
  class Log
38
106
  include Singleton
107
+ include ::Contrast::TraceTiming
39
108
 
40
109
  DEFAULT_NAME = 'contrast.log'
41
110
  DEFAULT_LEVEL = ::Ougai::Logging::Severity::INFO
@@ -49,8 +118,8 @@ module Contrast
49
118
  update
50
119
  end
51
120
 
52
- # Given new settings from TeamServer, update our logging to use the new
53
- # file and level, assuming they weren't set by local configuration.
121
+ # Given new settings from TeamServer, update our logging to use the new file and level, assuming they weren't
122
+ # set by local configuration.
54
123
  #
55
124
  # @param log_file [String] the file to which to log, as provided by TeamServer settings
56
125
  # @param log_level [String] the level at which to log, as provided by TeamServer settings
@@ -67,6 +136,8 @@ module Contrast
67
136
  @previous_path = current_path
68
137
  @previous_level = current_level_const
69
138
 
139
+ enable_trace_timing if current_level_const == ::Ougai::Logging::TRACE
140
+
70
141
  @_logger = build(path: current_path, level_const: current_level_const)
71
142
  # If we're logging to a new path, then let's start it w/ our helpful
72
143
  # data gathering messages
@@ -76,6 +147,9 @@ module Contrast
76
147
  logger.error('Unable to process update to LoggerManager.', e)
77
148
  else
78
149
  puts 'Unable to process update to LoggerManager.'
150
+ raise e if ENV['CONTRAST__AGENT__RUBY_MORE_COWBELL']
151
+
152
+ puts e.message
79
153
  puts e.backtrace.join("\n")
80
154
  end
81
155
  end
@@ -158,7 +232,8 @@ module Contrast
158
232
  # @return [::Ougai::Logging::Severity] the level at which to log
159
233
  def find_valid_level log_level
160
234
  config = ::Contrast::CONFIG.root.agent.logger
161
- config_level = config.level&.length&.positive? ? config.level : nil
235
+ config_level = config&.level&.length&.positive? ? config.level : nil
236
+
162
237
  valid_level(config_level || log_level)
163
238
  end
164
239
 
@@ -174,8 +249,7 @@ module Contrast
174
249
  DEFAULT_LEVEL
175
250
  end
176
251
 
177
- # Log that the Agent log has changed and include some default information
178
- # at the start of the log.
252
+ # Log that the Agent log has changed and include some default information at the start of the log.
179
253
  def log_update
180
254
  logger.debug('Initialized new contrast agent logger')
181
255
  logger.debug_with_time('middleware: log environment') do
@@ -7,7 +7,6 @@ module Contrast
7
7
  # A Rake task to generate a contrast_security.yaml file with some basic settings
8
8
  module Config
9
9
  extend Rake::DSL
10
- include Contrast::Components::Interface
11
10
  DEFAULT_CONFIG = {
12
11
  'api' => {
13
12
  'url' => 'Enter your Contrast URL ex: https://app.contrastsecurity.com/Contrast',
@@ -3,21 +3,21 @@
3
3
 
4
4
  require 'contrast/extension/module'
5
5
  require 'contrast/utils/object_share'
6
+ require 'contrast/utils/lru_cache'
6
7
 
7
8
  module Contrast
8
9
  module Utils
9
10
  # Utility methods for exploring the complete space of Objects
10
11
  class ClassUtil
12
+ @lru_cache = LRUCache.new(300)
13
+ @string_cache = LRUCache.new(300)
11
14
  class << self
12
- # some classes have had things prepended to them, like Marshal in Rails
13
- # 5 and higher. Their ActiveSupport::MarshalWithAutoloading will break
14
- # our alias patching approach, as will any other prepend on something
15
- # that we touch. Prepend and Alias are inherently incompatible monkey
16
- # patching approaches. As such, we need to know if something has been
17
- # prepended to.
15
+ # some classes have had things prepended to them, like Marshal in Rails 5 and higher. Their
16
+ # ActiveSupport::MarshalWithAutoloading will break our alias patching approach, as will any other prepend on
17
+ # something that we touch. Prepend and Alias are inherently incompatible monkey patching approaches. As such,
18
+ # we need to know if something has been prepended to.
18
19
  #
19
- # @param mod [Module] the Module to check to see if it has had something
20
- # prepended
20
+ # @param mod [Module] the Module to check to see if it has had something prepended
21
21
  # @param ancestors [Array<Module>] the array of ancestors for the mod
22
22
  # @return [Boolean] if the mod has been prepended or not
23
23
  def prepended? mod, ancestors = nil
@@ -25,8 +25,13 @@ module Contrast
25
25
  ancestors[0] != mod
26
26
  end
27
27
 
28
- # return true if the given method is overwritten by one of the ancestors
29
- # in the ancestor change that comes before the given module
28
+ # return true if the given method is overwritten by one of the ancestors in the ancestor change that comes
29
+ # before the given module
30
+ #
31
+ # @param mod [Module] the Module to check to see if it has had something prepended
32
+ # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that holds the method we
33
+ # need to check
34
+ # @return [Boolean] if this method specifically was prepended
30
35
  def prepended_method? mod, method_policy
31
36
  target_module = determine_target_class mod, method_policy.instance_method
32
37
  ancestors = target_module.ancestors
@@ -41,44 +46,49 @@ module Contrast
41
46
  false
42
47
  end
43
48
 
44
- # Return a String representing the object invoking this method in the
45
- # form expected by our dataflow events.
49
+ # Return a String representing the object invoking this method in the form expected by our dataflow events.
50
+ # After implementing the LRU Cache, we firstly need to check if already had that object cached and if we have
51
+ # it - we can return it directly; otherwise we'll calculate and store the result before returning.
52
+ #
53
+ # TODO: RUBY-1327
54
+ # Once we move to 2.7+, we can combine the caches using ID b/c the memory location stops being the id
46
55
  #
47
56
  # @param object [Object, nil] the entity to convert to a String
48
57
  # @return [String] the human readable form of the String, as defined by
49
58
  # https://bitbucket.org/contrastsecurity/assess-specifications/src/master/vulnerability/capture-snapshot.md
50
59
  def to_contrast_string object
51
- # Only treat object like a string if it actually is a string
52
- # some subclasses of String override string methods we depend on
60
+ # Only treat object like a string if it actually is a string+ some subclasses of String override string
61
+ # methods we depend on
53
62
  if object.cs__class == String
54
- cached = to_cached_string(object)
55
- return cached if cached
56
-
57
- object.dup
58
- elsif object.nil?
59
- Contrast::Utils::ObjectShare::NIL_STRING
60
- elsif object.cs__is_a?(Symbol)
61
- ":#{ object }"
62
- elsif object.cs__is_a?(Module) || object.cs__is_a?(Class)
63
- "#{ object.cs__name }@#{ object.__id__ }"
64
- elsif object.cs__is_a?(Regexp)
65
- object.source
66
- elsif use_to_s?(object)
67
- object.to_s
63
+ return @string_cache[object] if @string_cache.key? object
64
+
65
+ @string_cache[object] = to_cached_string(object) || object.dup
68
66
  else
69
- "#{ object.cs__class.cs__name }@#{ object.__id__ }"
67
+ return @lru_cache[object.__id__] if @lru_cache.key? object.__id__
68
+
69
+ @lru_cache[object.__id__] = if object.nil?
70
+ Contrast::Utils::ObjectShare::NIL_STRING
71
+ elsif object.cs__is_a?(Symbol)
72
+ ":#{ object }"
73
+ elsif object.cs__is_a?(Module) || object.cs__is_a?(Class)
74
+ "#{ object.cs__name }@#{ object.__id__ }"
75
+ elsif object.cs__is_a?(Regexp)
76
+ object.source
77
+ elsif use_to_s?(object)
78
+ object.to_s
79
+ else
80
+ "#{ object.cs__class.cs__name }@#{ object.__id__ }"
81
+ end
70
82
  end
71
83
  end
72
84
 
73
- # The method const_defined? can cause autoload, which is bad for us.
74
- # The method autoload? doesn't traverse namespaces. This method lets us
75
- # provide a constant, as a String, and parse it to determine if it has
76
- # been truly truly defined, meaning it existed before this method was
77
- # invoked, not as a result of it.
85
+ # The method const_defined? can cause autoload, which is bad for us. The method autoload? doesn't traverse
86
+ # namespaces. This method lets us provide a constant, as a String, and parse it to determine if it has been
87
+ # truly truly defined, meaning it existed before this method was invoked, not as a result of it.
78
88
  #
79
- # This is required to handle a bug in Ruby prior to 2.7.0. When we drop
80
- # support for 2.6.X, we should remove this code.
81
- # https://bugs.ruby-lang.org/issues/10741
89
+ # TODO: RUBY-1326
90
+ # This is required to handle a bug in Ruby prior to 2.7.0. When we drop support for 2.6.X, we should remove
91
+ # this code. https://bugs.ruby-lang.org/issues/10741
82
92
  # @param name [String] the name of the constant to look up
83
93
  # @return [Boolean]
84
94
  def truly_defined? name
@@ -101,7 +111,8 @@ module Contrast
101
111
  private
102
112
 
103
113
  # Some objects have nice to_s that we can use to make them human readable. If they do, we should leverage them.
104
- # We used to do this by default, but this opened us up to danger, so we're instead using an allow list approach.
114
+ # We used to do this by default, but this opened us up to danger, so we're instead using an allow list
115
+ # approach.
105
116
  #
106
117
  # @param object [Object] something that may have a safe to_s method
107
118
  # @return [Boolean] if we should invoke to_s to represent the object
@@ -112,6 +123,11 @@ module Contrast
112
123
  false
113
124
  end
114
125
 
126
+ # Find the target class based on the instance, or module, provided. If a module, return it.
127
+ #
128
+ # @param mod [Module] the Module, or instance of a Module, that we need to check
129
+ # @param is_instance [Boolean] is the object provided an instance of a class, requiring lookup by class
130
+ # @return [Module]
115
131
  def determine_target_class mod, is_instance
116
132
  return mod if mod.singleton_class?
117
133
 
@@ -120,13 +136,11 @@ module Contrast
120
136
  mod
121
137
  end
122
138
 
123
- # If the String matches a common String in our ObjectShare, return that
124
- # rather that for use as the representation of the String rather than
125
- # forcing a duplication of the String.
139
+ # If the String matches a common String in our ObjectShare, return that rather that for use as the
140
+ # representation of the String rather than forcing a duplication of the String.
126
141
  #
127
- # @param string [String] some string of which we want a Contrast
128
- # representation.
129
- # @return [String,nil] the ObjectShare version of the String or nil
142
+ # @param string [String] some string of which we want a Contrast representation.
143
+ # @return [String, nil] the ObjectShare version of the String or nil
130
144
  def to_cached_string string
131
145
  return Contrast::Utils::ObjectShare::EMPTY_STRING if string.empty?
132
146
  return Contrast::Utils::ObjectShare::SLASH if string == Contrast::Utils::ObjectShare::SLASH
@@ -6,43 +6,51 @@ require 'contrast/components/logger'
6
6
  module Contrast
7
7
  module Utils
8
8
  # Util for information about an IO
9
- class IOUtil
9
+ module IOUtil
10
10
  extend Contrast::Components::Logger::InstanceMethods
11
11
 
12
- # We're only going to call rewind on things that we believe are safe to
13
- # call it on. This method white lists those cases and returns false in
14
- # all others.
15
- def self.should_rewind? potential_io
16
- return true if potential_io.is_a?(StringIO)
17
- return false unless io?(potential_io)
18
-
19
- should_rewind_io?(potential_io)
20
- rescue StandardError => e
21
- logger.debug('Encountered an issue determining if rewindable', e, module: potential_io.cs__class.cs__name)
22
- false
23
- end
24
-
25
- # IO cannot be used with streams such as pipes, ttys, and sockets.
26
- def self.should_rewind_io? io
27
- return false if io.tty?
28
-
29
- status = io.stat
30
- return false unless status
31
- return false if status.pipe?
32
- return false if status.socket?
33
-
34
- true
35
- end
36
-
37
- # A class is IO if it is a decedent or delegate of IO
38
- def self.io? object
39
- return true if object.is_a?(IO)
40
-
41
- # DelegateClass, which is a Delegator, defines __getobj__ as a way to
42
- # get the object that the class wraps around (delegates to)
43
- return false unless object.is_a?(Delegator)
44
-
45
- object.__getobj__.is_a?(IO)
12
+ class << self
13
+ # We're only going to call rewind on things that we believe are safe to
14
+ # call it on. This method white lists those cases and returns false in
15
+ # all others.
16
+ def should_rewind? potential_io
17
+ return true if potential_io.is_a?(StringIO)
18
+ return false unless io?(potential_io)
19
+
20
+ should_rewind_io?(potential_io)
21
+ rescue StandardError => e
22
+ logger.debug('Encountered an issue determining if rewindable', e, module: potential_io.cs__class.cs__name)
23
+ false
24
+ end
25
+
26
+ # A class is IO if it is a decedent or delegate of IO
27
+ def io? object
28
+ return true if object.is_a?(IO)
29
+
30
+ # DelegateClass, which is a Delegator, defines __getobj__ as a way to
31
+ # get the object that the class wraps around (delegates to)
32
+ return false unless object.is_a?(Delegator)
33
+
34
+ object.__getobj__.is_a?(IO)
35
+ end
36
+
37
+ private
38
+
39
+ # IO rewind cannot be used with streams such as pipes, ttys, and sockets or for ones which have been closed.
40
+ #
41
+ # @param io [IO] the input to check for the ability to rewind
42
+ # @return [Boolean] if the given IO can be rewound
43
+ def should_rewind_io? io
44
+ return false if io.closed?
45
+ return false if io.tty?
46
+
47
+ status = io.stat
48
+ return false unless status
49
+ return false if status.pipe?
50
+ return false if status.socket?
51
+
52
+ true
53
+ end
46
54
  end
47
55
  end
48
56
  end
@@ -0,0 +1,45 @@
1
+ # Copyright (c) 2021 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 Utils
6
+ # A LRU(Least Recently Used) Cache store.
7
+ class LRUCache
8
+ def initialize capacity = 500
9
+ raise StandardError 'Capacity must be bigger than 0' if capacity <= 0
10
+
11
+ @capacity = capacity
12
+ @cache = {}
13
+ end
14
+
15
+ def [] key
16
+ val = @cache.delete(key)
17
+ @cache[key] = val if val
18
+ val
19
+ end
20
+
21
+ def []= key, value
22
+ @cache.delete(key)
23
+ @cache[key] = value
24
+ @cache.shift if @cache.size > @capacity
25
+ value # rubocop:disable Lint/Void
26
+ end
27
+
28
+ def keys
29
+ @cache.keys
30
+ end
31
+
32
+ def key? key
33
+ @cache.key?(key)
34
+ end
35
+
36
+ def values
37
+ @cache.values
38
+ end
39
+
40
+ def clear
41
+ @cache.clear
42
+ end
43
+ end
44
+ end
45
+ end
@@ -36,36 +36,39 @@ module Contrast
36
36
  # the replace within the given node.
37
37
  def on_dstr node
38
38
  return if node.children.all? { |child_node| child_node.type == :str }
39
-
40
39
  new_content = +'('
41
- node.children.each_with_index do |child_node, index|
40
+ idx = 0
41
+ while idx < node.children.length
42
+ #node.children.each_with_index do |child_node, index|
43
+ child_node = node.children[idx]
42
44
  # A begin node looks like #{some_code} in ruby, we do a substring
43
45
  # from [2...-1] to get rid of the #{ & trailing }.
44
46
  if child_node.type == :begin
45
47
  code = child_node.
46
- location.
47
- expression.
48
- source_buffer.
49
- source[child_node.location.begin.begin_pos...child_node.location.end.end_pos]
48
+ location.
49
+ expression.
50
+ source_buffer.
51
+ source[child_node.location.begin.begin_pos...child_node.location.end.end_pos]
50
52
  code = code[2...-1]
51
53
  new_content += "((#{ code }).to_s)"
52
54
 
53
- # For interpolations that use class, instance, or global variables,
54
- # those are NOT within a begin block, but instead are a ivar, cvar,
55
- # or gvar node, not stripping of interpolation markers required.
55
+ # For interpolations that use class, instance, or global variables,
56
+ # those are NOT within a begin block, but instead are a ivar, cvar,
57
+ # or gvar node, not stripping of interpolation markers required.
56
58
  elsif VARIABLES.include?(child_node.type)
57
59
  variable = child_node.children.first
58
60
  new_content << "((#{ variable }).to_s)"
59
61
 
60
- # When interpolation includes strings before or after an
61
- # interpolation they are simple :str nodes, in order to preserve
62
- # escaping we need to do a dump of the string value.
62
+ # When interpolation includes strings before or after an
63
+ # interpolation they are simple :str nodes, in order to preserve
64
+ # escaping we need to do a dump of the string value.
63
65
  elsif child_node.type == :str
64
66
  literal_value = child_node.children.first
65
67
  literal_value = literal_value.dump[1...-1]
66
68
  new_content << "\"#{ literal_value }\""
67
69
  end
68
- new_content << ' + ' unless index == node.children.length - 1
70
+ new_content << ' + ' unless idx == node.children.length - 1
71
+ idx += 1
69
72
  end
70
73
  new_content << ')'
71
74
  if node.location.cs__respond_to?(:heredoc_body)
@@ -8,7 +8,8 @@ module Contrast
8
8
  class << self
9
9
  # Determine if the given array of tags is covered by the other
10
10
  #
11
- # @param remaining_ranges [Array<Contrast::Agent::Assess::Tag>] the tags left that haven't been covered by those given
11
+ # @param remaining_ranges [Array<Contrast::Agent::Assess::Tag>] the tags left that haven't been covered by
12
+ # those given
12
13
  # @param ranges Array<Contrast::Agent::Assess::Tag> the tags that are covering the first
13
14
  def covered? remaining_ranges, ranges
14
15
  return true unless remaining_ranges&.any?
data/lib/contrast.rb CHANGED
@@ -23,7 +23,7 @@ end
23
23
 
24
24
  if RUBY_VERSION >= '3.0.0'
25
25
  # This fixes Ruby 3.0 issues with Module#(some instance method) patching by preventing the prepending of
26
- # a JSON helper on protobuf load. String.instance_method(:+) is one of the most noticable.
26
+ # a JSON helper on protobuf load. String.instance_method(:+) is one of the most noticeable.
27
27
  # TODO: RUBY-1132 Remove this once Ruby 3 is fixed.
28
28
  # See bug here: https://bugs.ruby-lang.org/issues/17725
29
29
  class Class