datadog 2.12.2 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -1
  3. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +14 -13
  4. data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +89 -0
  5. data/lib/datadog/appsec/actions_handler.rb +22 -1
  6. data/lib/datadog/appsec/anonymizer.rb +16 -0
  7. data/lib/datadog/appsec/configuration/settings.rb +62 -10
  8. data/lib/datadog/appsec/contrib/auto_instrument.rb +1 -1
  9. data/lib/datadog/appsec/contrib/devise/configuration.rb +7 -31
  10. data/lib/datadog/appsec/contrib/devise/data_extractor.rb +79 -0
  11. data/lib/datadog/appsec/contrib/devise/ext.rb +21 -0
  12. data/lib/datadog/appsec/contrib/devise/integration.rb +0 -1
  13. data/lib/datadog/appsec/contrib/devise/patcher.rb +36 -23
  14. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +102 -0
  15. data/lib/datadog/appsec/contrib/devise/patches/signup_tracking_patch.rb +69 -0
  16. data/lib/datadog/appsec/contrib/devise/{patcher/rememberable_patch.rb → patches/skip_signin_tracking_patch.rb} +2 -2
  17. data/lib/datadog/appsec/contrib/devise/tracking_middleware.rb +93 -0
  18. data/lib/datadog/appsec/contrib/rack/ext.rb +14 -0
  19. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +10 -3
  20. data/lib/datadog/appsec/contrib/rack/request_middleware.rb +0 -2
  21. data/lib/datadog/appsec/event.rb +1 -1
  22. data/lib/datadog/appsec/ext.rb +4 -2
  23. data/lib/datadog/appsec/instrumentation/gateway/argument.rb +4 -2
  24. data/lib/datadog/appsec/monitor/gateway/watcher.rb +8 -3
  25. data/lib/datadog/appsec/security_engine/runner.rb +2 -2
  26. data/lib/datadog/appsec/utils.rb +0 -2
  27. data/lib/datadog/core/configuration/components.rb +2 -1
  28. data/lib/datadog/core/configuration/ext.rb +4 -0
  29. data/lib/datadog/core/configuration/options.rb +2 -2
  30. data/lib/datadog/core/configuration/settings.rb +53 -30
  31. data/lib/datadog/core/environment/agent_info.rb +4 -3
  32. data/lib/datadog/core/remote/component.rb +3 -6
  33. data/lib/datadog/core/remote/configuration/repository.rb +2 -1
  34. data/lib/datadog/core/remote/negotiation.rb +9 -9
  35. data/lib/datadog/core/remote/transport/config.rb +4 -3
  36. data/lib/datadog/core/remote/transport/http/client.rb +4 -3
  37. data/lib/datadog/core/remote/transport/http/config.rb +6 -32
  38. data/lib/datadog/core/remote/transport/http/negotiation.rb +6 -32
  39. data/lib/datadog/core/remote/transport/http.rb +22 -57
  40. data/lib/datadog/core/remote/transport/negotiation.rb +4 -3
  41. data/lib/datadog/core/runtime/metrics.rb +8 -1
  42. data/lib/datadog/core/telemetry/http/adapters/net.rb +1 -1
  43. data/lib/datadog/core/transport/http/api/instance.rb +17 -0
  44. data/lib/datadog/core/transport/http/api/spec.rb +17 -0
  45. data/lib/datadog/core/transport/http/builder.rb +5 -3
  46. data/lib/datadog/core/transport/http.rb +39 -2
  47. data/lib/datadog/di/component.rb +0 -2
  48. data/lib/datadog/di/probe_notifier_worker.rb +16 -16
  49. data/lib/datadog/di/transport/diagnostics.rb +4 -3
  50. data/lib/datadog/di/transport/http/api.rb +2 -12
  51. data/lib/datadog/di/transport/http/client.rb +4 -3
  52. data/lib/datadog/di/transport/http/diagnostics.rb +7 -33
  53. data/lib/datadog/di/transport/http/input.rb +7 -33
  54. data/lib/datadog/di/transport/http.rb +14 -56
  55. data/lib/datadog/di/transport/input.rb +4 -3
  56. data/lib/datadog/di/utils.rb +5 -0
  57. data/lib/datadog/kit/appsec/events.rb +9 -0
  58. data/lib/datadog/kit/identity.rb +5 -1
  59. data/lib/datadog/opentelemetry/api/baggage.rb +90 -0
  60. data/lib/datadog/opentelemetry/api/baggage.rbs +26 -0
  61. data/lib/datadog/opentelemetry/api/context.rb +16 -2
  62. data/lib/datadog/opentelemetry/sdk/trace/span.rb +1 -1
  63. data/lib/datadog/opentelemetry.rb +2 -1
  64. data/lib/datadog/profiling/collectors/thread_context.rb +1 -1
  65. data/lib/datadog/profiling.rb +5 -2
  66. data/lib/datadog/tracing/component.rb +15 -12
  67. data/lib/datadog/tracing/configuration/ext.rb +7 -1
  68. data/lib/datadog/tracing/configuration/settings.rb +18 -2
  69. data/lib/datadog/tracing/context_provider.rb +1 -1
  70. data/lib/datadog/tracing/contrib/configuration/settings.rb +1 -1
  71. data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +4 -5
  72. data/lib/datadog/tracing/contrib/excon/middleware.rb +5 -3
  73. data/lib/datadog/tracing/contrib/faraday/middleware.rb +5 -3
  74. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +7 -1
  75. data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +3 -0
  76. data/lib/datadog/tracing/contrib/http/circuit_breaker.rb +0 -15
  77. data/lib/datadog/tracing/contrib/http/distributed/propagation.rb +4 -1
  78. data/lib/datadog/tracing/contrib/http/instrumentation.rb +5 -5
  79. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +5 -11
  80. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +6 -10
  81. data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +5 -3
  82. data/lib/datadog/tracing/contrib/sidekiq/client_tracer.rb +6 -1
  83. data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +3 -0
  84. data/lib/datadog/tracing/correlation.rb +9 -2
  85. data/lib/datadog/tracing/distributed/baggage.rb +131 -0
  86. data/lib/datadog/tracing/distributed/datadog.rb +2 -0
  87. data/lib/datadog/tracing/distributed/propagation.rb +25 -4
  88. data/lib/datadog/tracing/distributed/propagation_policy.rb +42 -0
  89. data/lib/datadog/tracing/metadata/ext.rb +5 -0
  90. data/lib/datadog/tracing/sampling/span/rule.rb +0 -1
  91. data/lib/datadog/tracing/span_operation.rb +2 -1
  92. data/lib/datadog/tracing/sync_writer.rb +1 -2
  93. data/lib/datadog/tracing/trace_digest.rb +9 -2
  94. data/lib/datadog/tracing/trace_operation.rb +29 -17
  95. data/lib/datadog/tracing/trace_segment.rb +6 -4
  96. data/lib/datadog/tracing/tracer.rb +38 -2
  97. data/lib/datadog/tracing/transport/http/api.rb +2 -10
  98. data/lib/datadog/tracing/transport/http/client.rb +5 -4
  99. data/lib/datadog/tracing/transport/http/traces.rb +13 -41
  100. data/lib/datadog/tracing/transport/http.rb +11 -44
  101. data/lib/datadog/tracing/transport/trace_formatter.rb +7 -0
  102. data/lib/datadog/tracing/transport/traces.rb +21 -9
  103. data/lib/datadog/tracing/workers/trace_writer.rb +2 -6
  104. data/lib/datadog/tracing/writer.rb +2 -6
  105. data/lib/datadog/tracing.rb +16 -3
  106. data/lib/datadog/version.rb +2 -2
  107. metadata +17 -13
  108. data/lib/datadog/appsec/contrib/devise/event.rb +0 -54
  109. data/lib/datadog/appsec/contrib/devise/patcher/authenticatable_patch.rb +0 -72
  110. data/lib/datadog/appsec/contrib/devise/patcher/registration_controller_patch.rb +0 -47
  111. data/lib/datadog/appsec/contrib/devise/resource.rb +0 -35
  112. data/lib/datadog/appsec/contrib/devise/tracking.rb +0 -57
  113. data/lib/datadog/appsec/utils/trace_operation.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a27bb8e1291ee014e342297fc3d53aca8a895017281201b1b0a29cd376ba905
4
- data.tar.gz: e26c326437e4edcbdfac0ad604514a48c23c31500a68dcc4affb53251aed5f8f
3
+ metadata.gz: 2c826569b30a9e809d44f98b6371650aa38306e325b2f1ceb79ece2d3fb23360
4
+ data.tar.gz: 6753a8ac457c797fe29e19f1af4d434e33d6f74aa3b7c9a32394a8d3e5d57f33
5
5
  SHA512:
6
- metadata.gz: e8dab2f6ed8bc48fb95a4c9c3c28fce16b8eaaceb70179ee12b9a009498dabe498d73b08a25180fdb53642fc78da4388b2c80bbf76a61afd25af53449f2e9375
7
- data.tar.gz: 4489a8e084f7873e59b4d2a13d27b9a75f2b95ae0c9b5129edfdb02a58c736890bd5226df765de216010512b9fd0ffad6f82cc373ea8ee995340ef838c721d65
6
+ metadata.gz: 14445f04bb88f59798ed30ed28c849556556639720d2086f678058021e23ca64ab4740a3e4df1e53cab34e5f18eb785dfeebfd0716c20a75971acb605cdf6884
7
+ data.tar.gz: b3d8a318c7571747d80bb474478ab4a4ae92c422ba9a6177b6889fc3231bac4666c13d8c77ebd6bf43bd5045f427d6771e3f69531b23903ebdf4673824268f50
data/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.13.0] - 2025-04-02
6
+
7
+ ### Added
8
+
9
+ * Core: Add `DD_TRACE_EXPERIMENTAL_RUNTIME_ID_ENABLED` experimental option to enable runtime ID collection for runtime metrics. ([#4473][])
10
+ * Tracing: Add support for W3C Baggage API along with automatic extraction and injection, and OpenTelemetry support. ([#4493][], [#4505][])
11
+ * Tracing: Add `DD_APM_TRACING_ENABLED` option to disable APM tracing while keeping other products traces. ([#4498][])
12
+ * Tracing: Add `DD_TRACE_NATIVE_SPAN_EVENTS` option to override span events serialization for agent-less environments. ([#4507][])
13
+ * AppSec: Add stack trace reporting for security events. ([#4526][])
14
+ * AppSec: Improve `devise` instrumentation to support latest Account Takeover (ATO) detection. ([#4433][])
15
+
16
+ ### Changed
17
+
18
+ * Core: Improve `DD_TAGS` configuration handling to be more consistent across Datadog libraries and Agent. ([#4530][])
19
+ * Tracing: Enable by default 128-bit trace ID logging so that trace IDs are consistent across logs and the Datadog UI. ([#4528][])
20
+
21
+ ### Fixed
22
+
23
+ * Core: Fix initialization when the library is partially loaded. ([#4498][])
24
+ * Tracing: Fix trace ID propagation by ensuring extraction of 16-character hex values from the `_dd.p.tid` tag in `x-datadog-tags` header. ([#4534][])
25
+ * Tracing: Profiling: Fix warnings printed by `ruby -w`. ([#4547][], [#4549][])
26
+
5
27
  ## [2.12.2] - 2025-03-17
6
28
 
7
29
  ### Fixed
@@ -3145,7 +3167,8 @@ Release notes: https://github.com/DataDog/dd-trace-rb/releases/tag/v0.3.1
3145
3167
  Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
3146
3168
 
3147
3169
 
3148
- [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.12.2...master
3170
+ [Unreleased]: https://github.com/DataDog/dd-trace-rb/compare/v2.13.0...master
3171
+ [2.13.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.12.2...v2.13.0
3149
3172
  [2.12.2]: https://github.com/DataDog/dd-trace-rb/compare/v2.12.1...v2.12.2
3150
3173
  [2.12.1]: https://github.com/DataDog/dd-trace-rb/compare/v2.12.0...v2.12.1
3151
3174
  [2.12.0]: https://github.com/DataDog/dd-trace-rb/compare/v2.11.0...v2.12.0
@@ -4649,8 +4672,20 @@ Git diff: https://github.com/DataDog/dd-trace-rb/compare/v0.3.0...v0.3.1
4649
4672
  [#4424]: https://github.com/DataDog/dd-trace-rb/issues/4424
4650
4673
  [#4425]: https://github.com/DataDog/dd-trace-rb/issues/4425
4651
4674
  [#4426]: https://github.com/DataDog/dd-trace-rb/issues/4426
4675
+ [#4433]: https://github.com/DataDog/dd-trace-rb/issues/4433
4652
4676
  [#4437]: https://github.com/DataDog/dd-trace-rb/issues/4437
4677
+ [#4473]: https://github.com/DataDog/dd-trace-rb/issues/4473
4678
+ [#4493]: https://github.com/DataDog/dd-trace-rb/issues/4493
4653
4679
  [#4497]: https://github.com/DataDog/dd-trace-rb/issues/4497
4680
+ [#4498]: https://github.com/DataDog/dd-trace-rb/issues/4498
4681
+ [#4505]: https://github.com/DataDog/dd-trace-rb/issues/4505
4682
+ [#4507]: https://github.com/DataDog/dd-trace-rb/issues/4507
4683
+ [#4526]: https://github.com/DataDog/dd-trace-rb/issues/4526
4684
+ [#4528]: https://github.com/DataDog/dd-trace-rb/issues/4528
4685
+ [#4530]: https://github.com/DataDog/dd-trace-rb/issues/4530
4686
+ [#4534]: https://github.com/DataDog/dd-trace-rb/issues/4534
4687
+ [#4547]: https://github.com/DataDog/dd-trace-rb/issues/4547
4688
+ [#4549]: https://github.com/DataDog/dd-trace-rb/issues/4549
4654
4689
  [@AdrianLC]: https://github.com/AdrianLC
4655
4690
  [@Azure7111]: https://github.com/Azure7111
4656
4691
  [@BabyGroot]: https://github.com/BabyGroot
@@ -212,7 +212,7 @@ static VALUE _native_initialize(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _sel
212
212
  static VALUE _native_sample(VALUE self, VALUE collector_instance, VALUE profiler_overhead_stack_thread, VALUE allow_exception);
213
213
  static VALUE _native_on_gc_start(VALUE self, VALUE collector_instance);
214
214
  static VALUE _native_on_gc_finish(VALUE self, VALUE collector_instance);
215
- static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state, VALUE allow_exception);
215
+ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception);
216
216
  static void update_metrics_and_sample(
217
217
  thread_context_collector_state *state,
218
218
  VALUE thread_being_sampled,
@@ -297,6 +297,7 @@ static void otel_without_ddtrace_trace_identifiers_for(
297
297
  static otel_span otel_span_from(VALUE otel_context, VALUE otel_current_span_key);
298
298
  static uint64_t otel_span_id_to_uint(VALUE otel_span_id);
299
299
  static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE key);
300
+ static VALUE _native_reset_monotonic_to_system_state(DDTRACE_UNUSED VALUE self, VALUE collector_instance);
300
301
 
301
302
  void collectors_thread_context_init(VALUE profiling_module) {
302
303
  VALUE collectors_module = rb_define_module_under(profiling_module, "Collectors");
@@ -321,13 +322,14 @@ void collectors_thread_context_init(VALUE profiling_module) {
321
322
  rb_define_singleton_method(testing_module, "_native_sample_allocation", _native_sample_allocation, 3);
322
323
  rb_define_singleton_method(testing_module, "_native_on_gc_start", _native_on_gc_start, 1);
323
324
  rb_define_singleton_method(testing_module, "_native_on_gc_finish", _native_on_gc_finish, 1);
324
- rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 3);
325
+ rb_define_singleton_method(testing_module, "_native_sample_after_gc", _native_sample_after_gc, 2);
325
326
  rb_define_singleton_method(testing_module, "_native_thread_list", _native_thread_list, 0);
326
327
  rb_define_singleton_method(testing_module, "_native_per_thread_context", _native_per_thread_context, 1);
327
328
  rb_define_singleton_method(testing_module, "_native_stats", _native_stats, 1);
328
329
  rb_define_singleton_method(testing_module, "_native_gc_tracking", _native_gc_tracking, 1);
329
330
  rb_define_singleton_method(testing_module, "_native_new_empty_thread", _native_new_empty_thread, 0);
330
331
  rb_define_singleton_method(testing_module, "_native_sample_skipped_allocation_samples", _native_sample_skipped_allocation_samples, 2);
332
+ rb_define_singleton_method(testing_module, "_native_reset_monotonic_to_system_state", _native_reset_monotonic_to_system_state, 1);
331
333
  #ifndef NO_GVL_INSTRUMENTATION
332
334
  rb_define_singleton_method(testing_module, "_native_on_gvl_waiting", _native_on_gvl_waiting, 1);
333
335
  rb_define_singleton_method(testing_module, "_native_gvl_waiting_at_for", _native_gvl_waiting_at_for, 1);
@@ -551,19 +553,9 @@ static VALUE _native_on_gc_finish(DDTRACE_UNUSED VALUE self, VALUE collector_ins
551
553
  return Qtrue;
552
554
  }
553
555
 
554
- // This method exists only to enable testing Datadog::Profiling::Collectors::ThreadContext behavior using RSpec.
555
- // It SHOULD NOT be used for other purposes.
556
- static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE reset_monotonic_to_system_state, VALUE allow_exception) {
557
- ENFORCE_BOOLEAN(reset_monotonic_to_system_state);
556
+ static VALUE _native_sample_after_gc(DDTRACE_UNUSED VALUE self, VALUE collector_instance, VALUE allow_exception) {
558
557
  ENFORCE_BOOLEAN(allow_exception);
559
558
 
560
- thread_context_collector_state *state;
561
- TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
562
-
563
- if (reset_monotonic_to_system_state == Qtrue) {
564
- state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
565
- }
566
-
567
559
  if (allow_exception == Qfalse) debug_enter_unsafe_context();
568
560
 
569
561
  thread_context_collector_sample_after_gc(collector_instance);
@@ -2167,3 +2159,12 @@ static VALUE safely_lookup_hash_without_going_into_ruby_code(VALUE hash, VALUE k
2167
2159
 
2168
2160
  return state.result;
2169
2161
  }
2162
+
2163
+ static VALUE _native_reset_monotonic_to_system_state(DDTRACE_UNUSED VALUE self, VALUE collector_instance) {
2164
+ thread_context_collector_state *state;
2165
+ TypedData_Get_Struct(collector_instance, thread_context_collector_state, &thread_context_collector_typed_data, state);
2166
+
2167
+ state->time_converter_state = (monotonic_to_system_epoch_state) MONOTONIC_TO_SYSTEM_EPOCH_INITIALIZER;
2168
+
2169
+ return Qtrue;
2170
+ }
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AppSec
5
+ module ActionsHandler
6
+ # This module serves encapsulates MessagePack serialization for caller locations.
7
+ #
8
+ # It serializes part of the stack:
9
+ # up to 32 frames (configurable)
10
+ # keeping frames from top and bottom of the stack (75% to 25%, configurable).
11
+ #
12
+ # It represents the stack trace that is added to span metastruct field.
13
+ class SerializableBacktrace
14
+ CLASS_AND_FUNCTION_NAME_REGEX = /\b((?:\w+::)*\w+)?[#.]?\b(\w+)\z/.freeze
15
+
16
+ def initialize(locations:, stack_id:)
17
+ @stack_id = stack_id
18
+ @locations = locations
19
+ end
20
+
21
+ def to_msgpack(packer = nil)
22
+ # JRuby doesn't pass the packer
23
+ packer ||= MessagePack::Packer.new
24
+
25
+ packer.write_map_header(3)
26
+
27
+ packer.write('id')
28
+ packer.write(@stack_id.encode('UTF-8'))
29
+
30
+ packer.write('language')
31
+ packer.write('ruby'.encode('UTF-8'))
32
+
33
+ serializable_locations_map = build_serializable_locations_map
34
+
35
+ packer.write('frames')
36
+ packer.write_array_header(serializable_locations_map.size)
37
+
38
+ serializable_locations_map.each do |frame_id, location|
39
+ packer.write_map_header(6)
40
+
41
+ packer.write('id')
42
+ packer.write(frame_id)
43
+
44
+ packer.write('text')
45
+ packer.write(location.to_s.encode('UTF-8'))
46
+
47
+ packer.write('file')
48
+ packer.write(location.path&.encode('UTF-8'))
49
+
50
+ packer.write('line')
51
+ packer.write(location.lineno)
52
+
53
+ class_name, function_name = location.label&.match(CLASS_AND_FUNCTION_NAME_REGEX)&.captures
54
+
55
+ packer.write('class_name')
56
+ packer.write(class_name&.encode('UTF-8'))
57
+
58
+ packer.write('function')
59
+ packer.write(function_name&.encode('UTF-8'))
60
+ end
61
+
62
+ packer
63
+ end
64
+
65
+ private
66
+
67
+ def build_serializable_locations_map
68
+ max_depth = Datadog.configuration.appsec.stack_trace.max_depth
69
+ top_percent = Datadog.configuration.appsec.stack_trace.top_percentage
70
+
71
+ drop_from_idx = max_depth * top_percent / 100
72
+ drop_until_idx = @locations.size - (max_depth - drop_from_idx)
73
+
74
+ frame_idx = -1
75
+ @locations.each_with_object({}) do |location, map|
76
+ # we are dropping frames from library code without increasing frame index
77
+ next if location.path&.include?('lib/datadog')
78
+
79
+ frame_idx += 1
80
+
81
+ next if max_depth != 0 && frame_idx >= drop_from_idx && frame_idx < drop_until_idx
82
+
83
+ map[frame_idx] = location
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'actions_handler/serializable_backtrace'
4
+
3
5
  module Datadog
4
6
  module AppSec
5
7
  # this module encapsulates functions for handling actions that libddawf returns
@@ -19,7 +21,26 @@ module Datadog
19
21
  throw(Datadog::AppSec::Ext::INTERRUPT, action_params)
20
22
  end
21
23
 
22
- def generate_stack(_action_params); end
24
+ def generate_stack(action_params)
25
+ return unless Datadog.configuration.appsec.stack_trace.enabled
26
+
27
+ stack_id = action_params['stack_id']
28
+ return unless stack_id
29
+
30
+ active_span = AppSec.active_context&.span
31
+ return unless active_span
32
+
33
+ event_category = Ext::EXPLOIT_PREVENTION_EVENT_CATEGORY
34
+ tag_key = Ext::TAG_METASTRUCT_STACK_TRACE
35
+
36
+ existing_stack_data = active_span.get_metastruct_tag(tag_key).dup || { event_category => [] }
37
+ max_stack_traces = Datadog.configuration.appsec.stack_trace.max_stack_traces
38
+ return if max_stack_traces != 0 && existing_stack_data[event_category].count >= max_stack_traces
39
+
40
+ backtrace = SerializableBacktrace.new(locations: Array(caller_locations), stack_id: stack_id)
41
+ existing_stack_data[event_category] << backtrace
42
+ active_span.set_metastruct_tag(tag_key, existing_stack_data)
43
+ end
23
44
 
24
45
  def generate_schema(_action_params); end
25
46
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest/sha2'
4
+
5
+ module Datadog
6
+ module AppSec
7
+ # Manual anonymization of the potential PII data
8
+ module Anonymizer
9
+ def self.anonymize(payload)
10
+ raise ArgumentError, "expected String, received #{payload.class}" unless payload.is_a?(String)
11
+
12
+ "anon_#{Digest::SHA256.hexdigest(payload)[0, 32]}"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -164,6 +164,66 @@ module Datadog
164
164
  end
165
165
  end
166
166
 
167
+ settings :stack_trace do
168
+ option :enabled do |o|
169
+ o.type :bool
170
+ o.env 'DD_APPSEC_STACK_TRACE_ENABLED'
171
+ o.default true
172
+ end
173
+
174
+ # The maximum number of stack trace frames to collect for each stack trace.
175
+ #
176
+ # If the stack trace exceeds this limit, the frames are dropped from the middle of the stack trace:
177
+ # 75% of the frames are kept from the top of the stack trace and 25% from the bottom
178
+ # (this percentage is also configurable).
179
+ #
180
+ # Minimum value is 10.
181
+ # Set to zero if you don't want any frames to be dropped.
182
+ #
183
+ # Default value is 32
184
+ option :max_depth do |o|
185
+ o.type :int
186
+ o.env 'DD_APPSEC_MAX_STACK_TRACE_DEPTH'
187
+ o.default 32
188
+
189
+ o.setter do |value|
190
+ value = 0 if value < 0
191
+ value
192
+ end
193
+ end
194
+
195
+ # The percentage of frames to keep from the top of the stack trace.
196
+ #
197
+ # Default value is 75
198
+ option :top_percentage do |o|
199
+ o.type :int
200
+ o.env 'DD_APPSEC_MAX_STACK_TRACE_DEPTH_TOP_PERCENT'
201
+ o.default 75
202
+
203
+ o.setter do |value|
204
+ value = 100 if value > 100
205
+ value = 0 if value.negative?
206
+ value
207
+ end
208
+ end
209
+
210
+ # Maximum number of stack traces to collect per span.
211
+ #
212
+ # Set to zero if you want to collect all stack traces.
213
+ #
214
+ # Default value is 2
215
+ option :max_stack_traces do |o|
216
+ o.type :int
217
+ o.env 'DD_APPSEC_MAX_STACK_TRACES'
218
+ o.default 2
219
+
220
+ o.setter do |value|
221
+ value = 0 if value < 0
222
+ value
223
+ end
224
+ end
225
+ end
226
+
167
227
  settings :auto_user_instrumentation do
168
228
  define_method(:enabled?) { get_option(:mode) != DISABLED_AUTO_USER_INSTRUMENTATION_MODE }
169
229
 
@@ -178,10 +238,10 @@ module Datadog
178
238
  Datadog.logger.warn(
179
239
  'The appsec.auto_user_instrumentation.mode value provided is not supported. ' \
180
240
  "Supported values are: #{AUTO_USER_INSTRUMENTATION_MODES.join(' | ')}. " \
181
- "Using default value: #{IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE}."
241
+ "Using value: #{DISABLED_AUTO_USER_INSTRUMENTATION_MODE}."
182
242
  )
183
243
 
184
- IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE
244
+ DISABLED_AUTO_USER_INSTRUMENTATION_MODE
185
245
  end
186
246
  end
187
247
  end
@@ -259,14 +319,6 @@ module Datadog
259
319
  o.type :bool, nilable: true
260
320
  o.env 'DD_APPSEC_SCA_ENABLED'
261
321
  end
262
-
263
- settings :standalone do
264
- option :enabled do |o|
265
- o.type :bool
266
- o.env 'DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED'
267
- o.default false
268
- end
269
- end
270
322
  end
271
323
  end
272
324
  end
@@ -9,7 +9,7 @@ module Datadog
9
9
  def self.patch_all
10
10
  integrations = []
11
11
 
12
- Datadog::AppSec::Contrib::Integration.registry.each do |_name, integration|
12
+ Datadog::AppSec::Contrib::Integration.registry.each_value do |integration|
13
13
  next unless integration.klass.auto_instrument?
14
14
 
15
15
  integrations << integration.name
@@ -7,19 +7,11 @@ module Datadog
7
7
  # A temporary configuration module to accomodate new RFC changes.
8
8
  # NOTE: DEV-3 Remove module
9
9
  module Configuration
10
- MODES_CONVERSION_RULES = {
11
- track_user_to_auto_instrumentation: {
12
- AppSec::Configuration::Settings::SAFE_TRACK_USER_EVENTS_MODE =>
10
+ TRACK_USER_EVENTS_CONVERSION_RULES = {
11
+ AppSec::Configuration::Settings::SAFE_TRACK_USER_EVENTS_MODE =>
13
12
  AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE,
14
- AppSec::Configuration::Settings::EXTENDED_TRACK_USER_EVENTS_MODE =>
13
+ AppSec::Configuration::Settings::EXTENDED_TRACK_USER_EVENTS_MODE =>
15
14
  AppSec::Configuration::Settings::IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE
16
- }.freeze,
17
- auto_instrumentation_to_track_user: {
18
- AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE =>
19
- AppSec::Configuration::Settings::SAFE_TRACK_USER_EVENTS_MODE,
20
- AppSec::Configuration::Settings::IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE =>
21
- AppSec::Configuration::Settings::EXTENDED_TRACK_USER_EVENTS_MODE
22
- }.freeze
23
15
  }.freeze
24
16
 
25
17
  module_function
@@ -44,30 +36,14 @@ module Datadog
44
36
  appsec.auto_user_instrumentation.mode
45
37
  appsec.track_user_events.mode
46
38
 
47
- if !appsec.auto_user_instrumentation.options[:mode].default_precedence? &&
48
- appsec.track_user_events.options[:mode].default_precedence?
49
- return appsec.auto_user_instrumentation.mode
50
- end
51
-
52
- if appsec.auto_user_instrumentation.options[:mode].default_precedence?
53
- return MODES_CONVERSION_RULES[:track_user_to_auto_instrumentation].fetch(
39
+ if !appsec.track_user_events.options[:mode].default_precedence? &&
40
+ appsec.auto_user_instrumentation.options[:mode].default_precedence?
41
+ return TRACK_USER_EVENTS_CONVERSION_RULES.fetch(
54
42
  appsec.track_user_events.mode, appsec.auto_user_instrumentation.mode
55
43
  )
56
44
  end
57
45
 
58
- identification_mode = AppSec::Configuration::Settings::IDENTIFICATION_AUTO_USER_INSTRUMENTATION_MODE
59
- if appsec.auto_user_instrumentation.mode == identification_mode ||
60
- appsec.track_user_events.mode == AppSec::Configuration::Settings::EXTENDED_TRACK_USER_EVENTS_MODE
61
- return identification_mode
62
- end
63
-
64
- AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE
65
- end
66
-
67
- # NOTE: Remove in next version of tracking
68
- def track_user_events_mode
69
- MODES_CONVERSION_RULES[:auto_instrumentation_to_track_user]
70
- .fetch(auto_user_instrumentation_mode, Datadog.configuration.appsec.track_user_events.mode)
46
+ appsec.auto_user_instrumentation.mode
71
47
  end
72
48
  end
73
49
  end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../anonymizer'
4
+
5
+ module Datadog
6
+ module AppSec
7
+ module Contrib
8
+ module Devise
9
+ # Extracts user identification data from Devise resources.
10
+ # Supports both regular and anonymized data extraction modes.
11
+ class DataExtractor
12
+ PRIORITY_ORDERED_ID_KEYS = [:id, 'id', :uuid, 'uuid'].freeze
13
+ PRIORITY_ORDERED_LOGIN_KEYS = [:email, 'email', :username, 'username', :login, 'login'].freeze
14
+
15
+ def initialize(mode:)
16
+ @mode = mode
17
+ @devise_scopes = {}
18
+ end
19
+
20
+ def extract_id(object)
21
+ return if object.nil?
22
+
23
+ if object.respond_to?(:[])
24
+ id = object[PRIORITY_ORDERED_ID_KEYS.find { |key| object[key] }]
25
+ scope = find_devise_scope(object)
26
+
27
+ id = "#{scope}:#{id}" if id && scope
28
+ return transform(id)
29
+ end
30
+
31
+ id = object.id if object.respond_to?(:id)
32
+ id ||= object.uuid if object.respond_to?(:uuid)
33
+
34
+ scope = find_devise_scope(object)
35
+ id = "#{scope}:#{id}" if id && scope
36
+
37
+ transform(id)
38
+ end
39
+
40
+ def extract_login(object)
41
+ return if object.nil?
42
+
43
+ if object.respond_to?(:[])
44
+ login = object[PRIORITY_ORDERED_LOGIN_KEYS.find { |key| object[key] }]
45
+ return transform(login)
46
+ end
47
+
48
+ login = object.email if object.respond_to?(:email)
49
+ login ||= object.username if object.respond_to?(:username)
50
+ login ||= object.login if object.respond_to?(:login)
51
+
52
+ transform(login)
53
+ end
54
+
55
+ private
56
+
57
+ def find_devise_scope(object)
58
+ return if ::Devise.mappings.count == 1
59
+
60
+ @devise_scopes[object.class.name] ||= begin
61
+ ::Devise.mappings.each_value.find { |mapping| mapping.class_name == object.class.name }&.name
62
+ end
63
+ end
64
+
65
+ def transform(value)
66
+ return if value.nil?
67
+ return value.to_s unless anonymize?
68
+
69
+ Anonymizer.anonymize(value.to_s)
70
+ end
71
+
72
+ def anonymize?
73
+ @mode == AppSec::Configuration::Settings::ANONYMIZATION_AUTO_USER_INSTRUMENTATION_MODE
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -6,6 +6,27 @@ module Datadog
6
6
  module Devise
7
7
  # Devise integration constants
8
8
  module Ext
9
+ EVENT_LOGIN_SUCCESS = 'users.login.success'
10
+ EVENT_LOGIN_FAILURE = 'users.login.failure'
11
+ EVENT_SIGNUP = 'users.signup'
12
+
13
+ TAG_DD_USR_ID = '_dd.appsec.usr.id'
14
+ TAG_DD_USR_LOGIN = '_dd.appsec.usr.login'
15
+ TAG_DD_SIGNUP_MODE = '_dd.appsec.events.users.signup.auto.mode'
16
+ TAG_DD_COLLECTION_MODE = '_dd.appsec.user.collection_mode'
17
+ TAG_DD_LOGIN_SUCCESS_MODE = '_dd.appsec.events.users.login.success.auto.mode'
18
+ TAG_DD_LOGIN_FAILURE_MODE = '_dd.appsec.events.users.login.failure.auto.mode'
19
+
20
+ TAG_USR_ID = 'usr.id'
21
+ TAG_SIGNUP_TRACK = 'appsec.events.users.signup.track'
22
+ TAG_SIGNUP_USR_ID = 'appsec.events.users.signup.usr.id'
23
+ TAG_SIGNUP_USR_LOGIN = 'appsec.events.users.signup.usr.login'
24
+ TAG_LOGIN_FAILURE_TRACK = 'appsec.events.users.login.failure.track'
25
+ TAG_LOGIN_FAILURE_USR_ID = 'appsec.events.users.login.failure.usr.id'
26
+ TAG_LOGIN_FAILURE_USR_LOGIN = 'appsec.events.users.login.failure.usr.login'
27
+ TAG_LOGIN_FAILURE_USR_EXISTS = 'appsec.events.users.login.failure.usr.exists'
28
+ TAG_LOGIN_SUCCESS_TRACK = 'appsec.events.users.login.success.track'
29
+ TAG_LOGIN_SUCCESS_USR_LOGIN = 'appsec.events.users.login.success.usr.login'
9
30
  end
10
31
  end
11
32
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../integration'
4
-
5
4
  require_relative 'patcher'
6
5
 
7
6
  module Datadog
@@ -1,15 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'patcher/authenticatable_patch'
4
- require_relative 'patcher/rememberable_patch'
5
- require_relative 'patcher/registration_controller_patch'
3
+ require_relative '../../../core/utils/only_once'
4
+
5
+ require_relative 'tracking_middleware'
6
+ require_relative 'patches/signup_tracking_patch'
7
+ require_relative 'patches/signin_tracking_patch'
8
+ require_relative 'patches/skip_signin_tracking_patch'
6
9
 
7
10
  module Datadog
8
11
  module AppSec
9
12
  module Contrib
10
13
  module Devise
11
- # Patcher for AppSec on Devise
14
+ # Devise patcher
12
15
  module Patcher
16
+ GUARD_ONCE_PER_APP = Hash.new do |hash, key|
17
+ hash[key] = Datadog::Core::Utils::OnlyOnce.new
18
+ end
19
+
13
20
  module_function
14
21
 
15
22
  def patched?
@@ -21,29 +28,35 @@ module Datadog
21
28
  end
22
29
 
23
30
  def patch
24
- patch_authenticatable_strategy
25
- patch_rememberable_strategy
26
- patch_registration_controller
27
-
28
- Patcher.instance_variable_set(:@patched, true)
29
- end
30
-
31
- def patch_authenticatable_strategy
32
- ::Devise::Strategies::Authenticatable.prepend(AuthenticatablePatch)
33
- end
31
+ ::ActiveSupport.on_load(:before_initialize) do |app|
32
+ GUARD_ONCE_PER_APP[app].run do
33
+ begin
34
+ app.middleware.insert_after(Warden::Manager, TrackingMiddleware)
35
+ rescue RuntimeError
36
+ AppSec.telemetry.error('AppSec: unable to insert Devise TrackingMiddleware')
37
+ end
38
+ end
39
+ end
34
40
 
35
- def patch_rememberable_strategy
36
- return unless ::Devise::STRATEGIES.include?(:rememberable)
41
+ ::ActiveSupport.on_load(:after_initialize) do
42
+ if ::Devise::RegistrationsController.descendants.empty?
43
+ ::Devise::RegistrationsController.prepend(Patches::SignupTrackingPatch)
44
+ else
45
+ ::Devise::RegistrationsController.descendants.each do |controller|
46
+ controller.prepend(Patches::SignupTrackingPatch)
47
+ end
48
+ end
49
+ end
37
50
 
38
- # Rememberable strategy is required in autoloaded Rememberable model
39
- ::Devise::Models::Rememberable # rubocop:disable Lint/Void
40
- ::Devise::Strategies::Rememberable.prepend(RememberablePatch)
41
- end
51
+ ::Devise::Strategies::Authenticatable.prepend(Patches::SigninTrackingPatch)
42
52
 
43
- def patch_registration_controller
44
- ::ActiveSupport.on_load(:after_initialize) do
45
- ::Devise::RegistrationsController.prepend(RegistrationControllerPatch)
53
+ if ::Devise::STRATEGIES.include?(:rememberable)
54
+ # Rememberable strategy is required in autoloaded Rememberable model
55
+ require 'devise/models/rememberable'
56
+ ::Devise::Strategies::Rememberable.prepend(Patches::SkipSigninTrackingPatch)
46
57
  end
58
+
59
+ Patcher.instance_variable_set(:@patched, true)
47
60
  end
48
61
  end
49
62
  end