contrast-agent 4.12.0 → 4.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +1 -0
  3. data/ext/cs__assess_module/cs__assess_module.c +48 -0
  4. data/ext/cs__assess_module/cs__assess_module.h +7 -0
  5. data/ext/cs__common/cs__common.c +5 -0
  6. data/ext/cs__common/cs__common.h +8 -0
  7. data/ext/cs__contrast_patch/cs__contrast_patch.c +16 -1
  8. data/ext/cs__os_information/cs__os_information.c +31 -0
  9. data/ext/cs__os_information/cs__os_information.h +7 -0
  10. data/ext/cs__os_information/extconf.rb +5 -0
  11. data/lib/contrast/agent/assess/policy/policy_node.rb +6 -6
  12. data/lib/contrast/agent/assess/policy/policy_scanner.rb +5 -0
  13. data/lib/contrast/agent/assess/policy/propagation_method.rb +2 -116
  14. data/lib/contrast/agent/assess/policy/propagation_node.rb +4 -4
  15. data/lib/contrast/agent/assess/policy/propagator/center.rb +1 -1
  16. data/lib/contrast/agent/assess/policy/propagator/substitution.rb +2 -154
  17. data/lib/contrast/agent/assess/policy/source_method.rb +2 -71
  18. data/lib/contrast/agent/assess/policy/trigger_method.rb +45 -110
  19. data/lib/contrast/agent/assess/policy/trigger_node.rb +14 -6
  20. data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
  21. data/lib/contrast/agent/assess/property/tagged.rb +53 -185
  22. data/lib/contrast/agent/assess/rule/provider/hardcoded_value_rule.rb +40 -6
  23. data/lib/contrast/agent/deadzone/policy/policy.rb +1 -1
  24. data/lib/contrast/agent/inventory/dependency_usage_analysis.rb +1 -0
  25. data/lib/contrast/agent/metric_telemetry_event.rb +26 -0
  26. data/lib/contrast/agent/middleware.rb +14 -62
  27. data/lib/contrast/agent/patching/policy/method_policy.rb +3 -89
  28. data/lib/contrast/agent/patching/policy/method_policy_extend.rb +111 -0
  29. data/lib/contrast/agent/patching/policy/patch.rb +28 -235
  30. data/lib/contrast/agent/patching/policy/patcher.rb +14 -49
  31. data/lib/contrast/agent/reporting/report.rb +21 -0
  32. data/lib/contrast/agent/reporting/reporter.rb +142 -0
  33. data/lib/contrast/agent/reporting/reporting_events/finding.rb +90 -0
  34. data/lib/contrast/agent/reporting/reporting_events/preflight.rb +25 -0
  35. data/lib/contrast/agent/reporting/reporting_events/preflight_message.rb +56 -0
  36. data/lib/contrast/agent/reporting/reporting_events/reporting_event.rb +37 -0
  37. data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +127 -0
  38. data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +168 -0
  39. data/lib/contrast/agent/reporting/reporting_utilities/reporting_storage.rb +66 -0
  40. data/lib/contrast/agent/request.rb +2 -81
  41. data/lib/contrast/agent/request_context.rb +4 -128
  42. data/lib/contrast/agent/request_context_extend.rb +138 -0
  43. data/lib/contrast/agent/request_handler.rb +7 -3
  44. data/lib/contrast/agent/response.rb +2 -73
  45. data/lib/contrast/agent/startup_metrics_telemetry_event.rb +94 -0
  46. data/lib/contrast/agent/static_analysis.rb +5 -3
  47. data/lib/contrast/agent/telemetry.rb +137 -0
  48. data/lib/contrast/agent/telemetry_event.rb +33 -0
  49. data/lib/contrast/agent/thread_watcher.rb +66 -11
  50. data/lib/contrast/agent/version.rb +1 -1
  51. data/lib/contrast/agent.rb +21 -0
  52. data/lib/contrast/api/communication/connection_status.rb +10 -7
  53. data/lib/contrast/api/communication/messaging_queue.rb +37 -3
  54. data/lib/contrast/api/communication/response_processor.rb +15 -8
  55. data/lib/contrast/api/communication/service_lifecycle.rb +13 -3
  56. data/lib/contrast/api/communication/socket.rb +6 -8
  57. data/lib/contrast/api/communication/socket_client.rb +29 -12
  58. data/lib/contrast/api/communication/speedracer.rb +37 -1
  59. data/lib/contrast/api/communication/tcp_socket.rb +4 -3
  60. data/lib/contrast/api/communication/unix_socket.rb +1 -0
  61. data/lib/contrast/api/decorators/finding.rb +45 -0
  62. data/lib/contrast/components/api.rb +90 -0
  63. data/lib/contrast/components/app_context.rb +10 -41
  64. data/lib/contrast/components/app_context_extend.rb +78 -0
  65. data/lib/contrast/components/base.rb +23 -0
  66. data/lib/contrast/components/config.rb +92 -13
  67. data/lib/contrast/components/contrast_service.rb +11 -0
  68. data/lib/contrast/components/sampling.rb +2 -2
  69. data/lib/contrast/config/agent_configuration.rb +1 -1
  70. data/lib/contrast/config/api_configuration.rb +27 -0
  71. data/lib/contrast/config/api_proxy_configuration.rb +14 -0
  72. data/lib/contrast/config/application_configuration.rb +2 -3
  73. data/lib/contrast/config/assess_configuration.rb +3 -3
  74. data/lib/contrast/config/base_configuration.rb +17 -28
  75. data/lib/contrast/config/certification_configuration.rb +15 -0
  76. data/lib/contrast/config/env_variables.rb +18 -0
  77. data/lib/contrast/config/heap_dump_configuration.rb +6 -6
  78. data/lib/contrast/config/inventory_configuration.rb +1 -5
  79. data/lib/contrast/config/protect_rule_configuration.rb +1 -1
  80. data/lib/contrast/config/request_audit_configuration.rb +18 -0
  81. data/lib/contrast/config/root_configuration.rb +1 -0
  82. data/lib/contrast/config/ruby_configuration.rb +6 -6
  83. data/lib/contrast/config/service_configuration.rb +2 -2
  84. data/lib/contrast/config.rb +1 -1
  85. data/lib/contrast/configuration.rb +4 -2
  86. data/lib/contrast/extension/assess/array.rb +5 -7
  87. data/lib/contrast/extension/thread.rb +31 -12
  88. data/lib/contrast/framework/manager.rb +22 -44
  89. data/lib/contrast/framework/manager_extend.rb +50 -0
  90. data/lib/contrast/framework/rails/patch/action_controller_live_buffer.rb +9 -6
  91. data/lib/contrast/framework/rails/patch/support.rb +31 -29
  92. data/lib/contrast/framework/rails/railtie.rb +1 -1
  93. data/lib/contrast/framework/sinatra/support.rb +2 -1
  94. data/lib/contrast/logger/application.rb +4 -0
  95. data/lib/contrast/logger/log.rb +8 -103
  96. data/lib/contrast/utils/assess/propagation_method_utils.rb +129 -0
  97. data/lib/contrast/utils/assess/property/tagged_utils.rb +165 -0
  98. data/lib/contrast/utils/assess/source_method_utils.rb +83 -0
  99. data/lib/contrast/utils/assess/tracking_util.rb +20 -15
  100. data/lib/contrast/utils/assess/trigger_method_utils.rb +138 -0
  101. data/lib/contrast/utils/class_util.rb +18 -14
  102. data/lib/contrast/utils/exclude_key.rb +20 -0
  103. data/lib/contrast/utils/findings.rb +62 -0
  104. data/lib/contrast/utils/hash_digest.rb +10 -73
  105. data/lib/contrast/utils/hash_digest_extend.rb +86 -0
  106. data/lib/contrast/utils/head_dump_utils_extend.rb +74 -0
  107. data/lib/contrast/utils/heap_dump_util.rb +2 -65
  108. data/lib/contrast/utils/invalid_configuration_util.rb +29 -0
  109. data/lib/contrast/utils/io_util.rb +1 -1
  110. data/lib/contrast/utils/log_utils.rb +108 -0
  111. data/lib/contrast/utils/metrics_hash.rb +59 -0
  112. data/lib/contrast/utils/middleware_utils.rb +87 -0
  113. data/lib/contrast/utils/net_http_base.rb +158 -0
  114. data/lib/contrast/utils/object_share.rb +1 -0
  115. data/lib/contrast/utils/os.rb +23 -0
  116. data/lib/contrast/utils/patching/policy/patch_utils.rb +232 -0
  117. data/lib/contrast/utils/patching/policy/patcher_utils.rb +54 -0
  118. data/lib/contrast/utils/request_utils.rb +88 -0
  119. data/lib/contrast/utils/response_utils.rb +97 -0
  120. data/lib/contrast/utils/substitution_utils.rb +167 -0
  121. data/lib/contrast/utils/tag_util.rb +9 -9
  122. data/lib/contrast/utils/telemetry.rb +79 -0
  123. data/lib/contrast/utils/telemetry_client.rb +90 -0
  124. data/lib/contrast/utils/telemetry_identifier.rb +130 -0
  125. data/lib/contrast.rb +18 -0
  126. data/ruby-agent.gemspec +7 -6
  127. data/service_executables/VERSION +1 -1
  128. data/service_executables/linux/contrast-service +0 -0
  129. data/service_executables/mac/contrast-service +0 -0
  130. metadata +69 -22
  131. data/lib/contrast/config/default_value.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a0a50913c5c680462f9afd79070ca1f3d46d09d66ffdb943b1dc778f84830d6
4
- data.tar.gz: bb3254c6a2cdc2b4add4e09b4bf1ed63c6a640f0dd82824db2769b911aacf96b
3
+ metadata.gz: 8ab9eae65aeefbeb3c12102f71287c60856783238067a7c461fcf534aaa65712
4
+ data.tar.gz: 1fe74d85374374f40a11c47e44ab0da1042a3f99d607fdfd697a45cc321b45b0
5
5
  SHA512:
6
- metadata.gz: 78d5e66ee3ab7408e94348f6c2998e73cd78227a8b75cffa7add261b771092d470431ce8e44357dfb7d1a178e03e5033efc9f7a4c974a979f9dea88b2c55a3ec
7
- data.tar.gz: d20f41b563778d84e63e4db7a9399e666a85fe1a2343772258f5248cd139f02e6a6bea33e51d9e5a0f954b1e2b4683ec08f3db7a52ab5a6990f7d725d2bc3dea
6
+ metadata.gz: df020b9d0ff09e61107e280f6e76411f9a9b75c1c94f7082ef52d487e5f88b940e75996dd869d648ac0af654f5f84e48b1f691fdcc9e5f294a565d2bb1a39b7d
7
+ data.tar.gz: 8bd869bfacbb82aea7b0cf55ff806fded4bae5c6b0454185c4c422bdd14ee07fd1edb3f4b843e702765a4693b51697a0497129d73d3d3652f32cc50056ed783b
data/.simplecov CHANGED
@@ -4,5 +4,6 @@
4
4
  SimpleCov.minimum_coverage line: 94.75
5
5
  SimpleCov.start do
6
6
  add_filter '/spec/'
7
+ add_filter '/lib/contrast/extension/assess/erb.rb'
7
8
  enable_coverage :branch
8
9
  end
@@ -57,6 +57,45 @@ contrast_assess_module_module_eval(const int argc, const VALUE *argv,
57
57
  return ret;
58
58
  }
59
59
 
60
+ VALUE
61
+ contrast_assess_module_prepend(const int argc, const VALUE *argv,
62
+ const VALUE self) {
63
+
64
+ rb_prepend_module(self, argv[0]);
65
+
66
+ VALUE module_at;
67
+ VALUE rb_incl_in_mod_ary = rb_funcall(self, rb_intern("included_in"), 0);
68
+
69
+ if (RB_TYPE_P(rb_incl_in_mod_ary, T_ARRAY)) {
70
+ int i = 0;
71
+ int size = rb_funcall(rb_incl_in_mod_ary, rb_intern("length"), 0);
72
+ for (i = 0; i < size; ++i) {
73
+ module_at = rb_ary_entry(rb_incl_in_mod_ary, i);
74
+ if (RB_TYPE_P(module_at, T_MODULE)) {
75
+ rb_include_module(module_at, argv[0]);
76
+ }
77
+ }
78
+ }
79
+ return self;
80
+ }
81
+
82
+ VALUE
83
+ contrast_assess_module_included(const int argc, const VALUE *argv,
84
+ const VALUE self) {
85
+ VALUE frozen;
86
+ if (RB_TYPE_P(self, T_MODULE)) {
87
+ // check if frozen
88
+ frozen = rb_funcall(self, rb_intern("cs__frozen?"), 0);
89
+ if (frozen == Qfalse) {
90
+ VALUE ary = rb_funcall(self, rb_intern("included_in"), 0);
91
+ if (RB_TYPE_P(ary, T_ARRAY)) {
92
+ rb_ary_push(ary, argv[0]);
93
+ }
94
+ }
95
+ }
96
+ return self;
97
+ }
98
+
60
99
  void Init_cs__assess_module(void) {
61
100
  module_eval_trigger =
62
101
  rb_define_class_under(core_assess, "EvalTrigger", rb_cObject);
@@ -76,4 +115,13 @@ void Init_cs__assess_module(void) {
76
115
 
77
116
  contrast_register_patch("Module", "module_eval",
78
117
  contrast_assess_module_module_eval);
118
+ /*
119
+ * We patch these for better ancestors handling, and only for older ruby versions.
120
+ */
121
+ if (rb_ver_below_three()) {
122
+ contrast_register_patch("Module", "included",
123
+ contrast_assess_module_included);
124
+ contrast_register_patch("Module", "prepend",
125
+ contrast_assess_module_prepend);
126
+ }
79
127
  }
@@ -24,5 +24,12 @@ contrast_assess_module_class_eval(const int argc, const VALUE *argv,
24
24
  VALUE
25
25
  contrast_assess_module_module_eval(const int argc, const VALUE *argv,
26
26
  const VALUE mod);
27
+ VALUE
28
+ contrast_assess_module_prepend(const int argc, const VALUE *argv,
29
+ const VALUE self);
30
+
31
+ VALUE
32
+ contrast_assess_module_included(const int argc, const VALUE *argv,
33
+ const VALUE mod);
27
34
 
28
35
  void Init_cs__assess_module(void);
@@ -144,6 +144,11 @@ _contrast_register_patch(const char *module_name, const char *method_name,
144
144
  return SYM2ID(underlying_method_name);
145
145
  }
146
146
 
147
+ int rb_ver_below_three() {
148
+ int ruby_version = FIX2INT(rb_funcall(rb_const_get(rb_cObject, rb_intern("RUBY_VERSION")), rb_intern("to_i"), 0));
149
+ return ruby_version < 3;
150
+ }
151
+
147
152
  void Init_cs__common(void) {
148
153
  cs__send_method = rb_intern("send");
149
154
  cs__alias_method_sym = ID2SYM(rb_intern("alias_method"));
@@ -34,6 +34,14 @@ static VALUE rb_sym_alias_singleton;
34
34
  static VALUE rb_sym_prepend_instance;
35
35
  static VALUE rb_sym_prepend_singleton;
36
36
 
37
+ /*
38
+ * Check if ruby version is < 3.0.0.
39
+ * We are using this for handling ancestors of included modules.
40
+ * Since this is fixed after Ruby 3.0.0 we should remove this after
41
+ * dropping support for older versions, as no longer needed.
42
+ */
43
+ int rb_ver_below_three();
44
+
37
45
  void patch_via_funchook(void *original_function, void *hook_function);
38
46
 
39
47
  void contrast_alias_method(const VALUE target, const char *to,
@@ -43,7 +43,7 @@ VALUE contrast_patch_call_original(const VALUE *args) {
43
43
  if (rb_block_given_p()) {
44
44
  return rb_funcall_with_block_kw(object, method_id, argc, params, rb_block_proc(), RB_PASS_CALLED_KEYWORDS);
45
45
  } else {
46
- return rb_funcallv_kw(object, method_id, argc, params, RB_PASS_CALLED_KEYWORDS);
46
+ return rb_funcallv_kw(object, method_id, argc, params, RB_PASS_CALLED_KEYWORDS);
47
47
  }
48
48
  /* Ruby < 2.7 */
49
49
  #else
@@ -448,6 +448,21 @@ VALUE contrast_patch_prepend(const VALUE self, const VALUE originalModule,
448
448
  -1);
449
449
  }
450
450
  rb_prepend_module(originalModule, module);
451
+
452
+ if (rb_ver_below_three()) {
453
+ VALUE module_at;
454
+ VALUE rb_incl_in_mod_ary = rb_funcall(originalModule, rb_intern("included_in"), 0);
455
+ if (RB_TYPE_P(rb_incl_in_mod_ary, T_ARRAY)) {
456
+ int i = 0;
457
+ int size = rb_funcall(rb_incl_in_mod_ary, rb_intern("length"), 0);
458
+ for (i = 0; i < size; ++i) {
459
+ module_at = rb_ary_entry(rb_incl_in_mod_ary, i);
460
+ if (RB_TYPE_P(module_at, T_MODULE)) {
461
+ rb_include_module(module_at, module);
462
+ }
463
+ }
464
+ }
465
+ }
451
466
  return Qtrue;
452
467
  }
453
468
 
@@ -0,0 +1,31 @@
1
+ /* Copyright (c) 2021 Contrast Security, Inc. See
2
+ * https://www.contrastsecurity.com/enduser-terms-0317a for more details. */
3
+
4
+ #include "cs__os_information.h"
5
+ #include <dlfcn.h>
6
+ #include <ruby.h>
7
+ #include <sys/utsname.h>
8
+
9
+ VALUE contrast, utils, os;
10
+
11
+ VALUE contrast_get_system_information()
12
+ {
13
+ struct utsname uname_pointer;
14
+
15
+ uname (&uname_pointer);
16
+
17
+ VALUE rb_data_hash = rb_hash_new();
18
+ rb_hash_aset(rb_data_hash, rb_str_new2("os_type"), rb_str_new2(uname_pointer.sysname));
19
+ rb_hash_aset(rb_data_hash, rb_str_new2("os_version"), rb_str_new2(uname_pointer.release));
20
+ rb_hash_aset(rb_data_hash, rb_str_new2("os_complete_version"), rb_str_new2(uname_pointer.version));
21
+ rb_hash_aset(rb_data_hash, rb_str_new2("os_arch"), rb_str_new2(uname_pointer.machine));
22
+ return rb_data_hash;
23
+ }
24
+
25
+ void Init_cs__os_information(void)
26
+ {
27
+ contrast = rb_define_module("Contrast");
28
+ utils = rb_define_module_under(contrast, "Utils");
29
+ os = rb_define_module_under(utils, "OS");
30
+ rb_define_module_function(os, "get_system_information", contrast_get_system_information, 0);
31
+ }
@@ -0,0 +1,7 @@
1
+ #include <ruby.h>
2
+
3
+ extern VALUE contrast, utils, os;
4
+
5
+ VALUE contrast_get_system_information();
6
+
7
+ void Init_cs__os_information(void);
@@ -0,0 +1,5 @@
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
+ $TO_MAKE = File.basename(__dir__)
5
+ require_relative '../extconf_common'
@@ -135,14 +135,14 @@ module Contrast
135
135
 
136
136
  converted = []
137
137
  markers.split(Contrast::Utils::ObjectShare::COMMA).each do |t|
138
- case t
139
- when Contrast::Utils::ObjectShare::OBJECT_KEY,
138
+ converted << case t
139
+ when Contrast::Utils::ObjectShare::OBJECT_KEY,
140
140
  Contrast::Utils::ObjectShare::RETURN_KEY
141
141
 
142
- converted << t
143
- else
144
- converted << Integer(t[1..-1])
145
- end
142
+ t
143
+ else
144
+ Integer(t[1..-1])
145
+ end
146
146
  end
147
147
  converted
148
148
  end
@@ -32,8 +32,13 @@ module Contrast
32
32
  mod = trace_point.self
33
33
  return if mod.cs__frozen? || mod.singleton_class?
34
34
 
35
+ different_ruby_version trace_point, provider_values, mod
36
+ end
37
+
38
+ def different_ruby_version trace_point, provider_values, mod
35
39
  # TODO: RUBY-1014 - remove non-AST approach
36
40
  if RUBY_VERSION >= '2.6.0'
41
+ # TODO: RUBY-714 EOL 2.5. That check will be removed and the code will be brought back in here.
37
42
  ast = RubyVM::AbstractSyntaxTree.parse_file(trace_point.path)
38
43
  provider_values.each do |provider|
39
44
  provider.parse(trace_point, ast)
@@ -7,6 +7,7 @@ require 'contrast/agent/assess/policy/propagator'
7
7
  require 'contrast/components/logger'
8
8
  require 'contrast/utils/object_share'
9
9
  require 'contrast/utils/sha256_builder'
10
+ require 'contrast/utils/assess/propagation_method_utils'
10
11
 
11
12
  module Contrast
12
13
  module Agent
@@ -16,35 +17,9 @@ module Contrast
16
17
  # untrusted value. In general, these methods work on the String class or a holder of Strings.
17
18
  module PropagationMethod
18
19
  extend Contrast::Components::Logger::InstanceMethods
19
-
20
- APPEND_ACTION = 'APPEND'
21
- CENTER_ACTION = 'CENTER'
22
- INSERT_ACTION = 'INSERT'
23
- KEEP_ACTION = 'KEEP'
24
- NEXT_ACTION = 'NEXT'
25
- NOOP_ACTION = 'NOOP'
26
- PREPEND_ACTION = 'PREPEND'
27
- REPLACE_ACTION = 'REPLACE'
28
- REMOVE_ACTION = 'REMOVE'
29
- REVERSE_ACTION = 'REVERSE'
30
- SPLAT_ACTION = 'SPLAT'
31
- SPLIT_ACTION = 'SPLIT'
32
- DB_WRITE_ACTION = 'DB_WRITE'
33
- CUSTOM_ACTION = 'CUSTOM'
20
+ extend Contrast::Utils::Assess::PropagationMethodUtils
34
21
 
35
22
  class << self
36
- def determine_target propagation_node, ret, object, args
37
- target = propagation_node.targets[0]
38
- case target
39
- when Contrast::Utils::ObjectShare::OBJECT_KEY
40
- object
41
- when Contrast::Utils::ObjectShare::RETURN_KEY
42
- ret
43
- else
44
- args[target]
45
- end
46
- end
47
-
48
23
  # @param method_policy [Contrast::Agent::Patching::Policy::MethodPolicy] the policy that governs the
49
24
  # patches to this method
50
25
  # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
@@ -65,21 +40,6 @@ module Contrast
65
40
  PropagationMethod.apply_propagator(propagation_node, preshift, target, object, ret, args, block)
66
41
  end
67
42
 
68
- PROPAGATION_ACTIONS = {
69
- APPEND_ACTION => Contrast::Agent::Assess::Policy::Propagator::Append,
70
- CENTER_ACTION => Contrast::Agent::Assess::Policy::Propagator::Center,
71
- INSERT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Insert,
72
- KEEP_ACTION => Contrast::Agent::Assess::Policy::Propagator::Keep,
73
- NEXT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Next,
74
- NOOP_ACTION => nil,
75
- PREPEND_ACTION => Contrast::Agent::Assess::Policy::Propagator::Prepend,
76
- REPLACE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Replace,
77
- REMOVE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Remove,
78
- REVERSE_ACTION => Contrast::Agent::Assess::Policy::Propagator::Reverse,
79
- SPLAT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Splat,
80
- SPLIT_ACTION => Contrast::Agent::Assess::Policy::Propagator::Split
81
- }.cs__freeze
82
-
83
43
  # I lied above. We had to figure out what the target of the propagation was. Now that we know, we'll
84
44
  # actually do things to it. Note that the return of this method will replace the original return of the
85
45
  # patched function unless it is nil, so be sure you're returning what you intend.
@@ -116,80 +76,6 @@ module Contrast
116
76
  nil
117
77
  end
118
78
 
119
- # Custom actions tend to be the more complex of our propagations. Often, the method has to make decisions
120
- # about the target based on the context with which the method was called. As such, defer determining if the
121
- # target is valid to that method.
122
- #
123
- # In all other cases, a target is valid for propagation if it is not nil
124
- #
125
- # @param target [Object] the thing to which to propagate
126
- # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
127
- # propagation event.
128
- # @return [Boolean]
129
- def valid_target? target, propagation_node
130
- return true if propagation_node.action == CUSTOM_ACTION
131
-
132
- !!target
133
- end
134
-
135
- ZERO_LENGTH_ACTIONS = [DB_WRITE_ACTION, CUSTOM_ACTION, KEEP_ACTION, REPLACE_ACTION, SPLAT_ACTION].cs__freeze
136
- # If the action required needs a length and the target does not have one, the length is not valid
137
- #
138
- # @param target [Object] the thing to which to propagate
139
- # @param action [String] the name of the action taken during this propagation
140
- # @return [Boolean]
141
- def valid_length? target, action
142
- return true if ZERO_LENGTH_ACTIONS.include?(action)
143
-
144
- if Contrast::Utils::DuckUtils.quacks_to?(target, :length)
145
- target.length != 0 # rubocop:disable Style/ZeroLengthPredicate
146
- else
147
- !target.to_s.empty?
148
- end
149
- end
150
-
151
- # Before we do any work, we should check if we even need to. If the source and target of this patcher are
152
- # not tracked, there's no need to do anything. A copy of nothing is still nothing.
153
- #
154
- # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
155
- # propagation event.
156
- # @param preshift [Contrast::Agent::Assess::PreShift] The capture of the state of the code just prior to
157
- # the invocation of the patched method.
158
- # @param target [Object] the thing to which to propagate
159
- # @return [Boolean]
160
- def can_propagate? propagation_node, preshift, target
161
- return false unless appropriate_target?(propagation_node, target)
162
- return true if Contrast::Utils::Assess::TrackingUtil.tracked?(target)
163
- return false unless preshift
164
-
165
- propagation_node.sources.each do |source|
166
- case source
167
- when Contrast::Utils::ObjectShare::OBJECT_KEY
168
- return true if Contrast::Utils::Assess::TrackingUtil.tracked?(preshift.object)
169
- else
170
- # has to be P, there's no ret source type (yet? ever?)
171
- return true if preshift.args && Contrast::Utils::Assess::TrackingUtil.tracked?(preshift.args[source])
172
- end
173
- end
174
- false
175
- end
176
-
177
- # We cannot propagate to frozen things that have not been updated to work with our property tracking,
178
- # unless they're duplicable and the return. We probably shouldn't propagate to frozen things at all, as
179
- # they're supposed to be immutable, but third parties do jenky things, so allow it as long as it is safe to
180
- # do.
181
- #
182
- # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
183
- # propagation event.
184
- # @param target [Object] the Target to which to propagate.
185
- # @return [Boolean] if the target can be propagated to
186
- def appropriate_target? propagation_node, target
187
- # special handle Returns b/c we can do unfreezing magic during propagation
188
- return true if propagation_node.targets[0] == Contrast::Utils::ObjectShare::RETURN_KEY
189
-
190
- Contrast::Agent::Assess::Tracker.trackable?(target)
191
- end
192
-
193
79
  # If this patcher has tags, apply them to the entire target
194
80
  # @param propagation_node [Contrast::Agent::Assess::Policy::PropagationNode] the node that governs this
195
81
  # propagation event.
@@ -95,8 +95,8 @@ module Contrast
95
95
 
96
96
  def needs_object?
97
97
  if @_needs_object.nil?
98
- @_needs_object = action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION ||
99
- action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION ||
98
+ @_needs_object = action == Contrast::Utils::Assess::PropagationMethodUtils::CUSTOM_ACTION ||
99
+ action == Contrast::Utils::Assess::PropagationMethodUtils::DB_WRITE_ACTION ||
100
100
  sources.any?(Contrast::Utils::ObjectShare::OBJECT_KEY) ||
101
101
  targets.any?(Contrast::Utils::ObjectShare::OBJECT_KEY)
102
102
  end
@@ -105,8 +105,8 @@ module Contrast
105
105
 
106
106
  def needs_args?
107
107
  if @_needs_args.nil?
108
- @_needs_args = action == Contrast::Agent::Assess::Policy::PropagationMethod::CUSTOM_ACTION ||
109
- action == Contrast::Agent::Assess::Policy::PropagationMethod::DB_WRITE_ACTION ||
108
+ @_needs_args = action == Contrast::Utils::Assess::PropagationMethodUtils::CUSTOM_ACTION ||
109
+ action == Contrast::Utils::Assess::PropagationMethodUtils::DB_WRITE_ACTION ||
110
110
  sources.any? { |source| source.is_a?(Integer) || source.is_a?(Symbol) } ||
111
111
  targets.any? { |target| target.is_a?(Integer) || target.is_a?(Symbol) }
112
112
  end
@@ -24,7 +24,7 @@ module Contrast
24
24
  end
25
25
 
26
26
  # find original in the target, copy tags to the new position in target
27
- original_start_index = target[0..target.length / 2 + 1].rindex(source1)
27
+ original_start_index = target[0..(target.length / 2) + 1].rindex(source1)
28
28
  original_start_index ||= 1
29
29
  properties.copy_from(source1, target, original_start_index, propagation_node.untags)
30
30
 
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'contrast/components/logger'
5
5
  require 'contrast/utils/duck_utils'
6
+ require 'contrast/utils/substitution_utils'
6
7
 
7
8
  module Contrast
8
9
  module Agent
@@ -17,9 +18,7 @@ module Contrast
17
18
  class Substitution
18
19
  include Contrast::Components::Logger::InstanceMethods
19
20
  extend Contrast::Components::Logger::InstanceMethods
20
-
21
- CAPTURE_GROUP_REGEXP = /\\[[:digit:]]/.cs__freeze
22
- CAPTURE_NAME_REGEXP = /\\k<[[:alpha:]]/.cs__freeze
21
+ extend Contrast::Utils::SubstitutionUtils
23
22
 
24
23
  class << self
25
24
  # gsub is hard. there are four versions of this method
@@ -38,157 +37,6 @@ module Contrast
38
37
  def sub_tagger patcher, preshift, ret, block
39
38
  substitution_tagger(patcher, preshift, ret, !block.nil?, false)
40
39
  end
41
-
42
- private
43
-
44
- def substitution_tagger patcher, preshift, ret, block, global = true
45
- return ret unless ret
46
-
47
- begin
48
- source = preshift.object
49
- self_tracked = Contrast::Agent::Assess::Tracker.tracked?(source)
50
- args = preshift.args[1]
51
- incoming_tracked = args && determine_tracked(args)
52
- return ret unless self_tracked || incoming_tracked
53
-
54
- parent_events = []
55
- if block
56
- block_sub(self_tracked, source, ret)
57
- elsif args.is_a?(String)
58
- string_sub(parent_events, self_tracked, preshift, ret, args, incoming_tracked, global)
59
- elsif args.is_a?(Hash)
60
- hash_sub(self_tracked, source, ret)
61
- else # Enumerator, only for gsub
62
- pattern_gsub(parent_events, preshift, ret)
63
- end
64
-
65
- if self_tracked
66
- source_properties = Contrast::Agent::Assess::Tracker.properties(source)
67
- parent_event = source_properties&.event
68
- parent_events.prepend(parent_event) if parent_event
69
- end
70
- string_build_event(parent_events, patcher, preshift, ret)
71
- rescue StandardError => e
72
- logger.error('Unable to apply gsub propagator', e)
73
- end
74
- ret
75
- end
76
-
77
- def determine_tracked args
78
- # if there's no arg, it can't be tracked
79
- return false unless args
80
-
81
- # if it's a string, just ask if it's tracked
82
- case args
83
- when String
84
- Contrast::Agent::Assess::Tracker.tracked?(args)
85
- # if it's a hash, ask if it has a tracked string
86
- when Hash
87
- args.values.any? { |value| value.is_a?(String) && Contrast::Agent::Assess::Tracker.tracked?(value) }
88
- # this should never happen
89
- else
90
- false
91
- end
92
- end
93
-
94
- def string_sub parent_events, self_tracked, preshift, ret, incoming, incoming_tracked, global
95
- return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
96
-
97
- incoming_properties = Contrast::Agent::Assess::Tracker.properties(incoming)
98
- parent_events << incoming_properties&.event if incoming_properties&.event
99
-
100
- source = preshift.object
101
-
102
- # We can't efficiently find the places that things were
103
- # copied from regexp / captures. Trading accuracy for
104
- # performance
105
- if incoming.match?(CAPTURE_GROUP_REGEXP) || incoming.match?(CAPTURE_NAME_REGEXP)
106
- properties.splat_from(source, ret) if self_tracked
107
- return
108
- end
109
-
110
- # if it's just a straight insert, that we can do
111
- # Copy the tags from us to the return
112
- ranges = find_string_sub_insert(properties, preshift, incoming, ret, global)
113
-
114
- properties.delete_tags_at_ranges(ranges)
115
- properties.shift_tags(ranges)
116
- return unless incoming_tracked
117
- return unless incoming_properties
118
-
119
- tags = incoming_properties.tag_keys
120
- ranges.each do |range|
121
- tags.each do |tag|
122
- properties.add_tag(tag, range)
123
- end
124
- end
125
- end
126
-
127
- # Find the points at which the new String was placed into the original
128
- #
129
- # @param properties [Contrast::Agent::Assess::Properties] the Properties of the ret
130
- # @param preshift [Contrast::Agent::Assess::PreShift] the capture of the state of the code just prior to
131
- # the invocation of the patched method
132
- # @param incoming [String] the new String going into the substitution
133
- # @param ret [String] the result of the substitution
134
- # @param global [Boolean] if this was a global or single substitution
135
- # @return [Array<Range>] the Ranges where substitution occurred
136
- def find_string_sub_insert properties, preshift, incoming, ret, global
137
- pattern = preshift.args[0]
138
- source = preshift.object
139
-
140
- properties.copy_from(source, ret)
141
- # Figure out where inserts occurred
142
- last_idx = 0
143
- ranges = []
144
- # For each insert, move the tag ranges
145
- while last_idx
146
- idx = source.index(pattern, last_idx)
147
- break unless idx
148
-
149
- last_idx = idx ? idx + 1 : nil
150
- start_index = idx
151
- end_index = idx + incoming.length
152
- ranges << (start_index...end_index)
153
- break unless global
154
- end
155
- ranges
156
- end
157
-
158
- def block_sub self_tracked, source, ret
159
- return unless self_tracked
160
-
161
- properties = Contrast::Agent::Assess::Tracker.properties!(ret)
162
- properties&.splat_from(source, ret)
163
- end
164
-
165
- def hash_sub self_tracked, source, ret
166
- return unless self_tracked
167
-
168
- properties = Contrast::Agent::Assess::Tracker.properties!(ret)
169
- properties&.splat_from(source, ret)
170
- end
171
-
172
- def pattern_gsub parent_events, preshift, ret
173
- source = preshift.object
174
- return unless (source_properties = Contrast::Agent::Assess::Tracker.properties(source))
175
- return unless (properties = Contrast::Agent::Assess::Tracker.properties!(ret))
176
-
177
- source_properties.tag_keys.each do |key|
178
- properties.add_tag(key, 0...1)
179
- end
180
- parent_event = source_properties.event
181
- parent_events << parent_event if parent_event
182
- end
183
-
184
- def string_build_event parent_events, patcher, preshift, ret
185
- return unless Contrast::Agent::Assess::Tracker.tracked?(ret)
186
-
187
- properties = Contrast::Agent::Assess::Tracker.properties(ret)
188
- args = preshift.args
189
- properties.build_event(patcher, ret, preshift.object, ret, args, 2)
190
- properties.event.instance_variable_set(:@_parent_events, parent_events)
191
- end
192
40
  end
193
41
  end
194
42
  end