contrast-agent 6.7.0 → 6.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -2
- data/.simplecov +0 -1
- data/Rakefile +0 -1
- data/ext/cs__assess_array/cs__assess_array.c +41 -10
- data/ext/cs__assess_array/cs__assess_array.h +4 -1
- data/lib/contrast/agent/assess/policy/trigger_method.rb +3 -3
- data/lib/contrast/agent/assess/policy/trigger_validation/redos_validator.rb +1 -1
- data/lib/contrast/agent/assess/policy/trigger_validation/ssrf_validator.rb +1 -1
- data/lib/contrast/agent/assess/policy/trigger_validation/xss_validator.rb +1 -1
- data/lib/contrast/agent/assess/property/evented.rb +11 -11
- data/lib/contrast/agent/assess.rb +0 -1
- data/lib/contrast/agent/excluder.rb +53 -35
- data/lib/contrast/agent/exclusion_matcher.rb +21 -9
- data/lib/contrast/agent/middleware.rb +12 -6
- data/lib/contrast/agent/patching/policy/after_load_patcher.rb +6 -0
- data/lib/contrast/agent/protect/input_analyzer/input_analyzer.rb +146 -127
- data/lib/contrast/agent/protect/input_analyzer/worth_watching_analyzer.rb +116 -0
- data/lib/contrast/agent/protect/policy/applies_path_traversal_rule.rb +20 -0
- data/lib/contrast/agent/protect/policy/rule_applicator.rb +1 -1
- data/lib/contrast/agent/protect/rule/base.rb +47 -55
- data/lib/contrast/agent/protect/rule/base_service.rb +48 -24
- data/lib/contrast/agent/protect/rule/bot_blocker/bot_blocker_input_classification.rb +98 -0
- data/lib/contrast/agent/protect/rule/bot_blocker.rb +81 -0
- data/lib/contrast/agent/protect/rule/cmd_injection.rb +20 -2
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_backdoors.rb +8 -5
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_base_rule.rb +22 -22
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_chained_command.rb +64 -0
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_dangerous_path.rb +63 -0
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_input_classification.rb +2 -58
- data/lib/contrast/agent/protect/rule/default_scanner.rb +1 -1
- data/lib/contrast/agent/protect/rule/deserialization.rb +3 -14
- data/lib/contrast/agent/protect/rule/http_method_tampering/http_method_tampering_input_classification.rb +2 -2
- data/lib/contrast/agent/protect/rule/http_method_tampering.rb +0 -11
- data/lib/contrast/agent/protect/rule/no_sqli/no_sqli_input_classification.rb +29 -34
- data/lib/contrast/agent/protect/rule/no_sqli.rb +25 -18
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_input_classification.rb +61 -0
- data/lib/contrast/agent/protect/rule/path_traversal/path_traversal_semantic_security_bypass.rb +114 -0
- data/lib/contrast/agent/protect/rule/path_traversal.rb +40 -13
- data/lib/contrast/agent/protect/rule/sql_sample_builder.rb +33 -15
- data/lib/contrast/agent/protect/rule/sqli/sqli_base_rule.rb +0 -14
- data/lib/contrast/agent/protect/rule/sqli/sqli_input_classification.rb +2 -62
- data/lib/contrast/agent/protect/rule/sqli.rb +74 -3
- data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_input_classification.rb +39 -63
- data/lib/contrast/agent/protect/rule/unsafe_file_upload.rb +6 -33
- data/lib/contrast/agent/protect/rule/xss/reflected_xss_input_classification.rb +58 -0
- data/lib/contrast/agent/protect/rule/xss.rb +15 -20
- data/lib/contrast/agent/protect/rule/xxe.rb +4 -24
- data/lib/contrast/agent/reporting/attack_result/rasp_rule_sample.rb +19 -40
- data/lib/contrast/agent/reporting/attack_result/response_type.rb +9 -9
- data/lib/contrast/agent/reporting/details/ip_denylist_details.rb +10 -2
- data/lib/contrast/agent/reporting/details/virtual_patch_details.rb +8 -2
- data/lib/contrast/agent/reporting/input_analysis/details/bot_blocker_details.rb +27 -0
- data/lib/contrast/agent/reporting/input_analysis/details/protect_rule_details.rb +15 -0
- data/lib/contrast/agent/reporting/input_analysis/input_analysis.rb +1 -2
- data/lib/contrast/agent/reporting/input_analysis/input_analysis_result.rb +16 -2
- data/lib/contrast/agent/reporting/masker/masker.rb +2 -0
- data/lib/contrast/agent/reporting/report.rb +1 -0
- data/lib/contrast/agent/reporting/reporter.rb +35 -14
- data/lib/contrast/agent/reporting/reporter_heartbeat.rb +3 -9
- data/lib/contrast/agent/reporting/reporting_events/application_activity.rb +16 -13
- data/lib/contrast/agent/reporting/reporting_events/application_defend_activity.rb +12 -7
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_activity.rb +3 -3
- data/lib/contrast/agent/reporting/reporting_events/application_defend_attack_sample.rb +1 -2
- data/lib/contrast/agent/reporting/reporting_events/application_inventory_activity.rb +6 -1
- data/lib/contrast/agent/reporting/reporting_events/application_update.rb +0 -2
- data/lib/contrast/agent/reporting/reporting_events/architecture_component.rb +0 -1
- data/lib/contrast/agent/reporting/reporting_events/finding.rb +6 -6
- data/lib/contrast/agent/reporting/reporting_events/finding_event.rb +239 -93
- data/lib/contrast/agent/reporting/reporting_events/finding_event_signature.rb +10 -23
- data/lib/contrast/agent/reporting/reporting_events/finding_event_source.rb +10 -9
- data/lib/contrast/agent/reporting/reporting_events/finding_request.rb +0 -5
- data/lib/contrast/agent/reporting/reporting_events/library_discovery.rb +0 -1
- data/lib/contrast/agent/reporting/reporting_events/observed_route.rb +12 -0
- data/lib/contrast/agent/reporting/reporting_events/poll.rb +1 -11
- data/lib/contrast/agent/reporting/reporting_events/route_discovery.rb +0 -1
- data/lib/contrast/agent/reporting/reporting_events/route_discovery_observation.rb +0 -1
- data/lib/contrast/agent/reporting/reporting_events/server_reporting_event.rb +8 -0
- data/lib/contrast/agent/reporting/reporting_events/server_settings.rb +40 -0
- data/lib/contrast/agent/reporting/reporting_utilities/audit.rb +2 -2
- data/lib/contrast/agent/reporting/reporting_utilities/endpoints.rb +6 -0
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client.rb +43 -1
- data/lib/contrast/agent/reporting/reporting_utilities/reporter_client_utils.rb +8 -4
- data/lib/contrast/agent/reporting/reporting_utilities/response.rb +1 -1
- data/lib/contrast/agent/reporting/reporting_utilities/response_extractor.rb +58 -4
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler.rb +4 -6
- data/lib/contrast/agent/reporting/reporting_utilities/response_handler_utils.rb +77 -16
- data/lib/contrast/agent/reporting/server_settings_worker.rb +44 -0
- data/lib/contrast/agent/reporting/settings/assess_server_feature.rb +14 -2
- data/lib/contrast/agent/reporting/settings/code_exclusion.rb +6 -1
- data/lib/contrast/agent/reporting/settings/exclusion_base.rb +18 -0
- data/lib/contrast/agent/reporting/settings/exclusions.rb +2 -1
- data/lib/contrast/agent/reporting/settings/helpers.rb +7 -0
- data/lib/contrast/agent/reporting/settings/input_exclusion.rb +9 -3
- data/lib/contrast/agent/reporting/settings/protect.rb +15 -15
- data/lib/contrast/agent/reporting/settings/protect_server_feature.rb +39 -2
- data/lib/contrast/agent/reporting/settings/rule_definition.rb +3 -0
- data/lib/contrast/agent/reporting/settings/security_logger.rb +77 -0
- data/lib/contrast/agent/reporting/settings/server_features.rb +9 -0
- data/lib/contrast/agent/reporting/settings/syslog.rb +34 -5
- data/lib/contrast/agent/request.rb +3 -14
- data/lib/contrast/agent/request_context.rb +6 -9
- data/lib/contrast/agent/request_context_extend.rb +9 -148
- data/lib/contrast/agent/request_handler.rb +5 -10
- data/lib/contrast/agent/telemetry/events/exceptions/telemetry_exception_event.rb +1 -1
- data/lib/contrast/agent/thread_watcher.rb +37 -18
- data/lib/contrast/agent/version.rb +1 -1
- data/lib/contrast/agent.rb +6 -11
- data/lib/contrast/agent_lib/api/command_injection.rb +46 -0
- data/lib/contrast/agent_lib/api/init.rb +101 -0
- data/lib/contrast/agent_lib/api/input_tracing.rb +267 -0
- data/lib/contrast/agent_lib/api/method_tempering.rb +29 -0
- data/lib/contrast/agent_lib/api/panic.rb +87 -0
- data/lib/contrast/agent_lib/api/path_semantic_file_security_bypass.rb +40 -0
- data/lib/contrast/agent_lib/interface.rb +260 -0
- data/lib/contrast/agent_lib/interface_base.rb +118 -0
- data/lib/contrast/agent_lib/return_types/eval_result.rb +44 -0
- data/lib/contrast/agent_lib/test.rb +29 -0
- data/lib/contrast/api/communication/connection_status.rb +20 -5
- data/lib/contrast/components/agent.rb +34 -14
- data/lib/contrast/components/api.rb +23 -0
- data/lib/contrast/components/app_context.rb +23 -5
- data/lib/contrast/components/app_context_extend.rb +0 -25
- data/lib/contrast/components/assess.rb +34 -4
- data/lib/contrast/components/assess_rules.rb +18 -0
- data/lib/contrast/components/base.rb +40 -0
- data/lib/contrast/components/config/sources.rb +95 -0
- data/lib/contrast/components/config.rb +19 -19
- data/lib/contrast/components/heap_dump.rb +10 -0
- data/lib/contrast/components/inventory.rb +15 -2
- data/lib/contrast/components/logger.rb +18 -0
- data/lib/contrast/components/polling.rb +36 -0
- data/lib/contrast/components/protect.rb +52 -2
- data/lib/contrast/components/ruby_component.rb +16 -1
- data/lib/contrast/components/sampling.rb +70 -13
- data/lib/contrast/components/security_logger.rb +13 -0
- data/lib/contrast/components/settings.rb +105 -90
- data/lib/contrast/config/certification_configuration.rb +14 -0
- data/lib/contrast/config/config.rb +46 -0
- data/lib/contrast/config/diagnostics.rb +114 -0
- data/lib/contrast/config/diagnostics_tools.rb +98 -0
- data/lib/contrast/config/effective_config.rb +65 -0
- data/lib/contrast/config/effective_config_value.rb +32 -0
- data/lib/contrast/config/exception_configuration.rb +12 -0
- data/lib/contrast/config/protect_rule_configuration.rb +8 -8
- data/lib/contrast/config/protect_rules_configuration.rb +23 -60
- data/lib/contrast/config/request_audit_configuration.rb +13 -0
- data/lib/contrast/config/server_configuration.rb +41 -2
- data/lib/contrast/configuration.rb +29 -12
- data/lib/contrast/extension/assess/array.rb +9 -0
- data/lib/contrast/extension/assess/erb.rb +1 -1
- data/lib/contrast/extension/delegator.rb +2 -0
- data/lib/contrast/framework/manager.rb +3 -1
- data/lib/contrast/framework/rails/railtie.rb +0 -1
- data/lib/contrast/framework/rails/support.rb +0 -1
- data/lib/contrast/tasks/config.rb +1 -8
- data/lib/contrast/utils/assess/event_limit_utils.rb +31 -9
- data/lib/contrast/utils/assess/trigger_method_utils.rb +5 -4
- data/lib/contrast/utils/duck_utils.rb +1 -0
- data/lib/contrast/utils/hash_digest.rb +2 -2
- data/lib/contrast/utils/input_classification_base.rb +155 -0
- data/lib/contrast/utils/os.rb +0 -20
- data/lib/contrast/utils/reporting/application_activity_batch_utils.rb +81 -0
- data/lib/contrast/utils/response_utils.rb +0 -16
- data/lib/contrast/utils/routes_sent.rb +60 -0
- data/lib/contrast/utils/stack_trace_utils.rb +3 -15
- data/lib/contrast/utils/string_utils.rb +10 -7
- data/lib/contrast/utils/telemetry_client.rb +1 -2
- data/lib/contrast/utils/timer.rb +16 -0
- data/lib/contrast.rb +5 -4
- data/resources/protect/policy.json +1 -2
- data/ruby-agent.gemspec +7 -6
- metadata +69 -130
- data/exe/contrast_service +0 -23
- data/lib/contrast/agent/assess/contrast_event.rb +0 -157
- data/lib/contrast/agent/assess/events/event_factory.rb +0 -34
- data/lib/contrast/agent/assess/events/source_event.rb +0 -46
- data/lib/contrast/agent/protect/rule/cmdi/cmdi_worth_watching.rb +0 -64
- data/lib/contrast/agent/protect/rule/sqli/sqli_worth_watching.rb +0 -118
- data/lib/contrast/agent/protect/rule/unsafe_file_upload/unsafe_file_upload_matcher.rb +0 -45
- data/lib/contrast/agent/reaction_processor.rb +0 -47
- data/lib/contrast/agent/reporting/reporting_events/server_activity.rb +0 -36
- data/lib/contrast/agent/service_heartbeat.rb +0 -35
- data/lib/contrast/api/communication/messaging_queue.rb +0 -128
- data/lib/contrast/api/communication/response_processor.rb +0 -90
- data/lib/contrast/api/communication/service_lifecycle.rb +0 -77
- data/lib/contrast/api/communication/socket.rb +0 -44
- data/lib/contrast/api/communication/socket_client.rb +0 -130
- data/lib/contrast/api/communication/speedracer.rb +0 -138
- data/lib/contrast/api/communication/tcp_socket.rb +0 -32
- data/lib/contrast/api/communication/unix_socket.rb +0 -28
- data/lib/contrast/api/communication.rb +0 -20
- data/lib/contrast/api/decorators/address.rb +0 -59
- data/lib/contrast/api/decorators/agent_startup.rb +0 -56
- data/lib/contrast/api/decorators/application_settings.rb +0 -43
- data/lib/contrast/api/decorators/application_startup.rb +0 -56
- data/lib/contrast/api/decorators/bot_blocker.rb +0 -37
- data/lib/contrast/api/decorators/http_request.rb +0 -137
- data/lib/contrast/api/decorators/input_analysis.rb +0 -18
- data/lib/contrast/api/decorators/instrumentation_mode.rb +0 -35
- data/lib/contrast/api/decorators/ip_denylist.rb +0 -37
- data/lib/contrast/api/decorators/message.rb +0 -67
- data/lib/contrast/api/decorators/rasp_rule_sample.rb +0 -52
- data/lib/contrast/api/decorators/response_type.rb +0 -17
- data/lib/contrast/api/decorators/server_features.rb +0 -25
- data/lib/contrast/api/decorators/user_input.rb +0 -51
- data/lib/contrast/api/decorators/virtual_patch.rb +0 -34
- data/lib/contrast/api/decorators.rb +0 -22
- data/lib/contrast/api/dtm.pb.rb +0 -363
- data/lib/contrast/api/settings.pb.rb +0 -500
- data/lib/contrast/api.rb +0 -16
- data/lib/contrast/components/contrast_service.rb +0 -88
- data/lib/contrast/components/service.rb +0 -55
- data/lib/contrast/tasks/service.rb +0 -84
- data/lib/contrast/utils/input_classification.rb +0 -73
- data/lib/protobuf/code_generator.rb +0 -129
- data/lib/protobuf/decoder.rb +0 -28
- data/lib/protobuf/deprecation.rb +0 -117
- data/lib/protobuf/descriptors/google/protobuf/compiler/plugin.pb.rb +0 -79
- data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +0 -360
- data/lib/protobuf/descriptors.rb +0 -3
- data/lib/protobuf/encoder.rb +0 -11
- data/lib/protobuf/enum.rb +0 -365
- data/lib/protobuf/exceptions.rb +0 -9
- data/lib/protobuf/field/base_field.rb +0 -380
- data/lib/protobuf/field/base_field_object_definitions.rb +0 -504
- data/lib/protobuf/field/bool_field.rb +0 -64
- data/lib/protobuf/field/bytes_field.rb +0 -67
- data/lib/protobuf/field/double_field.rb +0 -25
- data/lib/protobuf/field/enum_field.rb +0 -56
- data/lib/protobuf/field/field_array.rb +0 -102
- data/lib/protobuf/field/field_hash.rb +0 -122
- data/lib/protobuf/field/fixed32_field.rb +0 -25
- data/lib/protobuf/field/fixed64_field.rb +0 -28
- data/lib/protobuf/field/float_field.rb +0 -43
- data/lib/protobuf/field/int32_field.rb +0 -21
- data/lib/protobuf/field/int64_field.rb +0 -34
- data/lib/protobuf/field/integer_field.rb +0 -23
- data/lib/protobuf/field/message_field.rb +0 -51
- data/lib/protobuf/field/sfixed32_field.rb +0 -27
- data/lib/protobuf/field/sfixed64_field.rb +0 -28
- data/lib/protobuf/field/signed_integer_field.rb +0 -29
- data/lib/protobuf/field/sint32_field.rb +0 -21
- data/lib/protobuf/field/sint64_field.rb +0 -21
- data/lib/protobuf/field/string_field.rb +0 -51
- data/lib/protobuf/field/uint32_field.rb +0 -21
- data/lib/protobuf/field/uint64_field.rb +0 -21
- data/lib/protobuf/field/varint_field.rb +0 -77
- data/lib/protobuf/field.rb +0 -74
- data/lib/protobuf/generators/base.rb +0 -85
- data/lib/protobuf/generators/enum_generator.rb +0 -39
- data/lib/protobuf/generators/extension_generator.rb +0 -27
- data/lib/protobuf/generators/field_generator.rb +0 -193
- data/lib/protobuf/generators/file_generator.rb +0 -262
- data/lib/protobuf/generators/group_generator.rb +0 -122
- data/lib/protobuf/generators/message_generator.rb +0 -104
- data/lib/protobuf/generators/option_generator.rb +0 -17
- data/lib/protobuf/generators/printable.rb +0 -160
- data/lib/protobuf/generators/service_generator.rb +0 -50
- data/lib/protobuf/lifecycle.rb +0 -33
- data/lib/protobuf/logging.rb +0 -39
- data/lib/protobuf/message/fields.rb +0 -233
- data/lib/protobuf/message/serialization.rb +0 -85
- data/lib/protobuf/message.rb +0 -241
- data/lib/protobuf/optionable.rb +0 -72
- data/lib/protobuf/tasks/compile.rake +0 -80
- data/lib/protobuf/tasks.rb +0 -1
- data/lib/protobuf/varint.rb +0 -20
- data/lib/protobuf/varint_pure.rb +0 -31
- data/lib/protobuf/version.rb +0 -3
- data/lib/protobuf/wire_type.rb +0 -10
- data/lib/protobuf.rb +0 -91
- data/proto/dynamic_discovery.proto +0 -46
- data/proto/google/protobuf/compiler/plugin.proto +0 -183
- data/proto/google/protobuf/descriptor.proto +0 -911
- data/proto/rpc.proto +0 -71
- data/service_executables/.gitkeep +0 -0
- data/service_executables/VERSION +0 -1
- data/service_executables/linux/contrast-service +0 -0
- data/service_executables/mac/contrast-service +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b850f63bce180f09f998f5363f58b01e9c69db61a2f98a31db97fb59b82564d7
|
4
|
+
data.tar.gz: 811072666998fb4daf0f49d2514d875b45c18d79d29398639862f1c2144aa930
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac0f4dcea0a62d6aa000659943f2b994a02940148fffdff2901ff4ff61d27fd257170ec4f48d0b1925d569dbc20de89a8866f76a17dd1c22aa15d9c80e4e9eb1
|
7
|
+
data.tar.gz: bd2490f015d1a5c8be5f0e7a0fc60e5acdc436b03e32420cbf19542a53b760c843cd84a17a0e7a8a3794ec69e3aa909e63fabdb32f4c32d5daa48ece261176aa
|
data/.gitignore
CHANGED
data/.simplecov
CHANGED
data/Rakefile
CHANGED
@@ -31,15 +31,46 @@ static VALUE contrast_assess_array_join(const int argc, const VALUE *argv,
|
|
31
31
|
return result;
|
32
32
|
}
|
33
33
|
|
34
|
+
static VALUE contrast_assess_prepend_array_join(const int argc, const VALUE *argv,
|
35
|
+
const VALUE ary) {
|
36
|
+
VALUE sep, result;
|
37
|
+
/* We need to figure out the separator the join method actually used. */
|
38
|
+
/* First, check if one was provided. */
|
39
|
+
rb_scan_args(argc, argv, "01", &sep);
|
40
|
+
/* Second, check to see if `$;` is set*/
|
41
|
+
if (NIL_P(sep)) {
|
42
|
+
sep = rb_output_fs;
|
43
|
+
}
|
44
|
+
|
45
|
+
/* call the Array.join but patched one */
|
46
|
+
result = rb_ary_join(ary, sep);
|
47
|
+
/* call the Contrast::Extensions::Assess::ArrayPropagator#cs__track_join */
|
48
|
+
result = rb_funcall(array_propagator, rb_sym_assess_track_array_join, 3,
|
49
|
+
ary, sep, result);
|
50
|
+
|
51
|
+
/*call original occurs in ruby*/
|
52
|
+
return Qtrue;
|
53
|
+
}
|
54
|
+
|
34
55
|
void Init_cs__assess_array(void) {
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
56
|
+
|
57
|
+
VALUE rb_mod_ary = rb_const_get(rb_cObject, rb_intern("Array"));
|
58
|
+
VALUE rb_sym_ary_join = ID2SYM(rb_intern("join"));
|
59
|
+
// check if prepended
|
60
|
+
VALUE is_prepended = contrast_check_prepended(rb_mod_ary, rb_sym_ary_join, Qtrue);
|
61
|
+
// register the cs__track_join method of the Array propagator, and call it here from Ruby.
|
62
|
+
array_propagator = rb_define_class_under(core_assess, "ArrayPropagator", rb_cObject);
|
63
|
+
rb_sym_assess_track_array_join = rb_intern("cs__track_join");
|
64
|
+
|
65
|
+
// register the cs__join method of the ContrastArray for prepending, and call it here from Ruby.
|
66
|
+
VALUE contrast_array = rb_define_module_under(core_assess, "ContrastArray");
|
67
|
+
rb_define_module_function(contrast_array, "cs__join", contrast_assess_prepend_array_join, -1);
|
68
|
+
|
69
|
+
if(is_prepended == Qtrue) {
|
70
|
+
// do nothing prepend is done in Ruby
|
71
|
+
} else {
|
72
|
+
// register alias patch
|
73
|
+
rb_sym_assess_array_join =
|
74
|
+
contrast_register_patch("Array", "join", contrast_assess_array_join);
|
75
|
+
}
|
45
76
|
}
|
@@ -3,8 +3,11 @@
|
|
3
3
|
static VALUE array_propagator;
|
4
4
|
static VALUE rb_sym_assess_array_join;
|
5
5
|
static VALUE rb_sym_assess_track_array_join;
|
6
|
-
|
6
|
+
static VALUE rb_mod_ary, rb_sym_ary_join, is_prepended, contrast_array;
|
7
7
|
static VALUE contrast_assess_array_join(const int argc, const VALUE *argv,
|
8
8
|
const VALUE ary);
|
9
9
|
|
10
|
+
static VALUE contrast_assess_prepend_array_join(const int argc, const VALUE *argv,
|
11
|
+
const VALUE ary);
|
12
|
+
|
10
13
|
void Init_cs__assess_array(void);
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# Copyright (c) 2022 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/events/event_factory'
|
5
4
|
require 'contrast/agent/assess/policy/trigger_validation/trigger_validation'
|
6
5
|
require 'contrast/agent/excluder'
|
7
6
|
require 'contrast/components/logger'
|
@@ -15,6 +14,7 @@ require 'contrast/agent/reporting/reporting_events/preflight_message'
|
|
15
14
|
require 'contrast/agent/reporting/reporting_events/route_discovery'
|
16
15
|
require 'contrast/agent/reporting/reporting_utilities/reporting_storage'
|
17
16
|
require 'contrast/agent/reporting/reporting_utilities/build_preflight'
|
17
|
+
require 'contrast/utils/assess/event_limit_utils'
|
18
18
|
|
19
19
|
module Contrast
|
20
20
|
module Agent
|
@@ -23,7 +23,7 @@ module Contrast
|
|
23
23
|
# A trigger method is one which can perform a dangerous action, as described by the
|
24
24
|
# Contrast::Agent::Assess::Policy::TriggerNode class. Each such method will call to this module just after
|
25
25
|
# invocation in order to determine if the call was done safely. In those cases where it was not, a Finding
|
26
|
-
# report is issued to
|
26
|
+
# report is issued to TeamServer.
|
27
27
|
module TriggerMethod
|
28
28
|
extend Contrast::Components::Logger::InstanceMethods
|
29
29
|
extend Contrast::Utils::Assess::TriggerMethodUtils
|
@@ -105,7 +105,7 @@ module Contrast
|
|
105
105
|
nil
|
106
106
|
end
|
107
107
|
|
108
|
-
# Given a finding, append it to an activity message and send it to the
|
108
|
+
# Given a finding, append it to an activity message and send it to the TeamServer for processing. If an
|
109
109
|
# activity message does not exist, b/c we're invoked outside of a request context, build an activity and
|
110
110
|
# immediately report it with the finding.
|
111
111
|
#
|
@@ -7,7 +7,7 @@ module Contrast
|
|
7
7
|
module Policy
|
8
8
|
module TriggerValidation
|
9
9
|
# Validator used to assert a REDOS finding is actually vulnerable
|
10
|
-
# before serializing that finding as a DTM to report to the
|
10
|
+
# before serializing that finding as a DTM to report to the TeamServer.
|
11
11
|
module REDOSValidator
|
12
12
|
RULE_NAME = 'redos'
|
13
13
|
|
@@ -7,7 +7,7 @@ module Contrast
|
|
7
7
|
module Policy
|
8
8
|
module TriggerValidation
|
9
9
|
# Validator used to assert a SSRF finding is actually vulnerable
|
10
|
-
# before serializing that finding as a DTM to report to the
|
10
|
+
# before serializing that finding as a DTM to report to the TeamServer.
|
11
11
|
module SSRFValidator
|
12
12
|
RULE_NAME = 'ssrf'
|
13
13
|
URL_PATTERN = %r{(?<protocol>http|https|ftp|sftp|telnet|gopher|rtsp|rtsps|ssh|svn)://(?<host>[^/?]+)(?<path>/?[^?]*)(?<query_string>\?.*)?}i.cs__freeze # rubocop:disable Layout/LineLength
|
@@ -8,7 +8,7 @@ module Contrast
|
|
8
8
|
module TriggerValidation
|
9
9
|
# Validator used to assert a Reflected XSS finding is actually
|
10
10
|
# vulnerable before serializing that finding as a DTM to report to
|
11
|
-
# the
|
11
|
+
# the TeamServer.
|
12
12
|
module XSSValidator
|
13
13
|
RULE_NAME = 'reflected-xss'
|
14
14
|
SAFE_CONTENT_TYPES = %w[/csv /javascript /json /pdf /x-javascript /x-json].cs__freeze
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# Copyright (c) 2022 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/
|
5
|
-
require 'contrast/agent/assess/events/source_event'
|
4
|
+
require 'contrast/agent/reporting/reporting_events/finding_event'
|
6
5
|
|
7
6
|
module Contrast
|
8
7
|
module Agent
|
@@ -25,7 +24,7 @@ module Contrast
|
|
25
24
|
# the key used to accessed if from a map or nil if a type like
|
26
25
|
# BODY
|
27
26
|
def build_event event_data, source_type = nil, source_name = nil
|
28
|
-
@event = Contrast::Agent::
|
27
|
+
@event = Contrast::Agent::Reporting::FindingEvent.new(event_data, source_type, source_name)
|
29
28
|
report_sources(event_data.tagged, @event)
|
30
29
|
end
|
31
30
|
|
@@ -35,21 +34,22 @@ module Contrast
|
|
35
34
|
# context's observed route
|
36
35
|
#
|
37
36
|
# @param tagged [Object] The Target of the Event
|
38
|
-
# @param event [Contrast::Agent::
|
37
|
+
# @param event [Contrast::Agent::Reporting::FindingEvent]
|
39
38
|
def report_sources tagged, event
|
40
39
|
return unless tagged && !tagged.to_s.empty?
|
41
|
-
return unless event.cs__is_a?(Contrast::Agent::Assess::Events::SourceEvent)
|
42
40
|
return unless event.source_type
|
43
41
|
return unless (current_request = Contrast::Agent::REQUEST_TRACKER.current)
|
44
42
|
|
45
|
-
|
46
|
-
|
47
|
-
|
43
|
+
event.event_sources&.each do |event_source|
|
44
|
+
if current_request.observed_route.sources.any? do |source|
|
45
|
+
source.source_type == event_source.source_type && source.source_name == event_source.source_name
|
46
|
+
end
|
48
47
|
|
49
|
-
|
50
|
-
|
48
|
+
next
|
49
|
+
end
|
51
50
|
|
52
|
-
|
51
|
+
current_request.observed_route.sources << event_source
|
52
|
+
end
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
@@ -1,13 +1,17 @@
|
|
1
1
|
# Copyright (c) 2022 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/reporting/settings/url_exclusion'
|
5
|
+
|
4
6
|
module Contrast
|
5
7
|
module Agent
|
6
8
|
# Given an array of exclusion matcher instances provides methods to
|
7
9
|
# determine if the exclusions apply to particular urls.
|
8
10
|
class Excluder # rubocop:disable Metrics/ClassLength
|
11
|
+
# @return [Array<Contrast::Agent::ExclusionMatcher>]
|
9
12
|
attr_reader :exclusions
|
10
13
|
|
14
|
+
# @param exclusions [Array<Contrast::Agent::ExclusionMatcher>]
|
11
15
|
def initialize exclusions = []
|
12
16
|
@exclusions = exclusions
|
13
17
|
end
|
@@ -16,12 +20,12 @@ module Contrast
|
|
16
20
|
# then we can avoid any tracking for the request.
|
17
21
|
#
|
18
22
|
# @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
|
19
|
-
# return [Boolean]
|
23
|
+
# @return [Boolean]
|
20
24
|
def assess_excluded_by_url? request
|
21
25
|
request_path = request.path
|
22
26
|
|
23
|
-
assess_url_exclusions_for_all_rules.any? do |
|
24
|
-
path_match?(
|
27
|
+
assess_url_exclusions_for_all_rules.any? do |exclusion_matcher|
|
28
|
+
path_match?(exclusion_matcher, request_path)
|
25
29
|
end
|
26
30
|
end
|
27
31
|
|
@@ -34,9 +38,9 @@ module Contrast
|
|
34
38
|
def assess_excluded_by_url_and_rule? request, rule_id
|
35
39
|
request_path = request.path
|
36
40
|
|
37
|
-
assess_url_exclusions.any? do |
|
38
|
-
path_match?(
|
39
|
-
(
|
41
|
+
assess_url_exclusions.any? do |exclusion_matcher|
|
42
|
+
path_match?(exclusion_matcher, request_path) &&
|
43
|
+
(exclusion_matcher.assess_rules.empty? || exclusion_matcher.assess_rules.include?(rule_id))
|
40
44
|
end
|
41
45
|
end
|
42
46
|
|
@@ -44,13 +48,14 @@ module Contrast
|
|
44
48
|
# rules, then we can avoid tracking this entry.
|
45
49
|
#
|
46
50
|
# @param request [Contrast::Agent::Request] a wrapper around the Rack::Request for the current request
|
47
|
-
# @param
|
51
|
+
# @param source_type [String]
|
52
|
+
# @param source_name [String]
|
48
53
|
# return [Boolean]
|
49
54
|
def assess_excluded_by_input? request, source_type, source_name
|
50
55
|
request_path = request.path
|
51
56
|
|
52
|
-
assess_input_exclusions_for_all_rules.any? do |
|
53
|
-
input_match?(
|
57
|
+
assess_input_exclusions_for_all_rules.any? do |exclusion_matcher|
|
58
|
+
input_match?(exclusion_matcher, source_type, source_name) && path_match?(exclusion_matcher, request_path)
|
54
59
|
end
|
55
60
|
end
|
56
61
|
|
@@ -66,18 +71,18 @@ module Contrast
|
|
66
71
|
|
67
72
|
# We need to check for url exclusions here for the input rules as the url exclusions
|
68
73
|
# that have already been checked didn't include the INPUT exclusions. So we look for
|
69
|
-
# any INPUT exclusions that apply to the current url and the
|
74
|
+
# any INPUT exclusions that apply to the current url and the supplied rule.
|
70
75
|
path = request.path
|
71
|
-
rule_input_exclusions = assess_input_exclusions.select do |
|
72
|
-
(
|
73
|
-
|
76
|
+
rule_input_exclusions = assess_input_exclusions.select do |exclusion_matcher|
|
77
|
+
(exclusion_matcher.protection_rules.empty? || exclusion_matcher.protection_rules.include?(rule)) &&
|
78
|
+
path_match?(exclusion_matcher, path)
|
74
79
|
end
|
75
80
|
return false if rule_input_exclusions.empty?
|
76
81
|
|
77
82
|
event_sources = finding.events.flat_map(&:event_sources)
|
78
83
|
event_sources.each do |event_source|
|
79
84
|
return false unless rule_input_exclusions.any? do |exclusion|
|
80
|
-
input_match?(exclusion, event_source.
|
85
|
+
input_match?(exclusion, event_source.source_type, event_source.source_name)
|
81
86
|
end
|
82
87
|
end
|
83
88
|
|
@@ -94,72 +99,85 @@ module Contrast
|
|
94
99
|
def protect_excluded_by_url? request
|
95
100
|
request_path = request.path
|
96
101
|
|
97
|
-
protect_url_exclusions_for_all_rules.any? do |
|
98
|
-
path_match?(
|
102
|
+
protect_url_exclusions_for_all_rules.any? do |exclusion_matcher|
|
103
|
+
path_match?(exclusion_matcher, request_path)
|
99
104
|
end
|
100
105
|
end
|
101
106
|
|
102
107
|
private
|
103
108
|
|
109
|
+
# @return [Array<Contrast::Agent::ExclusionMatcher>]
|
104
110
|
def assess_url_exclusions_for_all_rules
|
105
|
-
@_assess_url_exclusions_for_all_rules ||= assess_url_exclusions.select do |
|
106
|
-
|
111
|
+
@_assess_url_exclusions_for_all_rules ||= assess_url_exclusions.select do |exclusion_matcher|
|
112
|
+
exclusion_matcher.assess_rules.empty?
|
107
113
|
end
|
108
114
|
end
|
109
115
|
|
116
|
+
# @return [Array<Contrast::Agent::ExclusionMatcher>]
|
110
117
|
def assess_url_exclusions
|
111
|
-
@_assess_url_exclusions ||= assess_exclusions.select do |
|
112
|
-
|
118
|
+
@_assess_url_exclusions ||= assess_exclusions.select do |exclusion_matcher|
|
119
|
+
exclusion_matcher.type == :URL
|
113
120
|
end
|
114
121
|
end
|
115
122
|
|
123
|
+
# @return [Array<Contrast::Agent::ExclusionMatcher>]
|
116
124
|
def assess_input_exclusions_for_all_rules
|
117
|
-
@_assess_input_exclusions_for_all_rules ||= assess_input_exclusions.select do |
|
118
|
-
|
125
|
+
@_assess_input_exclusions_for_all_rules ||= assess_input_exclusions.select do |exclusion_matcher|
|
126
|
+
exclusion_matcher.assess_rules.empty?
|
119
127
|
end
|
120
128
|
end
|
121
129
|
|
130
|
+
# @return [Array<Contrast::Agent::ExclusionMatcher>]
|
122
131
|
def assess_input_exclusions
|
123
|
-
@_assess_input_exclusions ||= assess_exclusions.select do |
|
124
|
-
|
132
|
+
@_assess_input_exclusions ||= assess_exclusions.select do |exclusion_matcher|
|
133
|
+
exclusion_matcher.type == :INPUT
|
125
134
|
end
|
126
135
|
end
|
127
136
|
|
137
|
+
# @return [Array<Contrast::Agent::ExclusionMatcher>]
|
128
138
|
def assess_exclusions
|
129
139
|
@_assess_exclusions ||= @exclusions.select(&:assess)
|
130
140
|
end
|
131
141
|
|
142
|
+
# @return [Array<Contrast::Agent::ExclusionMatcher>]
|
132
143
|
def protect_url_exclusions_for_all_rules
|
133
|
-
@_protect_url_exclusions_for_all_rules ||= protect_url_exclusions.select do |
|
134
|
-
|
144
|
+
@_protect_url_exclusions_for_all_rules ||= protect_url_exclusions.select do |exclusion_matcher|
|
145
|
+
exclusion_matcher.protect_rules.empty?
|
135
146
|
end
|
136
147
|
end
|
137
148
|
|
149
|
+
# @return [Array<Contrast::Agent::ExclusionMatcher>]
|
138
150
|
def protect_url_exclusions
|
139
|
-
@_protect_url_exclusions ||= protect_exclusions.select do |
|
140
|
-
|
151
|
+
@_protect_url_exclusions ||= protect_exclusions.select do |exclusion_matcher|
|
152
|
+
exclusion_matcher.type == :URL
|
141
153
|
end
|
142
154
|
end
|
143
155
|
|
156
|
+
# @return [Array<Contrast::Agent::ExclusionMatcher>]
|
144
157
|
def protect_exclusions
|
145
158
|
@_protect_exclusions ||= @exclusions.select(&:protect)
|
146
159
|
end
|
147
160
|
|
148
|
-
|
149
|
-
|
161
|
+
# @return [Boolean]
|
162
|
+
def path_match? exclusion_matcher, path
|
163
|
+
exclusion_matcher.wildcard_url || exclusion_matcher.urls.any? { |url| url.match?(path) }
|
150
164
|
end
|
151
165
|
|
166
|
+
# @param exclusion [Contrast::Agent::ExclusionMatcher]
|
167
|
+
# @param source_type [String]
|
168
|
+
# @param source_name [String]
|
169
|
+
# @return [Boolean]
|
152
170
|
def input_match? exclusion, source_type, source_name
|
153
171
|
case exclusion.input_type
|
154
|
-
when
|
172
|
+
when 'PARAMETER'
|
155
173
|
input_match_parameter?(exclusion, source_type, source_name)
|
156
|
-
when
|
174
|
+
when 'COOKIE'
|
157
175
|
input_match_cookie?(exclusion, source_type, source_name)
|
158
|
-
when
|
176
|
+
when 'HEADER'
|
159
177
|
input_match_header?(exclusion, source_type, source_name)
|
160
|
-
when
|
178
|
+
when 'BODY'
|
161
179
|
Contrast::Agent::Assess::Policy::SourceMethod::BODY_TYPE == source_type
|
162
|
-
when
|
180
|
+
when 'QUERYSTRING'
|
163
181
|
Contrast::Agent::Assess::Policy::SourceMethod::QUERYSTRING_TYPE == source_type
|
164
182
|
else
|
165
183
|
false
|
@@ -2,6 +2,10 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'contrast/components/logger'
|
5
|
+
require 'contrast/agent/reporting/settings/exclusion_base'
|
6
|
+
require 'contrast/agent/reporting/settings/code_exclusion'
|
7
|
+
require 'contrast/agent/reporting/settings/input_exclusion'
|
8
|
+
require 'contrast/agent/reporting/settings/url_exclusion'
|
5
9
|
|
6
10
|
module Contrast
|
7
11
|
module Agent
|
@@ -13,22 +17,30 @@ module Contrast
|
|
13
17
|
|
14
18
|
extend Forwardable
|
15
19
|
|
16
|
-
attr_reader :protect, :assess, :urls, :wildcard_url, :wildcard_input
|
20
|
+
attr_reader :protect, :assess, :type, :urls, :wildcard_url, :wildcard_input
|
17
21
|
|
18
|
-
def_delegators :@exclusion, :
|
22
|
+
def_delegators :@exclusion, :protect_rules, :assess_rules, :input_type, :input_name
|
19
23
|
|
20
24
|
# Create a matcher around an exclusion sent from TeamServer.
|
21
25
|
#
|
22
|
-
# @param excl [Contrast::
|
26
|
+
# @param excl [Contrast::Agent::Reporting::Settings::ExclusionBase]
|
23
27
|
# @return [Contrast::Agent::ExclusionMatcher]
|
24
28
|
def initialize excl
|
25
29
|
@exclusion = excl
|
26
30
|
@protect = @exclusion.protect
|
27
31
|
@assess = @exclusion.assess
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
|
33
|
+
case excl
|
34
|
+
when Contrast::Agent::Reporting::Settings::CodeExclusion
|
35
|
+
handle_wildcard_code
|
36
|
+
@type = :CODE
|
37
|
+
when Contrast::Agent::Reporting::Settings::InputExclusion
|
38
|
+
handle_wildcard_input
|
39
|
+
@type = :INPUT
|
40
|
+
when Contrast::Agent::Reporting::Settings::UrlExclusion
|
41
|
+
handle_wildcard_url
|
42
|
+
@type = :URL
|
43
|
+
end
|
32
44
|
end
|
33
45
|
|
34
46
|
# According to the docs for exclusions, user input applies to all inputs if
|
@@ -97,7 +109,7 @@ module Contrast
|
|
97
109
|
end
|
98
110
|
|
99
111
|
def code?
|
100
|
-
@
|
112
|
+
@type == :CODE
|
101
113
|
end
|
102
114
|
|
103
115
|
def match_all?
|
@@ -105,12 +117,12 @@ module Contrast
|
|
105
117
|
end
|
106
118
|
|
107
119
|
# Determine if the given rule is excluded by this exclusion.
|
108
|
-
# In this case, the `
|
120
|
+
# In this case, the `protect_rules` being empty means apply to all rules,
|
109
121
|
# not no rules
|
110
122
|
#
|
111
123
|
# @param rule - the id of the rule which we're checking for exclusion
|
112
124
|
def protection_rule? rule
|
113
|
-
protect? && (@exclusion.
|
125
|
+
protect? && (@exclusion.protect_rules.empty? || @exclusion.protect_rules.include?(rule))
|
114
126
|
end
|
115
127
|
|
116
128
|
# Determine if the given rule is excluded by this exclusion.
|
@@ -15,7 +15,7 @@ require 'contrast/agent/request_handler'
|
|
15
15
|
require 'contrast/agent/static_analysis'
|
16
16
|
require 'contrast/agent/telemetry/events/startup_metrics_event'
|
17
17
|
require 'contrast/utils/middleware_utils'
|
18
|
-
|
18
|
+
require 'contrast/utils/reporting/application_activity_batch_utils'
|
19
19
|
require 'contrast/utils/timer'
|
20
20
|
|
21
21
|
module Contrast
|
@@ -27,6 +27,7 @@ module Contrast
|
|
27
27
|
include Contrast::Components::Logger::InstanceMethods
|
28
28
|
include Contrast::Components::Scope::InstanceMethods
|
29
29
|
include Contrast::Utils::MiddlewareUtils
|
30
|
+
include Contrast::Utils::Reporting::ApplicationActivityBatchUtils
|
30
31
|
|
31
32
|
attr_reader :app
|
32
33
|
|
@@ -62,6 +63,7 @@ module Contrast
|
|
62
63
|
# the Rack framework.
|
63
64
|
def call env
|
64
65
|
logger.trace_with_time('Elapsed time for Contrast::Agent::Middleware#call') do
|
66
|
+
::Contrast::Agent::ThreadWatcher.check_before_start
|
65
67
|
return app.call(env) unless ::Contrast::AGENT.enabled?
|
66
68
|
|
67
69
|
Contrast::Agent.heapdump_util.start_thread!
|
@@ -73,12 +75,12 @@ module Contrast
|
|
73
75
|
private
|
74
76
|
|
75
77
|
# Startup the Agent as part of the initialization process:
|
76
|
-
# - start the
|
77
|
-
# - start the heartbeat thread, which
|
78
|
+
# - start the TeamServer sending thread, responsible for sending and processing messages
|
79
|
+
# - start the heartbeat thread, which handles periodic messages to TeamServer
|
78
80
|
# - start instrumenting libraries and do a 'catchup' patch for everything we didn't see get loaded
|
79
81
|
# - enable TracePoint, which handles all class loads and required instrumentation going forward
|
80
82
|
def agent_startup_routine
|
81
|
-
logger.debug_with_time('middleware: starting
|
83
|
+
logger.debug_with_time('middleware: starting reporting threads') do
|
82
84
|
Contrast::Agent.thread_watcher.ensure_running?
|
83
85
|
end
|
84
86
|
|
@@ -147,7 +149,7 @@ module Contrast
|
|
147
149
|
# which is being triggered when there is a failure within the pre-call with the agent
|
148
150
|
def pre_call_with_agent context, request_handler
|
149
151
|
with_contrast_scope do
|
150
|
-
context.
|
152
|
+
context.protect_input_analysis
|
151
153
|
request_handler.ruleset.prefilter
|
152
154
|
end
|
153
155
|
rescue StandardError => e
|
@@ -173,13 +175,17 @@ module Contrast
|
|
173
175
|
Contrast::Agent::FINDINGS.report_collected_findings unless Contrast::Agent::FINDINGS.collection.empty?
|
174
176
|
# All protect rules, which are trigger but require response to be reported
|
175
177
|
Contrast::Agent::EXPLOITS.report_recorded_exploits(context) unless Contrast::Agent::EXPLOITS.collection.empty?
|
178
|
+
# Process Worth Watching Inputs for v2 rules
|
179
|
+
Contrast::Agent.worth_watching_analyzer&.add_to_queue(context.agent_input_analysis)
|
176
180
|
|
177
181
|
if Contrast::Agent.framework_manager.streaming?(env)
|
178
182
|
context.reset_activity
|
179
183
|
request_handler.stream_safe_postfilter
|
180
184
|
else
|
181
185
|
request_handler.ruleset.postfilter
|
182
|
-
request_handler.
|
186
|
+
request_handler.report_observed_route
|
187
|
+
add_activity_to_batch(context.activity)
|
188
|
+
report_batch
|
183
189
|
end
|
184
190
|
end
|
185
191
|
# unsuccessful attack
|
@@ -56,6 +56,7 @@ module Contrast
|
|
56
56
|
unless Contrast::Agent::Assess.cs__object_method_prepended?(Marshal, :load, false)
|
57
57
|
apply_marshal_load_alias_patch
|
58
58
|
end
|
59
|
+
apply_array_join_prepend_patch if Contrast::Agent::Assess.cs__object_method_prepended?(Array, :join, true)
|
59
60
|
true
|
60
61
|
end
|
61
62
|
end
|
@@ -121,6 +122,11 @@ module Contrast
|
|
121
122
|
Marshal.alias_method(:cs__marshal_load, :load)
|
122
123
|
Marshal.alias_method(:load, :cs__marshal_load)
|
123
124
|
end
|
125
|
+
|
126
|
+
# Prepend Contrast::Extension::Assess::ContrastArray to pick up the ContrastArray#join method
|
127
|
+
def apply_array_join_prepend_patch
|
128
|
+
Array.prepend(Contrast::Extension::Assess::ContrastArray)
|
129
|
+
end
|
124
130
|
end
|
125
131
|
end
|
126
132
|
end
|