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.
- checksums.yaml +4 -4
- data/.rspec +0 -1
- data/.rspec_parallel +6 -0
- data/ext/cs__common/cs__common.c +19 -7
- data/ext/cs__common/cs__common.h +4 -2
- data/ext/cs__contrast_patch/cs__contrast_patch.c +32 -11
- data/ext/cs__contrast_patch/cs__contrast_patch.h +5 -4
- data/lib/contrast/agent/assess/contrast_event.rb +1 -2
- data/lib/contrast/agent/assess/contrast_object.rb +1 -4
- data/lib/contrast/agent/assess/finalizers/hash.rb +0 -1
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +2 -0
- data/lib/contrast/agent/assess/policy/patcher.rb +0 -1
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +0 -2
- data/lib/contrast/agent/assess/policy/preshift.rb +29 -12
- data/lib/contrast/agent/assess/policy/propagation_method.rb +100 -57
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +2 -2
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +31 -11
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +4 -9
- data/lib/contrast/agent/assess/policy/propagator/split.rb +3 -2
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +1 -0
- data/lib/contrast/agent/assess/policy/rewriter_patch.rb +0 -1
- data/lib/contrast/agent/assess/policy/source_method.rb +13 -17
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +0 -1
- data/lib/contrast/agent/assess/policy/trigger_method.rb +60 -85
- data/lib/contrast/agent/assess/policy/trigger_node.rb +52 -19
- data/lib/contrast/agent/assess/property/evented.rb +2 -1
- data/lib/contrast/agent/assess/property/tagged.rb +34 -25
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +0 -1
- data/lib/contrast/agent/deadzone/policy/policy.rb +6 -0
- data/lib/contrast/agent/disable_reaction.rb +1 -1
- data/lib/contrast/agent/exclusion_matcher.rb +0 -4
- data/lib/contrast/agent/inventory/database_config.rb +117 -0
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +6 -5
- data/lib/contrast/agent/inventory/policy/datastores.rb +2 -2
- data/lib/contrast/agent/middleware.rb +1 -0
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +3 -0
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +17 -12
- data/lib/contrast/agent/patching/policy/method_policy.rb +54 -9
- data/lib/contrast/agent/patching/policy/module_policy.rb +2 -4
- data/lib/contrast/agent/patching/policy/patch.rb +17 -6
- data/lib/contrast/agent/patching/policy/patch_status.rb +3 -7
- data/lib/contrast/agent/patching/policy/patcher.rb +9 -9
- data/lib/contrast/agent/protect/policy/applies_no_sqli_rule.rb +1 -1
- data/lib/contrast/agent/protect/rule/no_sqli.rb +7 -53
- data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +137 -0
- data/lib/contrast/agent/protect/rule/sqli.rb +7 -70
- data/lib/contrast/agent/reaction_processor.rb +1 -1
- data/lib/contrast/agent/request.rb +9 -4
- data/lib/contrast/agent/request_context.rb +51 -33
- data/lib/contrast/agent/rule_set.rb +2 -4
- data/lib/contrast/agent/scope.rb +32 -20
- data/lib/contrast/agent/static_analysis.rb +1 -1
- data/lib/contrast/agent/tracepoint_hook.rb +16 -3
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +0 -1
- data/lib/contrast/api/communication/messaging_queue.rb +12 -6
- data/lib/contrast/api/communication/service_lifecycle.rb +4 -1
- data/lib/contrast/api/communication/socket_client.rb +4 -4
- data/lib/contrast/api/decorators/agent_startup.rb +4 -4
- data/lib/contrast/api/decorators/application_startup.rb +6 -5
- data/lib/contrast/api/decorators/route_coverage.rb +24 -1
- data/lib/contrast/components/agent.rb +5 -2
- data/lib/contrast/components/assess.rb +13 -3
- data/lib/contrast/components/base.rb +2 -2
- data/lib/contrast/components/config.rb +1 -0
- data/lib/contrast/components/contrast_service.rb +4 -2
- data/lib/contrast/components/logger.rb +13 -8
- data/lib/contrast/components/scope.rb +9 -28
- data/lib/contrast/config/assess_configuration.rb +1 -0
- data/lib/contrast/config/base_configuration.rb +14 -6
- data/lib/contrast/configuration.rb +19 -15
- data/lib/contrast/extension/assess/array.rb +1 -11
- data/lib/contrast/extension/assess/eval_trigger.rb +0 -20
- data/lib/contrast/extension/assess/fiber.rb +0 -11
- data/lib/contrast/extension/assess/hash.rb +0 -10
- data/lib/contrast/extension/assess/kernel.rb +1 -10
- data/lib/contrast/extension/assess/marshal.rb +3 -11
- data/lib/contrast/extension/assess/regexp.rb +0 -11
- data/lib/contrast/extension/assess/string.rb +1 -26
- data/lib/contrast/extension/extension.rb +61 -0
- data/lib/contrast/framework/grape/support.rb +174 -0
- data/lib/contrast/framework/manager.rb +42 -6
- data/lib/contrast/framework/rack/support.rb +1 -1
- data/lib/contrast/framework/rails/patch/assess_configuration.rb +0 -1
- data/lib/contrast/framework/rails/patch/support.rb +6 -3
- data/lib/contrast/framework/rails/railtie.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/active_record_named.rb +1 -0
- data/lib/contrast/framework/rails/support.rb +60 -13
- data/lib/contrast/framework/sinatra/support.rb +1 -1
- data/lib/contrast/logger/log.rb +89 -15
- data/lib/contrast/tasks/config.rb +0 -1
- data/lib/contrast/utils/class_util.rb +58 -44
- data/lib/contrast/utils/io_util.rb +43 -35
- data/lib/contrast/utils/lru_cache.rb +45 -0
- data/lib/contrast/utils/ruby_ast_rewriter.rb +16 -13
- data/lib/contrast/utils/tag_util.rb +2 -1
- data/lib/contrast.rb +1 -1
- data/resources/assess/policy.json +208 -7
- data/resources/deadzone/policy.json +91 -0
- data/ruby-agent.gemspec +10 -2
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +74 -26
- data/ext/cs__protect_kernel/cs__protect_kernel.c +0 -47
- data/ext/cs__protect_kernel/cs__protect_kernel.h +0 -12
- data/ext/cs__protect_kernel/extconf.rb +0 -5
- data/lib/contrast/extension/protect/kernel.rb +0 -39
- data/lib/contrast/utils/inventory_util.rb +0 -113
data/lib/contrast/logger/log.rb
CHANGED
@@ -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
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
14
|
-
#
|
15
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
55
|
-
|
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
|
-
|
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
|
-
#
|
75
|
-
#
|
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
|
-
#
|
80
|
-
# support for 2.6.X, we should remove
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
9
|
+
module IOUtil
|
10
10
|
extend Contrast::Components::Logger::InstanceMethods
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
#
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
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
|
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
|
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
|
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
|