contrast-agent 4.5.0 → 4.6.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.
- checksums.yaml +4 -4
- data/lib/contrast/agent.rb +0 -2
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +22 -18
- data/lib/contrast/agent/assess/policy/propagator/trim.rb +65 -34
- data/lib/contrast/agent/deadzone/policy/policy.rb +6 -2
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +6 -6
- data/lib/contrast/agent/middleware.rb +30 -56
- data/lib/contrast/agent/patching/policy/patch.rb +4 -7
- data/lib/contrast/agent/patching/policy/policy.rb +13 -20
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/response_processor.rb +2 -2
- data/lib/contrast/components/agent.rb +4 -10
- data/lib/contrast/components/assess.rb +5 -10
- data/lib/contrast/components/protect.rb +8 -13
- data/lib/contrast/components/scope.rb +1 -0
- data/lib/contrast/components/settings.rb +24 -97
- data/lib/contrast/framework/manager.rb +1 -4
- data/ruby-agent.gemspec +3 -0
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +53 -12
- data/lib/contrast/common_agent_configuration.rb +0 -87
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9748bb3a9e7e6ce4722edc88dbb3bd8d3a59b11dd691f47ac29b3ee9e6a00ba
|
4
|
+
data.tar.gz: d5b5ff651f2914bff6da5cbd2fdcdc28a2dd1a2ea8ad4eced6c416cbab72b632
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e03ec0934d333e79f957515dfdc851861d395e9c768d09ffdfbf56b6e818d27361cffb2f010985f6da2182b1a4b070ef2291b86ccd9280fb117c4125611ae8f
|
7
|
+
data.tar.gz: bb60224de8c19b547f311aca8d78a2b99593e329696ab6acbaf59336316109be5e9419d02e6e4cd06d0714627c0942fea6d1c0872a6c69faff531838e1dbc359
|
data/lib/contrast/agent.rb
CHANGED
@@ -6,28 +6,35 @@ module Contrast
|
|
6
6
|
module Assess
|
7
7
|
module Policy
|
8
8
|
module Propagator
|
9
|
-
# Propagation that results in all the tags of the source being
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# tags. The target's preexisting tags are also updated by this
|
13
|
-
# removal.
|
9
|
+
# Propagation that results in all the tags of the source being applied to the totality of the target and then
|
10
|
+
# those sections which have been removed from the target are removed from the tags. The target's preexisting
|
11
|
+
# tags are also updated by this removal.
|
14
12
|
class Remove < Contrast::Agent::Assess::Policy::Propagator::Base
|
15
13
|
class << self
|
16
|
-
# For the source, append its tags to the target.
|
17
|
-
#
|
18
|
-
# Unlike additive propagation, this currently only supports one source
|
14
|
+
# For the source, append its tags to the target. Once the tag is applied, remove the section that was
|
15
|
+
# removed by the delete. Unlike additive propagation, this currently only supports one source.
|
19
16
|
def propagate propagation_node, preshift, target
|
20
17
|
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
21
18
|
|
22
19
|
source = find_source(propagation_node.sources[0], preshift)
|
23
20
|
properties.copy_from(source, target, 0, propagation_node.untags)
|
24
|
-
|
25
|
-
handle_removal(source_chars, target)
|
21
|
+
handle_removal(propagation_node, source, target)
|
26
22
|
end
|
27
23
|
|
28
|
-
def handle_removal
|
24
|
+
def handle_removal propagation_node, source, target
|
25
|
+
return unless source
|
29
26
|
return unless (properties = Contrast::Agent::Assess::Tracker.properties!(target))
|
30
27
|
|
28
|
+
source_string = source.is_a?(String) ? source : source.to_s
|
29
|
+
|
30
|
+
# If the lengths are the same, we should just copy the tags because nothing was removed, but a new
|
31
|
+
# instance could have been created. copy_from will handle the case where the source is the target.
|
32
|
+
if source_string.length == target.length
|
33
|
+
properties.copy_from(source, target, 0, propagation_node.untags)
|
34
|
+
return
|
35
|
+
end
|
36
|
+
|
37
|
+
source_chars = source_string.chars
|
31
38
|
source_idx = 0
|
32
39
|
|
33
40
|
target_chars = target.chars
|
@@ -36,10 +43,8 @@ module Contrast
|
|
36
43
|
remove_ranges = []
|
37
44
|
start = nil
|
38
45
|
|
39
|
-
# loop over the target, the result of the delete
|
40
|
-
#
|
41
|
-
# represents a section that was deleted. these sections
|
42
|
-
# need to have their tags updated
|
46
|
+
# loop over the target, the result of the delete every range of characters that it differs from the
|
47
|
+
# source represents a section that was deleted. these sections need to have their tags updated
|
43
48
|
target_len = target_chars.length
|
44
49
|
while target_idx < target_len
|
45
50
|
target_char = target_chars[target_idx]
|
@@ -56,9 +61,8 @@ module Contrast
|
|
56
61
|
source_idx += 1
|
57
62
|
end
|
58
63
|
|
59
|
-
# once we're done looping over the target, anything left
|
60
|
-
#
|
61
|
-
# applying to it need to be removed.
|
64
|
+
# once we're done looping over the target, anything left over is extra from the source that was
|
65
|
+
# deleted. tags applying to it need to be removed.
|
62
66
|
remove_ranges << (source_idx...source_chars.length) if source_idx != source_chars.length
|
63
67
|
|
64
68
|
# handle deleting the removed ranges
|
@@ -8,49 +8,30 @@ module Contrast
|
|
8
8
|
module Propagator
|
9
9
|
# This class is specifically for String#tr(_s) propagation
|
10
10
|
#
|
11
|
-
# Disclaimer: there may be a better way, but we're
|
12
|
-
#
|
13
|
-
# a 'get it right' state soon.
|
11
|
+
# Disclaimer: there may be a better way, but we're in a 'get it work' state. hopefully, we'll be in a 'get it
|
12
|
+
# right' state soon.
|
14
13
|
module Trim
|
15
14
|
class << self
|
16
|
-
|
15
|
+
|
16
|
+
# @param policy_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
|
17
|
+
# propagation event.
|
18
|
+
# @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
|
19
|
+
# the invocation of the patched method.
|
20
|
+
# @param ret [nil, String] the target to which to propagate.
|
21
|
+
# @return [nil, String] ret
|
22
|
+
def tr_tagger policy_node, preshift, ret, _block
|
17
23
|
return ret unless ret && !ret.empty?
|
18
24
|
return ret unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
|
19
25
|
|
20
|
-
|
21
|
-
|
22
|
-
properties.copy_from(source, ret)
|
23
|
-
replace_string = args[1]
|
24
|
-
source_chars = source.chars
|
25
|
-
# if the replace string is empty, then there's a bunch of deletes. this
|
26
|
-
# functions the same as the Removal propagation.
|
27
|
-
if replace_string == Contrast::Utils::ObjectShare::EMPTY_STRING
|
28
|
-
Contrast::Agent::Assess::Policy::Propagator::Remove.handle_removal(source_chars, ret)
|
29
|
-
else
|
30
|
-
remove_ranges = []
|
31
|
-
ret_chars = ret.chars
|
32
|
-
start = nil
|
33
|
-
source_chars.each_with_index do |char, idx|
|
34
|
-
if ret_chars[idx] == char
|
35
|
-
next unless start
|
36
|
-
|
37
|
-
remove_ranges << (start...idx)
|
38
|
-
start = nil
|
39
|
-
else
|
40
|
-
start ||= idx
|
41
|
-
end
|
42
|
-
end
|
43
|
-
# account for the last char being different
|
44
|
-
remove_ranges << (start...source_chars.length) if start
|
45
|
-
properties.delete_tags_at_ranges(remove_ranges, false)
|
46
|
-
end
|
26
|
+
properties.copy_from(preshift.object, ret)
|
27
|
+
handle_tr(policy_node, preshift, ret, properties)
|
47
28
|
|
48
29
|
properties.build_event(
|
49
|
-
|
30
|
+
policy_node,
|
50
31
|
ret,
|
51
|
-
|
32
|
+
preshift.object,
|
52
33
|
ret,
|
53
|
-
args,
|
34
|
+
preshift.args,
|
54
35
|
1)
|
55
36
|
ret
|
56
37
|
end
|
@@ -70,6 +51,56 @@ module Contrast
|
|
70
51
|
args)
|
71
52
|
ret
|
72
53
|
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# @param policy_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
|
58
|
+
# propagation event.
|
59
|
+
# @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
|
60
|
+
# the invocation of the patched method.
|
61
|
+
# @param ret [String] the target to which to propagate.
|
62
|
+
# @param properties [Contrast::Agent::Assess::Properties] the properties of the ret
|
63
|
+
def handle_tr policy_node, preshift, ret, properties
|
64
|
+
source = preshift.object
|
65
|
+
replace_string = preshift.args[1]
|
66
|
+
|
67
|
+
# if the replace string is empty, then there's a bunch of deletes. this functions the same as the
|
68
|
+
# Removal propagation.
|
69
|
+
if replace_string == Contrast::Utils::ObjectShare::EMPTY_STRING
|
70
|
+
Contrast::Agent::Assess::Policy::Propagator::Remove.handle_removal(policy_node, source, ret)
|
71
|
+
return
|
72
|
+
end
|
73
|
+
|
74
|
+
# Otherwise, we need to target each insertion point. Based on the spec for #tr & #tr_s, the find is
|
75
|
+
# treated as a regex range, excepting the `\` character, which we'll need to escape. This converts to
|
76
|
+
# that form, wrapping the input in `[]`.
|
77
|
+
find_string = preshift.args[0]
|
78
|
+
find_string += '\\' if find_string.end_with?('\\')
|
79
|
+
find_regexp = Regexp.new("[#{ find_string }]")
|
80
|
+
|
81
|
+
# Find the first instance to be replaced. If there isn't one, than nothing changed here.
|
82
|
+
idx = source.index(find_regexp)
|
83
|
+
return unless idx
|
84
|
+
|
85
|
+
# Iterate over each change and record where it happened. B/c this is a one to one replace, the index of
|
86
|
+
# the replacement is always one; however, there may be adjacent replacements which become a single
|
87
|
+
# range.
|
88
|
+
start = idx
|
89
|
+
stop = idx + 1
|
90
|
+
remove_ranges = []
|
91
|
+
while (idx = source.index(find_regexp, idx + 1))
|
92
|
+
# If the previous range ends at this index, we can expand that range to include this index.
|
93
|
+
# Otherwise, we need to record the held range and start a new one.
|
94
|
+
if stop != idx
|
95
|
+
remove_ranges << (start...stop)
|
96
|
+
start = idx
|
97
|
+
end
|
98
|
+
stop = idx + 1
|
99
|
+
end
|
100
|
+
# Be sure to capture the last range in the holder.
|
101
|
+
remove_ranges << (start...stop)
|
102
|
+
properties.delete_tags_at_ranges(remove_ranges, false)
|
103
|
+
end
|
73
104
|
end
|
74
105
|
end
|
75
106
|
end
|
@@ -8,8 +8,8 @@ module Contrast
|
|
8
8
|
module Agent
|
9
9
|
module Deadzone
|
10
10
|
module Policy
|
11
|
-
# This is just a holder for our policy. Takes the policy JSON and
|
12
|
-
#
|
11
|
+
# This is just a holder for our policy. Takes the policy JSON and converts it into hashes that we can access
|
12
|
+
# nicely.
|
13
13
|
class Policy < Contrast::Agent::Patching::Policy::Policy
|
14
14
|
def self.policy_folder
|
15
15
|
'deadzone'
|
@@ -35,6 +35,10 @@ module Contrast
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
def module_names
|
39
|
+
@_module_names ||= Set.new(deadzones.map(&:class_name))
|
40
|
+
end
|
41
|
+
|
38
42
|
def add_node node, _node_type = :deadzones
|
39
43
|
unless node
|
40
44
|
logger.error('Node was nil when adding node to policy')
|
@@ -24,8 +24,8 @@ module Contrast
|
|
24
24
|
@lock.synchronize { @gemdigest_cache = Hash.new { |hash, key| hash[key] = Set.new } }
|
25
25
|
end
|
26
26
|
|
27
|
-
# This method is invoked once, along with the rest of our catchup code to report libraries and their
|
28
|
-
#
|
27
|
+
# This method is invoked once, along with the rest of our catchup code to report libraries and their associated
|
28
|
+
# files that have already been loaded pre-contrast.
|
29
29
|
def catchup
|
30
30
|
return unless enabled?
|
31
31
|
|
@@ -45,8 +45,8 @@ module Contrast
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
# This method is invoked once per TracePoint :end - to map a specific file being required to the gem
|
49
|
-
# it belongs
|
48
|
+
# This method is invoked once per TracePoint :end - to map a specific file being required to the gem to which
|
49
|
+
# it belongs.
|
50
50
|
#
|
51
51
|
# @param path [String] the result of TracePoint#path from the :end event in which the Module was defined.
|
52
52
|
def associate_file path
|
@@ -71,7 +71,7 @@ module Contrast
|
|
71
71
|
logger.error('Unable to inventory file path', e, path: path)
|
72
72
|
end
|
73
73
|
|
74
|
-
# Populate the library_usages field of the Activity message using the data stored in the @gemdigest_cache
|
74
|
+
# Populate the library_usages field of the Activity message using the data stored in the @gemdigest_cache.
|
75
75
|
#
|
76
76
|
# @param activity [Contrast::Api::Dtm::Activity] the message to which to append the usage data
|
77
77
|
def generate_library_usage activity
|
@@ -79,7 +79,7 @@ module Contrast
|
|
79
79
|
return unless activity
|
80
80
|
|
81
81
|
# Copy gemdigest_cache and clear it in sync.
|
82
|
-
gem_spec_digest_to_files = @lock.synchronize do
|
82
|
+
gem_spec_digest_to_files = @lock.synchronize do
|
83
83
|
copy = @gemdigest_cache.dup
|
84
84
|
@gemdigest_cache.clear
|
85
85
|
copy
|
@@ -16,18 +16,17 @@ require 'contrast/utils/timer'
|
|
16
16
|
|
17
17
|
module Contrast
|
18
18
|
module Agent
|
19
|
-
# This class allows the Agent to plug into the Rack middleware stack. When the
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# as it goes through the middleware stack inside of #call.
|
19
|
+
# This class allows the Agent to plug into the Rack middleware stack. When the application is first started, we
|
20
|
+
# initialize ourselves as a rack middleware inside of #initialize. Afterwards, we process each http request and
|
21
|
+
# response as it goes through the middleware stack inside of #call.
|
23
22
|
class Middleware
|
24
23
|
include Contrast::Components::Interface
|
25
24
|
access_component :agent, :config, :logging, :scope, :settings
|
26
25
|
|
27
26
|
attr_reader :app
|
28
27
|
|
29
|
-
# Allows the Agent to function as a middleware. We perform all our one-time whole-app routines in here
|
30
|
-
#
|
28
|
+
# Allows the Agent to function as a middleware. We perform all our one-time whole-app routines in here since
|
29
|
+
# we're only going to be initialized a single time. Our initialization order is:
|
31
30
|
# - capture the application
|
32
31
|
# - setup the Agent
|
33
32
|
# - startup the Agent
|
@@ -47,14 +46,13 @@ module Contrast
|
|
47
46
|
agent_startup_routine
|
48
47
|
end
|
49
48
|
|
50
|
-
# This is where we're hooked into the middleware stack. If the agent is enabled, we're ready
|
51
|
-
#
|
52
|
-
# next middleware in the stack.
|
49
|
+
# This is where we're hooked into the middleware stack. If the agent is enabled, we're ready to do some
|
50
|
+
# processing on a per request basis. If not, we just pass the request along to the next middleware in the stack.
|
53
51
|
#
|
54
|
-
# @param env [Hash] the various variables stored by this and other Middlewares to know the state
|
55
|
-
#
|
56
|
-
# @return [Array,Rack::Response] the Response of this and subsequent Middlewares to be passed back
|
57
|
-
#
|
52
|
+
# @param env [Hash] the various variables stored by this and other Middlewares to know the state and values of
|
53
|
+
# this Request
|
54
|
+
# @return [Array,Rack::Response] the Response of this and subsequent Middlewares to be passed back to the user up
|
55
|
+
# the Rack framework.
|
58
56
|
def call env
|
59
57
|
return app.call(env) unless AGENT.enabled?
|
60
58
|
|
@@ -100,43 +98,22 @@ module Contrast
|
|
100
98
|
Contrast::Agent::AtExitHook.exit_hook
|
101
99
|
end
|
102
100
|
|
103
|
-
# Some things have to wait until first request to happen, either because
|
104
|
-
#
|
105
|
-
# classes, which confuses some of our instrumentation.
|
101
|
+
# Some things have to wait until first request to happen, either because resolution is not complete or because
|
102
|
+
# the framework will preload classes, which confuses some of our instrumentation.
|
106
103
|
def handle_first_request
|
107
104
|
@_handle_first_request ||= begin
|
108
105
|
Contrast::Agent::StaticAnalysis.catchup
|
109
|
-
force_patching
|
110
106
|
true
|
111
107
|
end
|
112
108
|
end
|
113
109
|
|
114
|
-
#
|
115
|
-
#
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
def force_patching
|
122
|
-
force_patch(ActionDispatch::FileHandler) if defined?(ActionDispatch::FileHandler)
|
123
|
-
force_patch(ActionDispatch::Http::MimeNegotiation) if defined?(ActionDispatch::Http::MimeNegotiation)
|
124
|
-
force_patch(ActionDispatch::Journey::Router) if defined?(ActionDispatch::Journey::Router)
|
125
|
-
force_patch(ActionView::Template) if defined?(ActionView::Template)
|
126
|
-
end
|
127
|
-
|
128
|
-
def force_patch mod
|
129
|
-
data = Contrast::Agent::ModuleData.new(mod)
|
130
|
-
Contrast::Agent::Patching::Policy::Patcher.send(:patch_into_module, data, true)
|
131
|
-
end
|
132
|
-
|
133
|
-
# This is where we process each request we intercept as a middleware. We make the request context
|
134
|
-
# available globally so that it can be accessed from anywhere. A RequestHandler object is made
|
135
|
-
# for each request, which handles prefilter and postfilter operations.
|
136
|
-
# @param env [Hash] the various variables stored by this and other Middlewares to know the state
|
137
|
-
# and values of this Request
|
138
|
-
# @return [Array,Rack::Response] the Response of this and subsequent Middlewares to be passed back
|
139
|
-
# to the user up the Rack framework.
|
110
|
+
# This is where we process each request we intercept as a middleware. We make the request context available
|
111
|
+
# globally so that it can be accessed from anywhere. A RequestHandler object is made for each request, which
|
112
|
+
# handles prefilter and postfilter operations.
|
113
|
+
# @param env [Hash] the various variables stored by this and other Middlewares to know the state and values of
|
114
|
+
# this Request
|
115
|
+
# @return [Array,Rack::Response] the Response of this and subsequent Middlewares to be passed back to the user
|
116
|
+
# up the Rack framework.
|
140
117
|
def call_with_agent env
|
141
118
|
Contrast::Agent.thread_watcher.ensure_running?
|
142
119
|
framework_request = Contrast::Agent.framework_manager.retrieve_request(env)
|
@@ -211,8 +188,8 @@ module Contrast
|
|
211
188
|
end
|
212
189
|
|
213
190
|
SECURITY_EXCEPTION_MARKER = 'Contrast::SecurityException'
|
214
|
-
# We're only going to suppress SecurityExceptions indicating a blocked attack.
|
215
|
-
#
|
191
|
+
# We're only going to suppress SecurityExceptions indicating a blocked attack. And, only if the
|
192
|
+
# config.agent.ruby.exceptions.capture? is set
|
216
193
|
def handle_exception exception
|
217
194
|
if security_exception?(exception)
|
218
195
|
exception_control = AGENT.exception_control
|
@@ -234,18 +211,15 @@ module Contrast
|
|
234
211
|
exception.message&.include?(SECURITY_EXCEPTION_MARKER)
|
235
212
|
end
|
236
213
|
|
237
|
-
# As we deprecate support to prepare to remove dead code, we need to
|
238
|
-
#
|
239
|
-
#
|
240
|
-
# standard Kernel#warn approach
|
214
|
+
# As we deprecate support to prepare to remove dead code, we need to inform our users still relying on the now
|
215
|
+
# deprecated and soon to be removed functionality. This method handles doing that by leveraging the standard
|
216
|
+
# Kernel#warn approach
|
241
217
|
def inform_deprecations
|
242
|
-
# Ruby 2.5 is currently in security maintenance, meaning int is only
|
243
|
-
#
|
244
|
-
#
|
245
|
-
# the
|
246
|
-
#
|
247
|
-
# TODO: RUBY-715 remove this part of the method, leaving it empty if
|
248
|
-
# there are no other deprecations, when we drop 2.5 support.
|
218
|
+
# Ruby 2.5 is currently in security maintenance, meaning int is only receiving updates for security issues. It
|
219
|
+
# will move to eol on 31 March 2021. As such, we can remove support for it in Q3. We'll begin the deprecation
|
220
|
+
# warnings now so that customers have time to reach out if they'll be impacted.
|
221
|
+
# TODO: RUBY-715 remove this part of the method, leaving it empty if there are no other deprecations, when we
|
222
|
+
# drop 2.5 support.
|
249
223
|
return unless RUBY_VERSION < '2.6.0'
|
250
224
|
|
251
225
|
Kernel.warn('[Contrast Security] [DEPRECATION] Support for Ruby 2.5 will be removed in April 2021. '\
|
@@ -362,24 +362,21 @@ module Contrast
|
|
362
362
|
|
363
363
|
case impl
|
364
364
|
when :alias_instance, :alias_singleton
|
365
|
+
# Core to patching. Ignore define method usage cop.
|
366
|
+
# rubocop:disable Performance/Kernel/DefineMethod
|
365
367
|
unless target_module.instance_methods(false).include? underlying_method_name
|
366
368
|
# alias_method may be private
|
367
369
|
target_module.send(:alias_method, underlying_method_name, method_name)
|
368
|
-
# TODO: RUBY-1052
|
369
|
-
# rubocop:disable Performance/Kernel/DefineMethod
|
370
370
|
target_module.send(:define_method, method_name, unbound_method.bind(target_module))
|
371
|
-
# rubocop:enable Performance/Kernel/DefineMethod
|
372
371
|
end
|
373
372
|
target_module.send(visibility, method_name) # e.g., module.private(:my_method)
|
374
373
|
when :prepend
|
375
374
|
prepending_module = Module.new
|
376
|
-
# TODO: RUBY-1052
|
377
|
-
# rubocop:disable Performance/Kernel/DefineMethod
|
378
375
|
prepending_module.send(:define_method, method_name, unbound_method.bind(target_module))
|
379
|
-
# rubocop:enable Performance/Kernel/DefineMethod
|
380
376
|
prepending_module.send(visibility, method_name)
|
381
377
|
# This prepends to the singleton class (it patches a class method)
|
382
378
|
target_module.prepend prepending_module
|
379
|
+
# rubocop:enable Performance/Kernel/DefineMethod
|
383
380
|
end
|
384
381
|
# Ougai::Logger.create_item_with_2args calls Hash#[]=, so we
|
385
382
|
# can't invoke this logging method or we'll seg fault as we'd
|
@@ -393,7 +390,7 @@ module Contrast
|
|
393
390
|
# method: method_name,
|
394
391
|
# visibility: visibility)
|
395
392
|
# end
|
396
|
-
underlying_method_name
|
393
|
+
underlying_method_name
|
397
394
|
end
|
398
395
|
|
399
396
|
# @return [Boolean]
|
@@ -13,8 +13,9 @@ module Contrast
|
|
13
13
|
module Agent
|
14
14
|
module Patching
|
15
15
|
module Policy
|
16
|
-
# This is just a holder for our policy. Takes the policy JSON and
|
17
|
-
#
|
16
|
+
# This is just a holder for our policy. Takes the policy JSON and converts it into hashes that we can access
|
17
|
+
# nicely.
|
18
|
+
#
|
18
19
|
# @abstract
|
19
20
|
class Policy
|
20
21
|
include Singleton
|
@@ -25,8 +26,8 @@ module Contrast
|
|
25
26
|
raise(NoMethodError, 'specify policy_folder for patching')
|
26
27
|
end
|
27
28
|
|
28
|
-
# Indicates is this feature has been disabled by the configuration,
|
29
|
-
#
|
29
|
+
# Indicates is this feature has been disabled by the configuration, read at startup, and therefore can never
|
30
|
+
# be enabled.
|
30
31
|
def disabled_globally?
|
31
32
|
raise(NoMethodError, 'specify disabled_globally? conditions for patching')
|
32
33
|
end
|
@@ -58,17 +59,15 @@ module Contrast
|
|
58
59
|
from_hash_string(json)
|
59
60
|
end
|
60
61
|
|
61
|
-
# Our policy for patching rules is a 'dope ass' JSON file. Rather than
|
62
|
-
#
|
63
|
-
#
|
64
|
-
# This let's us be flexible and extensible.
|
62
|
+
# Our policy for patching rules is a 'dope ass' JSON file. Rather than hard code in a bunch of things to
|
63
|
+
# monkey patch, we let the JSON file define the conditions in which modifications are applied. This let's us
|
64
|
+
# be flexible and extensible.
|
65
65
|
def from_hash_string string
|
66
|
-
# The default behavior of the agent is to load the policy on startup,
|
67
|
-
#
|
66
|
+
# The default behavior of the agent is to load the policy on startup, as at this point we do not know in
|
67
|
+
# which mode we'll be run.
|
68
68
|
#
|
69
|
-
# If the configuration file explicitly disables a feature, we know
|
70
|
-
#
|
71
|
-
# can skip policy loading.
|
69
|
+
# If the configuration file explicitly disables a feature, we know that we will not ever be able to enable
|
70
|
+
# it, so in that case, we can skip policy loading.
|
72
71
|
return if disabled_globally?
|
73
72
|
|
74
73
|
policy_data = JSON.parse(string)
|
@@ -110,13 +109,7 @@ module Contrast
|
|
110
109
|
end
|
111
110
|
|
112
111
|
def module_names
|
113
|
-
@_module_names ||=
|
114
|
-
m = Set.new
|
115
|
-
sources.each { |source| m << source.class_name }
|
116
|
-
propagators.each { |propagator| m << propagator.class_name }
|
117
|
-
triggers.each { |trigger| m << trigger.class_name }
|
118
|
-
m
|
119
|
-
end
|
112
|
+
@_module_names ||= Set.new([sources, propagators, triggers].flatten.map!(&:class_name))
|
120
113
|
end
|
121
114
|
|
122
115
|
def find_triggers_by_rule rule_id
|
@@ -70,12 +70,12 @@ module Contrast
|
|
70
70
|
|
71
71
|
logger.trace_with_time('Rebuilding rule modes') do
|
72
72
|
SETTINGS.build_protect_rules if PROTECT.enabled?
|
73
|
-
SETTINGS.build_assess_rules if ASSESS.enabled?
|
74
73
|
AGENT.reset_ruleset
|
75
74
|
|
76
75
|
logger.info('Current rule settings:')
|
76
|
+
|
77
77
|
PROTECT.rules.each { |k, v| logger.info('Protect Rule mode set', rule: k, mode: v.mode) }
|
78
|
-
|
78
|
+
logger.info('Disabled Assess Rules', rules: ASSESS.disabled_rules)
|
79
79
|
end
|
80
80
|
end
|
81
81
|
end
|
@@ -36,7 +36,7 @@ module Contrast
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def ruleset
|
39
|
-
@_ruleset ||= Contrast::Agent::RuleSet.new(
|
39
|
+
@_ruleset ||= Contrast::Agent::RuleSet.new(retrieve_protect_ruleset&.values)
|
40
40
|
end
|
41
41
|
|
42
42
|
def reset_ruleset
|
@@ -98,16 +98,10 @@ module Contrast
|
|
98
98
|
@_interpolation_patch_possible
|
99
99
|
end
|
100
100
|
|
101
|
-
def
|
102
|
-
return {} unless enabled?
|
101
|
+
def retrieve_protect_ruleset
|
102
|
+
return {} unless enabled? && PROTECT.enabled?
|
103
103
|
|
104
|
-
|
105
|
-
ASSESS.rules.merge(PROTECT.rules)
|
106
|
-
elsif ASSESS.enabled?
|
107
|
-
ASSESS.rules
|
108
|
-
else
|
109
|
-
PROTECT.rules
|
110
|
-
end
|
104
|
+
PROTECT.rules
|
111
105
|
end
|
112
106
|
end
|
113
107
|
|
@@ -19,7 +19,7 @@ module Contrast
|
|
19
19
|
return false if forcibly_disabled?
|
20
20
|
return true if forcibly_enabled?
|
21
21
|
|
22
|
-
SETTINGS.
|
22
|
+
SETTINGS.assess_state.enabled == true
|
23
23
|
end
|
24
24
|
|
25
25
|
def tainted_columns
|
@@ -58,10 +58,9 @@ module Contrast
|
|
58
58
|
# node types (SourceNode, PolicyNode, TriggerNode, PropagationNode)
|
59
59
|
#
|
60
60
|
# @param policy_node [Contrast::Agent::Assess::Policy::PolicyNode] The node in question.
|
61
|
-
# @
|
61
|
+
# @return [Boolean] to capture or not to capture, that is the question.
|
62
62
|
def capture_stacktrace? policy_node
|
63
63
|
return true if capture_stacktrace_value == :ALL
|
64
|
-
|
65
64
|
return false if capture_stacktrace_value == :NONE
|
66
65
|
|
67
66
|
# Below here capture_stacktrace_value must be :SOME.
|
@@ -90,8 +89,9 @@ module Contrast
|
|
90
89
|
CONFIG.root.assess&.tags
|
91
90
|
end
|
92
91
|
|
93
|
-
def
|
94
|
-
|
92
|
+
def disabled_rules
|
93
|
+
# TODO: RUBY-903
|
94
|
+
CONFIG.root.assess&.rules&.disabled_rules || SETTINGS.assess_state.disabled_assess_rules || []
|
95
95
|
end
|
96
96
|
|
97
97
|
private
|
@@ -100,11 +100,6 @@ module Contrast
|
|
100
100
|
@_forcibly_enabled = true?(CONFIG.root.assess.enable) if @_forcibly_enabled.nil?
|
101
101
|
@_forcibly_enabled
|
102
102
|
end
|
103
|
-
|
104
|
-
def disabled_rules
|
105
|
-
# TODO: RUBY-903
|
106
|
-
CONFIG.root.assess&.rules&.disabled_rules || SETTINGS.disabled_assess_rules || []
|
107
|
-
end
|
108
103
|
end
|
109
104
|
|
110
105
|
COMPONENT_INTERFACE = Interface.new
|
@@ -4,11 +4,8 @@
|
|
4
4
|
module Contrast
|
5
5
|
module Components
|
6
6
|
module Protect
|
7
|
-
# A wrapper build around the Common Agent Configuration project to allow
|
8
|
-
# for
|
9
|
-
# parent_configuration_spec.yaml.
|
10
|
-
# Specifically, this allows for querying the state of the Protect
|
11
|
-
# product.
|
7
|
+
# A wrapper build around the Common Agent Configuration project to allow for access of the values contained in
|
8
|
+
# its parent_configuration_spec.yaml. Specifically, this allows for querying the state of the Protect product.
|
12
9
|
class Interface
|
13
10
|
include Contrast::Components::ComponentBase
|
14
11
|
include Contrast::Components::Interface
|
@@ -20,7 +17,7 @@ module Contrast
|
|
20
17
|
return false if forcibly_disabled?
|
21
18
|
return true if forcibly_enabled?
|
22
19
|
|
23
|
-
SETTINGS.
|
20
|
+
SETTINGS.protect_state.enabled == true
|
24
21
|
end
|
25
22
|
|
26
23
|
def rule_config
|
@@ -28,17 +25,17 @@ module Contrast
|
|
28
25
|
end
|
29
26
|
|
30
27
|
def rules
|
31
|
-
SETTINGS.
|
28
|
+
SETTINGS.protect_state.rules
|
32
29
|
end
|
33
30
|
|
34
31
|
def rule_mode rule_id
|
35
32
|
CONFIG.root.protect.rules[rule_id]&.applicable_mode ||
|
36
|
-
SETTINGS.modes_by_id[rule_id] ||
|
33
|
+
SETTINGS.application_state.modes_by_id[rule_id] ||
|
37
34
|
Contrast::Api::Settings::ProtectionRule::Mode::NO_ACTION
|
38
35
|
end
|
39
36
|
|
40
37
|
def rule name
|
41
|
-
SETTINGS.
|
38
|
+
SETTINGS.protect_state.rules[name]
|
42
39
|
end
|
43
40
|
|
44
41
|
def report_any_command_execution?
|
@@ -58,15 +55,13 @@ module Contrast
|
|
58
55
|
end
|
59
56
|
|
60
57
|
def forcibly_disabled?
|
61
|
-
@_forcibly_disabled
|
62
|
-
@_forcibly_disabled
|
58
|
+
@_forcibly_disabled ||= false?(CONFIG.root.protect.enable)
|
63
59
|
end
|
64
60
|
|
65
61
|
private
|
66
62
|
|
67
63
|
def forcibly_enabled?
|
68
|
-
@_forcibly_enabled
|
69
|
-
@_forcibly_enabled
|
64
|
+
@_forcibly_enabled ||= true?(CONFIG.root.protect.enable)
|
70
65
|
end
|
71
66
|
end
|
72
67
|
|
@@ -8,134 +8,61 @@ module Contrast
|
|
8
8
|
# directives (likely provided by TeamServer) about product operation.
|
9
9
|
# 'Settings' is not a generic term for 'configurable stuff'.
|
10
10
|
module Settings
|
11
|
+
APPLICATION_STATE_BASE = Struct.new(:modes_by_id, :exclusion_matchers).new(
|
12
|
+
Hash.new { Contrast::Api::Settings::ProtectionRule::Mode::NO_ACTION }, [])
|
13
|
+
PROTECT_STATE_BASE = Struct.new(:enabled, :rules).new(false, {})
|
14
|
+
ASSESS_STATE_BASE = Struct.new(:enabled, :sampling_settings, :disabled_assess_rules).new(false, nil, []) do
|
15
|
+
def sampling_settings= new_val
|
16
|
+
@sampling_settings = new_val
|
17
|
+
Contrast::Utils::Assess::SamplingUtil.instance.update
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
11
21
|
# This is a class.
|
12
22
|
class Interface
|
13
23
|
include Contrast::Components::ComponentBase
|
14
24
|
include Contrast::Components::Interface
|
15
25
|
access_component :config
|
16
26
|
|
17
|
-
attr_reader :assess_rules,
|
18
|
-
:protect_rules
|
19
|
-
|
20
|
-
# Other stateful information that doesn't yet cleanly fit anywhere:
|
21
|
-
|
22
27
|
# tainted_columns are database columns that receive unsanitized input.
|
23
|
-
# this statefulness
|
24
28
|
attr_reader :tainted_columns # This can probably go into assess_state?
|
25
|
-
|
26
|
-
# These three 'state' variables represent atomic config/setting state,
|
27
|
-
# outside of things like rule defs.
|
28
|
-
|
29
|
-
def assess_state
|
30
|
-
@assess_state ||= { # rubocop:disable Naming/MemoizedInstanceVariableName
|
31
|
-
enabled: false, # Boolean
|
32
|
-
sampling_features: nil # Contrast::Api::Settings::Sampling
|
33
|
-
}
|
34
|
-
end
|
35
|
-
|
36
|
-
def protect_state
|
37
|
-
@protect_state ||= { # rubocop:disable Naming/MemoizedInstanceVariableName
|
38
|
-
enabled: false
|
39
|
-
}
|
40
|
-
end
|
41
|
-
|
42
|
-
def application_state
|
43
|
-
@application_state ||= { # rubocop:disable Naming/MemoizedInstanceVariableName
|
44
|
-
modes_by_id: Hash.new(Contrast::Api::Settings::ProtectionRule::Mode::NO_ACTION),
|
45
|
-
exclusion_matchers: [],
|
46
|
-
disabled_assess_rules: []
|
47
|
-
}
|
48
|
-
end
|
49
|
-
|
50
|
-
# These are settings that we receive & store.
|
51
|
-
# Rules are settings too, but they're more involved.
|
52
|
-
# So, between this block and rules, that's setting state.
|
53
|
-
PROTECT_STATE_ATTRS = %i[].cs__freeze
|
54
|
-
ASSESS_STATE_ATTRS = %i[sampling_features].cs__freeze
|
55
|
-
APPLICATION_STATE_ATTRS = %i[modes_by_id exclusion_matchers disabled_assess_rules].cs__freeze
|
56
|
-
|
57
|
-
# Meta-define an accessor for each state attribute.
|
58
|
-
|
59
|
-
PROTECT_STATE_ATTRS.each do |attr|
|
60
|
-
# TODO: RUBY-1052
|
61
|
-
define_method(attr) do # rubocop:disable Performance/Kernel/DefineMethod
|
62
|
-
protect_state[attr]
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
ASSESS_STATE_ATTRS.each do |attr|
|
67
|
-
# TODO: RUBY-1052
|
68
|
-
define_method(attr) do # rubocop:disable Performance/Kernel/DefineMethod
|
69
|
-
assess_state[attr]
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
APPLICATION_STATE_ATTRS.each do |attr|
|
74
|
-
# TODO: RUBY-1052
|
75
|
-
define_method(attr) do # rubocop:disable Performance/Kernel/DefineMethod
|
76
|
-
application_state[attr]
|
77
|
-
end
|
78
|
-
end
|
29
|
+
attr_reader :assess_state, :protect_state, :application_state
|
79
30
|
|
80
31
|
def initialize
|
81
32
|
reset_state
|
82
33
|
end
|
83
34
|
|
84
|
-
def protect_enabled?
|
85
|
-
@_protect_enabled = !!protect_state[:enabled] if @_protect_enabled.nil?
|
86
|
-
@_protect_enabled
|
87
|
-
end
|
88
|
-
|
89
|
-
def assess_enabled?
|
90
|
-
@_assess_enabled = !!assess_state[:enabled] if @_assess_enabled.nil?
|
91
|
-
@_assess_enabled
|
92
|
-
end
|
93
|
-
|
94
35
|
def code_exclusions
|
95
|
-
exclusion_matchers.select(&:code?)
|
36
|
+
@application_state.exclusion_matchers.select(&:code?)
|
96
37
|
end
|
97
38
|
|
98
39
|
# @param server_features [Contrast::Api::Settings::ServerFeatures]
|
99
40
|
def update_from_server_features server_features
|
100
|
-
|
101
|
-
|
102
|
-
@
|
103
|
-
protect_state[:enabled] = server_features.protect_enabled?
|
104
|
-
|
105
|
-
# assess
|
106
|
-
|
107
|
-
@_assess_enabled = nil
|
108
|
-
assess_state[:enabled] = server_features.assess_enabled?
|
109
|
-
assess_state[:sampling_settings] = server_features.assess.sampling
|
110
|
-
Contrast::Utils::Assess::SamplingUtil.instance.update
|
41
|
+
@protect_state.enabled = server_features.protect_enabled?
|
42
|
+
@assess_state.enabled = server_features.assess_enabled?
|
43
|
+
@assess_state.sampling_settings = server_features.assess.sampling
|
111
44
|
end
|
112
45
|
|
113
46
|
# @param application_settings [Contrast::Api::Settings::ApplicationSettings]
|
114
47
|
def update_from_application_settings application_settings
|
115
|
-
|
48
|
+
new_vals = application_settings.application_state_translation
|
49
|
+
@application_state.modes_by_id = new_vals[:modes_by_id]
|
50
|
+
@application_state.exclusion_matchers = new_vals[:exclusion_matchers]
|
51
|
+
@assess_state.disabled_assess_rules = new_vals[:disabled_assess_rules]
|
116
52
|
end
|
117
53
|
|
118
54
|
# Wipe state to zero.
|
119
55
|
def reset_state
|
120
|
-
@
|
121
|
-
@
|
122
|
-
|
56
|
+
@protect_state = PROTECT_STATE_BASE.dup
|
57
|
+
@assess_state = ASSESS_STATE_BASE.dup
|
58
|
+
@application_state = APPLICATION_STATE_BASE.dup
|
123
59
|
@tainted_columns = {}
|
124
|
-
|
125
|
-
@assess_state = nil
|
126
|
-
@protect_state = nil
|
127
|
-
@application_state = nil
|
128
|
-
end
|
129
|
-
|
130
|
-
def build_assess_rules
|
131
|
-
# TODO: RUBY-1120 actually build assess_rules.
|
132
|
-
@assess_rules = {}
|
133
60
|
end
|
134
61
|
|
135
62
|
def build_protect_rules
|
136
|
-
@
|
63
|
+
@protect_state.rules = {}
|
137
64
|
|
138
|
-
#
|
65
|
+
# Rules. They add themselves on initialize.
|
139
66
|
Contrast::Agent::Protect::Rule::CmdInjection.new
|
140
67
|
Contrast::Agent::Protect::Rule::Deserialization.new
|
141
68
|
Contrast::Agent::Protect::Rule::HttpMethodTampering.new
|
@@ -107,11 +107,8 @@ module Contrast
|
|
107
107
|
#
|
108
108
|
# @param request [Contrast::Agent::Request] the current request.
|
109
109
|
# @return [Contrast::Api::Dtm::RouteCoverage] the current route as a Dtm.
|
110
|
-
# TODO: RUBY-1075 add unit test.
|
111
110
|
def get_route_dtm request
|
112
|
-
|
113
|
-
@_frameworks.find { |framework_klass| result = framework_klass.current_route(request) }
|
114
|
-
result
|
111
|
+
@_frameworks.lazy.map { |framework_support| framework_support.current_route(request) }.reject(&:nil?).first
|
115
112
|
end
|
116
113
|
|
117
114
|
private
|
data/ruby-agent.gemspec
CHANGED
@@ -67,6 +67,8 @@ def self.add_specs spec
|
|
67
67
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
68
68
|
spec.add_development_dependency 'rspec-benchmark'
|
69
69
|
spec.add_development_dependency 'rspec_junit_formatter', '0.3.0'
|
70
|
+
spec.add_development_dependency 'rspec-rails', '5.0'
|
71
|
+
spec.add_development_dependency 'tzinfo-data' # Alpine rspec-rails requirement.
|
70
72
|
end
|
71
73
|
|
72
74
|
def self.add_coverage spec
|
@@ -85,6 +87,7 @@ end
|
|
85
87
|
|
86
88
|
# Dependencies not mocked out during RSpec that we test real code of, beyond just frameworks.
|
87
89
|
def self.add_tested_gems spec
|
90
|
+
spec.add_development_dependency 'async'
|
88
91
|
spec.add_development_dependency 'execjs'
|
89
92
|
spec.add_development_dependency 'sqlite3'
|
90
93
|
spec.add_development_dependency 'therubyracer'
|
data/service_executables/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.19.0
|
Binary file
|
Binary file
|
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: 4.
|
4
|
+
version: 4.6.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: 2021-
|
16
|
+
date: 2021-04-22 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: bundler
|
@@ -253,6 +253,20 @@ dependencies:
|
|
253
253
|
- - ">="
|
254
254
|
- !ruby/object:Gem::Version
|
255
255
|
version: '2'
|
256
|
+
- !ruby/object:Gem::Dependency
|
257
|
+
name: async
|
258
|
+
requirement: !ruby/object:Gem::Requirement
|
259
|
+
requirements:
|
260
|
+
- - ">="
|
261
|
+
- !ruby/object:Gem::Version
|
262
|
+
version: '0'
|
263
|
+
type: :development
|
264
|
+
prerelease: false
|
265
|
+
version_requirements: !ruby/object:Gem::Requirement
|
266
|
+
requirements:
|
267
|
+
- - ">="
|
268
|
+
- !ruby/object:Gem::Version
|
269
|
+
version: '0'
|
256
270
|
- !ruby/object:Gem::Dependency
|
257
271
|
name: execjs
|
258
272
|
requirement: !ruby/object:Gem::Requirement
|
@@ -435,6 +449,34 @@ dependencies:
|
|
435
449
|
- - '='
|
436
450
|
- !ruby/object:Gem::Version
|
437
451
|
version: 0.3.0
|
452
|
+
- !ruby/object:Gem::Dependency
|
453
|
+
name: rspec-rails
|
454
|
+
requirement: !ruby/object:Gem::Requirement
|
455
|
+
requirements:
|
456
|
+
- - '='
|
457
|
+
- !ruby/object:Gem::Version
|
458
|
+
version: '5.0'
|
459
|
+
type: :development
|
460
|
+
prerelease: false
|
461
|
+
version_requirements: !ruby/object:Gem::Requirement
|
462
|
+
requirements:
|
463
|
+
- - '='
|
464
|
+
- !ruby/object:Gem::Version
|
465
|
+
version: '5.0'
|
466
|
+
- !ruby/object:Gem::Dependency
|
467
|
+
name: tzinfo-data
|
468
|
+
requirement: !ruby/object:Gem::Requirement
|
469
|
+
requirements:
|
470
|
+
- - ">="
|
471
|
+
- !ruby/object:Gem::Version
|
472
|
+
version: '0'
|
473
|
+
type: :development
|
474
|
+
prerelease: false
|
475
|
+
version_requirements: !ruby/object:Gem::Requirement
|
476
|
+
requirements:
|
477
|
+
- - ">="
|
478
|
+
- !ruby/object:Gem::Version
|
479
|
+
version: '0'
|
438
480
|
- !ruby/object:Gem::Dependency
|
439
481
|
name: ougai
|
440
482
|
requirement: !ruby/object:Gem::Requirement
|
@@ -499,20 +541,20 @@ executables:
|
|
499
541
|
- contrast_service
|
500
542
|
extensions:
|
501
543
|
- ext/cs__common/extconf.rb
|
502
|
-
- ext/
|
544
|
+
- ext/cs__assess_array/extconf.rb
|
503
545
|
- ext/cs__assess_string_interpolation26/extconf.rb
|
546
|
+
- ext/cs__assess_marshal_module/extconf.rb
|
504
547
|
- ext/cs__assess_hash/extconf.rb
|
548
|
+
- ext/cs__assess_yield_track/extconf.rb
|
549
|
+
- ext/cs__assess_string/extconf.rb
|
505
550
|
- ext/cs__protect_kernel/extconf.rb
|
551
|
+
- ext/cs__assess_basic_object/extconf.rb
|
552
|
+
- ext/cs__contrast_patch/extconf.rb
|
553
|
+
- ext/cs__assess_regexp/extconf.rb
|
506
554
|
- ext/cs__assess_fiber_track/extconf.rb
|
507
|
-
- ext/cs__assess_array/extconf.rb
|
508
|
-
- ext/cs__assess_active_record_named/extconf.rb
|
509
|
-
- ext/cs__assess_string/extconf.rb
|
510
555
|
- ext/cs__assess_kernel/extconf.rb
|
556
|
+
- ext/cs__assess_active_record_named/extconf.rb
|
511
557
|
- ext/cs__assess_module/extconf.rb
|
512
|
-
- ext/cs__assess_basic_object/extconf.rb
|
513
|
-
- ext/cs__assess_marshal_module/extconf.rb
|
514
|
-
- ext/cs__assess_yield_track/extconf.rb
|
515
|
-
- ext/cs__assess_regexp/extconf.rb
|
516
558
|
extra_rdoc_files: []
|
517
559
|
files:
|
518
560
|
- ".clang-format"
|
@@ -868,7 +910,6 @@ files:
|
|
868
910
|
- lib/contrast/api/decorators/user_input.rb
|
869
911
|
- lib/contrast/api/dtm.pb.rb
|
870
912
|
- lib/contrast/api/settings.pb.rb
|
871
|
-
- lib/contrast/common_agent_configuration.rb
|
872
913
|
- lib/contrast/components/agent.rb
|
873
914
|
- lib/contrast/components/app_context.rb
|
874
915
|
- lib/contrast/components/assess.rb
|
@@ -1007,7 +1048,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
1007
1048
|
- !ruby/object:Gem::Version
|
1008
1049
|
version: '0'
|
1009
1050
|
requirements: []
|
1010
|
-
rubygems_version: 3.1.
|
1051
|
+
rubygems_version: 3.1.6
|
1011
1052
|
signing_key:
|
1012
1053
|
specification_version: 4
|
1013
1054
|
summary: Contrast Security's agent for rack-based applications.
|
@@ -1,87 +0,0 @@
|
|
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
|
-
# rubocop:disable Style/MissingRespondToMissing
|
5
|
-
module Contrast
|
6
|
-
# A wrapper build around the Common Agent Configuration project to allow for
|
7
|
-
# access of the values contained in its parent_configuration_spec.yaml
|
8
|
-
class CommonAgentConfiguration
|
9
|
-
# The CAC spec, deserialized to a hash.
|
10
|
-
|
11
|
-
SPEC = 'spec'
|
12
|
-
NODES = 'nodes'
|
13
|
-
PROPERTIES = 'properties'
|
14
|
-
|
15
|
-
def initialize hsh
|
16
|
-
@hsh = hsh[SPEC]
|
17
|
-
end
|
18
|
-
|
19
|
-
# Used to indicate those sections of the configuration which have
|
20
|
-
# references to other nodes or properties, allowing for the parsing of and
|
21
|
-
# access to the nested configuration structure.
|
22
|
-
module IsANode
|
23
|
-
def children
|
24
|
-
hsh[NODES]&.map { |raw_node| Node.new(raw_node) } || []
|
25
|
-
end
|
26
|
-
|
27
|
-
def properties
|
28
|
-
hsh[PROPERTIES].map { |raw_property| Property.new(raw_property) }
|
29
|
-
end
|
30
|
-
|
31
|
-
def lookup *path
|
32
|
-
# Path will be N args, representing a path of nodes.
|
33
|
-
path.reduce(self) do |node, next_arg|
|
34
|
-
# If we can travel to a node by that name, do that.
|
35
|
-
candidate_node = node.children.find { |n| n.name == next_arg }
|
36
|
-
next candidate_node if candidate_node
|
37
|
-
|
38
|
-
# If there's a property, dereference that.
|
39
|
-
candidate_property = node.properties.find { |n| n.name == next_arg }
|
40
|
-
next candidate_property if candidate_property
|
41
|
-
|
42
|
-
raise IndexError, "couldn't traverse path:\t#{ path.join(Contrast::Utils::ObjectShare::PERIOD) }"
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def method_missing method, *args, &block
|
47
|
-
if args.any?
|
48
|
-
lookup(method.to_s).public_send(args, block)
|
49
|
-
else
|
50
|
-
lookup(method.to_s)
|
51
|
-
end
|
52
|
-
rescue IndexError
|
53
|
-
super(method, args, block)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# Used to indicate those sections of the configuration which are for a
|
58
|
-
# single property, allowing for the parsing of and access to the
|
59
|
-
# information describing the property.
|
60
|
-
module IsAProperty
|
61
|
-
attr_reader :hsh
|
62
|
-
|
63
|
-
def initialize hsh
|
64
|
-
@hsh = hsh
|
65
|
-
end
|
66
|
-
|
67
|
-
%w[name default description required_languages display].each do |field|
|
68
|
-
# TODO: RUBY-1052
|
69
|
-
define_method(field) { hsh[field].dup } # rubocop:disable Performance/Kernel/DefineMethod
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
include IsANode
|
74
|
-
include IsAProperty
|
75
|
-
|
76
|
-
# A Property in the Common Agent Configuration
|
77
|
-
class Property
|
78
|
-
include IsAProperty
|
79
|
-
end
|
80
|
-
|
81
|
-
# A Node in the Common Agent Configuration
|
82
|
-
class Node < Property
|
83
|
-
include IsANode
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
# rubocop:enable Style/MissingRespondToMissing
|