contrast-agent 3.14.0 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.c +18 -15
- data/ext/cs__assess_marshal_module/cs__assess_marshal_module.h +1 -0
- data/ext/cs__assess_string/cs__assess_string.c +24 -25
- data/ext/cs__assess_string/cs__assess_string.h +3 -1
- data/ext/cs__common/cs__common.c +4 -2
- data/ext/cs__common/cs__common.h +1 -1
- data/lib/contrast.rb +1 -1
- data/lib/contrast/agent.rb +4 -12
- data/lib/contrast/agent/assess.rb +1 -0
- data/lib/contrast/agent/assess/contrast_event.rb +143 -79
- data/lib/contrast/agent/assess/events/source_event.rb +1 -1
- data/lib/contrast/agent/assess/finalizers/freeze.rb +3 -1
- data/lib/contrast/agent/assess/finalizers/hash.rb +45 -1
- data/lib/contrast/agent/assess/policy/dynamic_source_factory.rb +10 -3
- data/lib/contrast/agent/assess/policy/patcher.rb +1 -1
- data/lib/contrast/agent/assess/policy/policy.rb +0 -2
- data/lib/contrast/agent/assess/policy/policy_node.rb +15 -10
- data/lib/contrast/agent/assess/policy/policy_scanner.rb +19 -3
- data/lib/contrast/agent/assess/policy/preshift.rb +7 -11
- data/lib/contrast/agent/assess/policy/propagation_method.rb +50 -33
- data/lib/contrast/agent/assess/policy/propagator/append.rb +8 -5
- data/lib/contrast/agent/assess/policy/propagator/base.rb +1 -1
- data/lib/contrast/agent/assess/policy/propagator/center.rb +9 -5
- data/lib/contrast/agent/assess/policy/propagator/database_write.rb +5 -3
- data/lib/contrast/agent/assess/policy/propagator/insert.rb +7 -4
- data/lib/contrast/agent/assess/policy/propagator/keep.rb +4 -1
- data/lib/contrast/agent/assess/policy/propagator/match_data.rb +4 -7
- data/lib/contrast/agent/assess/policy/propagator/next.rb +7 -5
- data/lib/contrast/agent/assess/policy/propagator/prepend.rb +8 -5
- data/lib/contrast/agent/assess/policy/propagator/remove.rb +8 -4
- data/lib/contrast/agent/assess/policy/propagator/replace.rb +5 -2
- data/lib/contrast/agent/assess/policy/propagator/reverse.rb +7 -5
- data/lib/contrast/agent/assess/policy/propagator/select.rb +13 -7
- data/lib/contrast/agent/assess/policy/propagator/splat.rb +10 -9
- data/lib/contrast/agent/assess/policy/propagator/split.rb +24 -19
- data/lib/contrast/agent/assess/policy/propagator/substitution.rb +47 -31
- data/lib/contrast/agent/assess/policy/propagator/trim.rb +11 -5
- data/lib/contrast/agent/assess/policy/source_method.rb +85 -58
- data/lib/contrast/agent/assess/policy/trigger/reflected_xss.rb +16 -12
- data/lib/contrast/agent/assess/policy/trigger/xpath.rb +1 -1
- data/lib/contrast/agent/assess/policy/trigger_method.rb +76 -28
- data/lib/contrast/agent/assess/policy/trigger_node.rb +38 -43
- data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +2 -1
- data/lib/contrast/agent/assess/properties.rb +2 -0
- data/lib/contrast/agent/assess/property/evented.rb +5 -18
- data/lib/contrast/agent/assess/property/tagged.rb +9 -3
- data/lib/contrast/agent/assess/property/updated.rb +131 -0
- data/lib/contrast/agent/assess/rule/provider/hardcoded_key.rb +58 -5
- data/lib/contrast/agent/assess/rule/provider/hardcoded_password.rb +23 -8
- data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +83 -14
- data/lib/contrast/agent/assess/tag.rb +1 -1
- data/lib/contrast/agent/assess/tracker.rb +66 -0
- data/lib/contrast/agent/at_exit_hook.rb +5 -5
- data/lib/contrast/agent/class_reopener.rb +7 -5
- data/lib/contrast/agent/inventory.rb +15 -0
- data/lib/contrast/agent/inventory/dependencies.rb +50 -0
- data/lib/contrast/agent/inventory/dependency_analysis.rb +37 -0
- data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +104 -0
- data/lib/contrast/agent/inventory/gemfile_digest_cache.rb +38 -0
- data/lib/contrast/agent/middleware.rb +1 -3
- data/lib/contrast/agent/patching/policy/after_load_patch.rb +5 -5
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +20 -20
- data/lib/contrast/agent/patching/policy/module_policy.rb +10 -10
- data/lib/contrast/agent/patching/policy/patch.rb +6 -0
- data/lib/contrast/agent/patching/policy/patcher.rb +13 -22
- data/lib/contrast/agent/patching/policy/policy.rb +17 -6
- data/lib/contrast/agent/protect/policy/applies_command_injection_rule.rb +3 -5
- data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +4 -3
- data/lib/contrast/agent/protect/policy/applies_xxe_rule.rb +1 -1
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +9 -25
- data/lib/contrast/agent/protect/rule/no_sqli/mongo_no_sql_scanner.rb +1 -0
- data/lib/contrast/agent/request.rb +34 -34
- data/lib/contrast/agent/request_handler.rb +1 -1
- data/lib/contrast/agent/response.rb +17 -6
- data/lib/contrast/agent/rewriter.rb +1 -3
- data/lib/contrast/agent/scope.rb +59 -53
- data/lib/contrast/agent/static_analysis.rb +7 -7
- data/lib/contrast/agent/tracepoint_hook.rb +1 -1
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/api/communication/messaging_queue.rb +1 -4
- data/lib/contrast/api/communication/socket_client.rb +36 -1
- data/lib/contrast/api/decorators.rb +3 -0
- data/lib/contrast/api/decorators/address.rb +13 -14
- data/lib/contrast/api/decorators/application_update.rb +2 -4
- data/lib/contrast/api/decorators/library.rb +53 -0
- data/lib/contrast/api/decorators/library_usage_update.rb +30 -0
- data/lib/contrast/api/decorators/message.rb +1 -0
- data/lib/contrast/api/decorators/trace_event.rb +25 -23
- data/lib/contrast/common_agent_configuration.rb +2 -1
- data/lib/contrast/components/agent.rb +6 -5
- data/lib/contrast/components/app_context.rb +49 -38
- data/lib/contrast/components/config.rb +30 -48
- data/lib/contrast/components/contrast_service.rb +9 -9
- data/lib/contrast/components/interface.rb +25 -3
- data/lib/contrast/components/inventory.rb +6 -1
- data/lib/contrast/components/scope.rb +49 -6
- data/lib/contrast/components/settings.rb +23 -23
- data/lib/contrast/config/application_configuration.rb +5 -2
- data/lib/contrast/config/inventory_configuration.rb +2 -2
- data/lib/contrast/config/service_configuration.rb +8 -0
- data/lib/contrast/configuration.rb +88 -47
- data/lib/contrast/extension/assess.rb +0 -2
- data/lib/contrast/extension/assess/array.rb +15 -8
- data/lib/contrast/extension/assess/erb.rb +11 -3
- data/lib/contrast/extension/assess/eval_trigger.rb +6 -6
- data/lib/contrast/extension/assess/exec_trigger.rb +1 -4
- data/lib/contrast/extension/assess/fiber.rb +12 -12
- data/lib/contrast/extension/assess/hash.rb +5 -6
- data/lib/contrast/extension/assess/kernel.rb +28 -23
- data/lib/contrast/extension/assess/marshal.rb +11 -6
- data/lib/contrast/extension/assess/regexp.rb +8 -7
- data/lib/contrast/extension/assess/string.rb +21 -21
- data/lib/contrast/extension/protect/kernel.rb +3 -3
- data/lib/contrast/framework/base_support.rb +1 -1
- data/lib/contrast/framework/manager.rb +3 -3
- data/lib/contrast/framework/rack/patch/session_cookie.rb +22 -28
- data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +13 -13
- data/lib/contrast/framework/rails/patch/assess_configuration.rb +5 -11
- data/lib/contrast/framework/rails/patch/rails_application_configuration.rb +10 -10
- data/lib/contrast/framework/rails/patch/support.rb +1 -1
- data/lib/contrast/framework/rails/rewrite/action_controller_railties_helper_inherited.rb +11 -11
- data/lib/contrast/framework/rails/rewrite/active_record_attribute_methods_read.rb +12 -12
- data/lib/contrast/framework/rails/rewrite/active_record_named.rb +3 -3
- data/lib/contrast/framework/rails/rewrite/active_record_time_zone_inherited.rb +12 -12
- data/lib/contrast/framework/rails/support.rb +5 -0
- data/lib/contrast/framework/sinatra/patch/base.rb +11 -11
- data/lib/contrast/framework/sinatra/support.rb +4 -4
- data/lib/contrast/logger/application.rb +11 -3
- data/lib/contrast/logger/log.rb +7 -2
- data/lib/contrast/utils/assess/tracking_util.rb +48 -3
- data/lib/contrast/utils/duck_utils.rb +0 -10
- data/lib/contrast/utils/env_configuration_item.rb +2 -1
- data/lib/contrast/utils/invalid_configuration_util.rb +20 -21
- data/lib/contrast/utils/inventory_util.rb +0 -7
- data/lib/contrast/utils/sha256_builder.rb +0 -12
- data/lib/contrast/utils/string_utils.rb +10 -5
- data/resources/assess/policy.json +31 -22
- data/ruby-agent.gemspec +21 -18
- data/service_executables/VERSION +1 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
- metadata +71 -30
- data/lib/contrast/agent/assess/finalizers/finalize.rb +0 -21
- data/lib/contrast/extension/assess/assess_extension.rb +0 -145
- data/lib/contrast/utils/boolean_util.rb +0 -30
- data/lib/contrast/utils/freeze_util.rb +0 -32
- data/lib/contrast/utils/gemfile_reader.rb +0 -193
@@ -33,6 +33,64 @@ module Contrast
|
|
33
33
|
NON_KEY_PARTIAL_NAMES.none? { |name| constant_string.index(name) }
|
34
34
|
end
|
35
35
|
|
36
|
+
BYTE_HOLDERS = %i[ARRAY LIST].cs__freeze
|
37
|
+
# Determine if the given value node violates the hardcode key rule
|
38
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
39
|
+
# evaluate
|
40
|
+
# @return [Boolean]
|
41
|
+
def value_node_passes? value_node
|
42
|
+
# If it's a freeze call, then evaluate the entity being frozen
|
43
|
+
value_node = value_node.children[0] if freeze_call?(value_node)
|
44
|
+
# If it's a String being turned into bytes, then it matches key
|
45
|
+
# expectations
|
46
|
+
return true if bytes_call?(value_node)
|
47
|
+
|
48
|
+
type = value_node.type
|
49
|
+
return false unless BYTE_HOLDERS.include?(type)
|
50
|
+
return false unless value_node.children.any?
|
51
|
+
|
52
|
+
# Unless this is an array of literal numerics, we don't match.
|
53
|
+
# That array seems to always end in a nil value, so we allow
|
54
|
+
# those as well.
|
55
|
+
value_node.children.each do |child|
|
56
|
+
next unless child
|
57
|
+
|
58
|
+
return false unless child.cs__is_a?(RubyVM::AbstractSyntaxTree::Node) &&
|
59
|
+
child.type == :LIT &&
|
60
|
+
child.children[0]&.cs__is_a?(Integer)
|
61
|
+
end
|
62
|
+
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
REDACTED_MARKER = ' = [**REDACTED**]'
|
67
|
+
def redacted_marker
|
68
|
+
REDACTED_MARKER
|
69
|
+
end
|
70
|
+
|
71
|
+
# A node is a bytes_call if it's the Node for String#bytes. We care
|
72
|
+
# about this specifically as it's likely to be a common way to
|
73
|
+
# generate a key constant, rather than directly declaring an
|
74
|
+
# integer array.
|
75
|
+
#
|
76
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
77
|
+
# evaluate
|
78
|
+
# @return [Boolean] is this a node for String#bytes or not
|
79
|
+
def bytes_call? value_node
|
80
|
+
return false unless value_node.type == :CALL
|
81
|
+
|
82
|
+
children = value_node.children
|
83
|
+
return false unless children
|
84
|
+
return false unless children.length >= 2
|
85
|
+
|
86
|
+
potential_string_node = children[0]
|
87
|
+
return false unless potential_string_node.cs__is_a?(RubyVM::AbstractSyntaxTree::Node) &&
|
88
|
+
potential_string_node.type == :STR
|
89
|
+
|
90
|
+
children[1] == :bytes
|
91
|
+
end
|
92
|
+
|
93
|
+
# TODO: RUBY-1014 remove `#value_type_passes?` and `#value_passes?`
|
36
94
|
# If the value is a byte array, or at least an array of numbers, it
|
37
95
|
# passes for this rule
|
38
96
|
def value_type_passes? value
|
@@ -49,11 +107,6 @@ module Contrast
|
|
49
107
|
def value_passes? _value
|
50
108
|
true
|
51
109
|
end
|
52
|
-
|
53
|
-
REDACTED_MARKER = ' = [**REDACTED**]'
|
54
|
-
def redacted_marker
|
55
|
-
REDACTED_MARKER
|
56
|
-
end
|
57
110
|
end
|
58
111
|
end
|
59
112
|
end
|
@@ -38,15 +38,18 @@ module Contrast
|
|
38
38
|
NON_PASSWORD_PARTIAL_NAMES.none? { |name| constant_string.index(name) }
|
39
39
|
end
|
40
40
|
|
41
|
-
#
|
42
|
-
|
43
|
-
|
44
|
-
|
41
|
+
# Determine if the given value node violates the hardcode key rule
|
42
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
43
|
+
# evaluate
|
44
|
+
# @return [Boolean]
|
45
|
+
def value_node_passes? value_node
|
46
|
+
# If it's a freeze call, then evaluate the entity being frozen
|
47
|
+
value_node = value_node.children[0] if freeze_call?(value_node)
|
48
|
+
return false unless value_node.type == :STR
|
45
49
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
!probably_property_name?(value)
|
50
|
+
# https://www.rubydoc.info/gems/ruby-internal/Node/STR
|
51
|
+
string = value_node.children[0]
|
52
|
+
!probably_property_name?(string)
|
50
53
|
end
|
51
54
|
|
52
55
|
# If a field name matches an expected password field, we'll check it's
|
@@ -65,6 +68,18 @@ module Contrast
|
|
65
68
|
def redacted_marker
|
66
69
|
REDACTED_MARKER
|
67
70
|
end
|
71
|
+
|
72
|
+
# TODO: RUBY-1014 remove `#value_type_passes?` and `#value_passes?`
|
73
|
+
# If the value is a string, it passes for this rule
|
74
|
+
def value_type_passes? value
|
75
|
+
value.is_a?(String)
|
76
|
+
end
|
77
|
+
|
78
|
+
# If the value probably isn't a property name, it passes for this
|
79
|
+
# rule
|
80
|
+
def value_passes? value
|
81
|
+
!probably_property_name?(value)
|
82
|
+
end
|
68
83
|
end
|
69
84
|
end
|
70
85
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'contrast/agent/assess/policy/trigger_method'
|
4
5
|
require 'contrast/components/interface'
|
5
6
|
require 'contrast/extension/module'
|
6
7
|
|
@@ -9,14 +10,13 @@ module Contrast
|
|
9
10
|
module Assess
|
10
11
|
module Rule
|
11
12
|
module Provider
|
12
|
-
# Hardcoded rules detect if any secret value has been written
|
13
|
-
# sourcecode of the application. To use this base
|
14
|
-
# implement
|
13
|
+
# Hardcoded rules detect if any secret value has been written
|
14
|
+
# directly into the sourcecode of the application. To use this base
|
15
|
+
# class, a provider must implement three methods:
|
15
16
|
# 1) name_passes? : does the constant name match a given value set
|
16
|
-
# 2)
|
17
|
-
#
|
18
|
-
# 3)
|
19
|
-
# 4) redacted_marker : the value to plug in for the obfuscated value
|
17
|
+
# 2) value_node_passes? : does the value of the constant match a
|
18
|
+
# given value set
|
19
|
+
# 3) redacted_marker : the value to plug in for the obfuscated value
|
20
20
|
module HardcodedValueRule
|
21
21
|
include Contrast::Components::Interface
|
22
22
|
access_component :analysis, :app_context, :logging
|
@@ -25,6 +25,7 @@ module Contrast
|
|
25
25
|
!ASSESS.enabled? || ASSESS.rule_disabled?(rule_id)
|
26
26
|
end
|
27
27
|
|
28
|
+
# TODO: RUBY-1014 - remove `#analyze`
|
28
29
|
COMMON_CONSTANTS = %i[
|
29
30
|
CONTRAST_ASSESS_POLICY_STATUS
|
30
31
|
VERSION
|
@@ -69,10 +70,31 @@ module Contrast
|
|
69
70
|
# if it looks like a placeholder / pointer to a config, skip it
|
70
71
|
next unless value_passes?(value)
|
71
72
|
|
72
|
-
|
73
|
+
build_finding(clazz, constant_string)
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
77
|
+
# Parse the file pertaining to the given TracePoint to walk its AST
|
78
|
+
# to determine if a Constant is hardcoded. For our purposes, this
|
79
|
+
# hard coding means directly set rather than as an interpolated
|
80
|
+
# String or through a method call.
|
81
|
+
#
|
82
|
+
# Note: This is a top layer check, we make no assertions about what
|
83
|
+
# the methods or interpolations do. Their presence, even if only
|
84
|
+
# calling a hardcoded thing, causes this check to not report.
|
85
|
+
#
|
86
|
+
# @param trace_point [TracePoint] the TracePoint event created on
|
87
|
+
# the :end of a Module being loaded
|
88
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node] the abstract syntax
|
89
|
+
# tree of the Module defined in the TracePoint end event
|
90
|
+
def parse trace_point, ast
|
91
|
+
return if disabled?
|
92
|
+
|
93
|
+
parse_ast(trace_point.self, ast)
|
94
|
+
rescue StandardError => e
|
95
|
+
logger.error('Unable to parse AST for hardcoded keys', e, module: trace_point.self)
|
96
|
+
end
|
97
|
+
|
76
98
|
# Constants can be variable or classes defined in the given
|
77
99
|
# class. We ONLY want the variables, which should be defined in
|
78
100
|
# the MACRO_CASE (upper case & underscore format)
|
@@ -90,7 +112,57 @@ module Contrast
|
|
90
112
|
|
91
113
|
private
|
92
114
|
|
93
|
-
|
115
|
+
# @param mod [Module] the module to which this AST pertains
|
116
|
+
# @param ast [RubyVM::AbstractSyntaxTree::Node, Object] a node
|
117
|
+
# within the AST, which may be a leaf, so any Object
|
118
|
+
def parse_ast mod, ast
|
119
|
+
return unless ast.cs__is_a?(RubyVM::AbstractSyntaxTree::Node)
|
120
|
+
return unless ast.cs__respond_to?(:children)
|
121
|
+
|
122
|
+
children = ast.children
|
123
|
+
return unless children.any?
|
124
|
+
|
125
|
+
ast.children.each do |child|
|
126
|
+
parse_ast(mod, child)
|
127
|
+
end
|
128
|
+
|
129
|
+
# https://www.rubydoc.info/gems/ruby-internal/Node/CDECL
|
130
|
+
return unless ast.type == :CDECL
|
131
|
+
|
132
|
+
# The CDECL Node has two children, the first being the Constant
|
133
|
+
# name as a symbol, the second as the value to assign to that
|
134
|
+
# constant
|
135
|
+
children = ast.children
|
136
|
+
name = children[0].to_s
|
137
|
+
# If that constant name doesn't pass our checks, move on.
|
138
|
+
return unless name_passes?(name)
|
139
|
+
|
140
|
+
value = children[1]
|
141
|
+
# The assignment node could be a direct value or a call of some
|
142
|
+
# sort. We leave it to each rule to properly handle these nodes.
|
143
|
+
return unless value_node_passes?(value)
|
144
|
+
|
145
|
+
build_finding(mod, name)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Constants can be set as frozen directly. We need to account for
|
149
|
+
# this change as it means the Node given to the :CDECL call will be
|
150
|
+
# a :CALL, not a constant.
|
151
|
+
#
|
152
|
+
# @param value_node [RubyVM::AbstractSyntaxTree::Node] the node to
|
153
|
+
# evaluate
|
154
|
+
# @return [Boolean] is this a freeze call or not
|
155
|
+
def freeze_call? value_node
|
156
|
+
return false unless value_node.type == :CALL
|
157
|
+
|
158
|
+
children = value_node.children
|
159
|
+
return false unless children
|
160
|
+
return false unless children.length >= 2
|
161
|
+
|
162
|
+
children[1] == :freeze
|
163
|
+
end
|
164
|
+
|
165
|
+
def build_finding clazz, constant_string
|
94
166
|
class_name = clazz.cs__name
|
95
167
|
|
96
168
|
finding = Contrast::Api::Dtm::Finding.new
|
@@ -104,13 +176,10 @@ module Contrast
|
|
104
176
|
hash = Contrast::Utils::HashDigest.generate_class_scanning_hash(finding)
|
105
177
|
finding.hash_code = Contrast::Utils::StringUtils.protobuf_safe_string(hash)
|
106
178
|
finding.preflight = Contrast::Utils::PreflightUtil.create_preflight(finding)
|
107
|
-
|
108
|
-
activity = Contrast::Api::Dtm::Activity.new
|
109
|
-
activity.findings << finding
|
110
|
-
|
111
|
-
Contrast::Agent.messaging_queue.send_event_eventually(activity)
|
179
|
+
Contrast::Agent::Assess::Policy::TriggerMethod.report_finding(finding)
|
112
180
|
rescue StandardError => e
|
113
181
|
logger.error('Unable to build a finding for Hardcoded Rule', e)
|
182
|
+
nil
|
114
183
|
end
|
115
184
|
end
|
116
185
|
end
|
@@ -14,7 +14,7 @@ module Contrast
|
|
14
14
|
|
15
15
|
# Initialize a new tag
|
16
16
|
#
|
17
|
-
# @param label [String] the
|
17
|
+
# @param label [String] the label of the tag
|
18
18
|
# @param length [Integer] the length of the string described with this
|
19
19
|
# tag
|
20
20
|
# @param start_idx [Integer] (0) the starting position in the string for
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Copyright (c) 2020 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details.
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'contrast/agent/assess/finalizers/hash'
|
5
|
+
|
6
|
+
module Contrast
|
7
|
+
module Agent
|
8
|
+
module Assess
|
9
|
+
# How we track the Assess properties attached to objects
|
10
|
+
#
|
11
|
+
# Finalized objects should run through this class as the Finalizers
|
12
|
+
# have tightly coupled dependencies on each other.
|
13
|
+
class Tracker
|
14
|
+
PROPERTIES_HASH = Contrast::Agent::Assess::Finalizers::Hash.new
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def properties source
|
18
|
+
return unless trackable?(source)
|
19
|
+
|
20
|
+
PROPERTIES_HASH[source] ||= Contrast::Agent::Assess::Properties.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def trackable? source
|
24
|
+
PROPERTIES_HASH.trackable?(source)
|
25
|
+
end
|
26
|
+
|
27
|
+
def tracked? source
|
28
|
+
PROPERTIES_HASH.tracked?(source)
|
29
|
+
end
|
30
|
+
|
31
|
+
def pre_freeze source
|
32
|
+
PROPERTIES_HASH.pre_freeze(source)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Copy the properties from one object to the next, assuming the
|
36
|
+
# target does not already have its own properties.
|
37
|
+
#
|
38
|
+
# @param source [Object] the instance from which to copy properties
|
39
|
+
# @param target [Object] the instance to which to copy properties
|
40
|
+
def copy source, target
|
41
|
+
PROPERTIES_HASH[target] ||= properties(source).dup
|
42
|
+
end
|
43
|
+
|
44
|
+
# Duplicate the given object, returning the duplicate after copying
|
45
|
+
# the properties of the original and storing them as the properties
|
46
|
+
# of the duplicate.
|
47
|
+
#
|
48
|
+
# @param source [Object] the thing to duplicate
|
49
|
+
# @return [Object] the duplicate of the original, or the original if
|
50
|
+
# it does not respond to duplication
|
51
|
+
def duplicate source
|
52
|
+
return source unless source
|
53
|
+
|
54
|
+
duplicate = source.dup
|
55
|
+
PROPERTIES_HASH[duplicate] ||= PROPERTIES_HASH[source].dup
|
56
|
+
duplicate
|
57
|
+
rescue StandardError
|
58
|
+
source
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
require 'contrast/agent/assess/finalizers/freeze'
|
@@ -11,11 +11,11 @@ module Contrast
|
|
11
11
|
access_component :logging
|
12
12
|
def self.exit_hook
|
13
13
|
@_exit_hook ||= begin
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
at_exit do
|
15
|
+
on_exit
|
16
|
+
end
|
17
|
+
true
|
18
|
+
end
|
19
19
|
end
|
20
20
|
|
21
21
|
# Actions to take when a process exits. Typically called from our
|
@@ -37,7 +37,7 @@ module Contrast
|
|
37
37
|
# being phased out with support for those language versions.
|
38
38
|
class ClassReopener
|
39
39
|
include Contrast::Components::Interface
|
40
|
-
access_component :logging
|
40
|
+
access_component :logging, :scope
|
41
41
|
|
42
42
|
END_NEW_LINE = "end\n"
|
43
43
|
PROTECTED_WITH_NEW_LINE = "protected\n"
|
@@ -101,11 +101,13 @@ module Contrast
|
|
101
101
|
# Evaluate the patches that have been staged for this class, replacing
|
102
102
|
# the method definitions with those our rewrite.
|
103
103
|
def commit_patches
|
104
|
-
|
104
|
+
with_contrast_scope do
|
105
|
+
return unless staged_changes?
|
105
106
|
|
106
|
-
|
107
|
-
|
108
|
-
|
107
|
+
content = build_content
|
108
|
+
valid = Ripper.sexp(content)
|
109
|
+
unbound_eval(class_name, content) if !!valid && !class_name.empty?
|
110
|
+
end
|
109
111
|
end
|
110
112
|
|
111
113
|
# Find the sourcecode of the method at the given location and return it
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Copyright (c) 2020 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 Agent
|
6
|
+
# Namespace used for inventory behavior
|
7
|
+
module Inventory
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'contrast/agent/inventory/dependencies'
|
13
|
+
require 'contrast/agent/inventory/gemfile_digest_cache'
|
14
|
+
require 'contrast/agent/inventory/dependency_usage_analysis'
|
15
|
+
require 'contrast/agent/inventory/dependency_analysis'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Copyright (c) 2020 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 Agent
|
6
|
+
module Inventory
|
7
|
+
# this module is included in classes that need access to the applications dependencies
|
8
|
+
module Dependencies
|
9
|
+
CONTRAST_AGENT = 'contrast-agent'
|
10
|
+
|
11
|
+
# the #clone is necessary here, as a require in another thread could
|
12
|
+
# potentially result in adding a key to the loaded_specs hash during
|
13
|
+
# iteration. (as in RUBY-330)
|
14
|
+
# this takes care of filtering out contrast-only dependencies
|
15
|
+
def loaded_specs
|
16
|
+
specs = Gem.loaded_specs.clone
|
17
|
+
specs.delete_if { |name, _v| contrast?(name) }
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def contrast_gems
|
23
|
+
@_contrast_gems ||= find_contrast_gems
|
24
|
+
end
|
25
|
+
|
26
|
+
def contrast? name
|
27
|
+
contrast_gems.include?(name)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Go through all dependents, given as a pair from the DependencyList: `dependency`
|
31
|
+
# is the dependency itself, filled with all its specs. `dependents` is the array of reverse
|
32
|
+
# dependencies for the aforementioned dependency. If the dependency is also in contrast_dep_set,
|
33
|
+
# then contrast depends on it. If its array of dependents is 1, then contrast is the
|
34
|
+
# only dependency in that list. Since only contrast depends on it, we should ignore it.
|
35
|
+
def find_contrast_gems
|
36
|
+
ignore = Set.new([CONTRAST_AGENT])
|
37
|
+
contrast_specs = Gem::DependencyList.from_specs.specs.find do |dependency|
|
38
|
+
dependency.name == CONTRAST_AGENT
|
39
|
+
end
|
40
|
+
contrast_dep_set = contrast_specs.dependencies.map(&:name).to_set
|
41
|
+
|
42
|
+
Gem::DependencyList.from_specs.spec_predecessors.each_pair do |dependency, dependents|
|
43
|
+
ignore.add(dependency.name) if contrast_dep_set.include?(dependency.name) && dependents.length == 1
|
44
|
+
end
|
45
|
+
ignore
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|